2010-01-27 09:12:25 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2007 Jacek Caban for CodeWeavers
|
|
|
|
* Copyright 2010 Erich Hoover
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "hhctrl.h"
|
|
|
|
#include "stream.h"
|
|
|
|
|
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp);
|
|
|
|
|
|
|
|
/* Fill the TreeView object corresponding to the Index items */
|
|
|
|
static void fill_index_tree(HWND hwnd, IndexItem *item)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
LVITEMW lvi;
|
|
|
|
|
|
|
|
while(item) {
|
|
|
|
TRACE("tree debug: %s\n", debugstr_w(item->keyword));
|
|
|
|
|
2010-02-07 18:07:56 +01:00
|
|
|
if(!item->keyword)
|
|
|
|
{
|
|
|
|
FIXME("HTML Help index item has no keyword.\n");
|
|
|
|
item = item->next;
|
|
|
|
continue;
|
|
|
|
}
|
2010-01-27 09:12:25 +01:00
|
|
|
memset(&lvi, 0, sizeof(lvi));
|
|
|
|
lvi.iItem = index++;
|
2010-02-07 18:08:18 +01:00
|
|
|
lvi.mask = LVIF_TEXT|LVIF_PARAM|LVIF_INDENT;
|
|
|
|
lvi.iIndent = item->indentLevel;
|
2010-01-27 09:12:25 +01:00
|
|
|
lvi.cchTextMax = strlenW(item->keyword)+1;
|
|
|
|
lvi.pszText = item->keyword;
|
|
|
|
lvi.lParam = (LPARAM)item;
|
|
|
|
item->id = (HTREEITEM)SendMessageW(hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvi);
|
|
|
|
item = item->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-25 21:46:26 +02:00
|
|
|
static void item_realloc(IndexItem *item, int num_items)
|
|
|
|
{
|
|
|
|
item->nItems = num_items;
|
|
|
|
item->items = heap_realloc(item->items, sizeof(IndexSubItem)*item->nItems);
|
|
|
|
item->items[item->nItems-1].name = NULL;
|
|
|
|
item->items[item->nItems-1].local = NULL;
|
|
|
|
item->itemFlags = 0x00;
|
|
|
|
}
|
|
|
|
|
2010-01-27 09:12:25 +01:00
|
|
|
/* Parse the attributes correspond to a list item, including sub-topics.
|
|
|
|
*
|
|
|
|
* Each list item has, at minimum, a param of type "keyword" and two
|
|
|
|
* parameters corresponding to a "sub-topic." For each sub-topic there
|
|
|
|
* must be a "name" param and a "local" param, if there is only one
|
|
|
|
* sub-topic then there isn't really a sub-topic, the index will jump
|
|
|
|
* directly to the requested item.
|
|
|
|
*/
|
2012-06-20 22:31:19 +02:00
|
|
|
static void parse_index_obj_node_param(IndexItem *item, const char *text, UINT code_page)
|
2010-01-27 09:12:25 +01:00
|
|
|
{
|
|
|
|
const char *ptr;
|
|
|
|
LPWSTR *param;
|
2012-06-20 22:31:15 +02:00
|
|
|
int len;
|
2010-01-27 09:12:25 +01:00
|
|
|
|
|
|
|
ptr = get_attr(text, "name", &len);
|
|
|
|
if(!ptr) {
|
|
|
|
WARN("name attr not found\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate a new sub-item, either on the first run or whenever a
|
|
|
|
* sub-topic has filled out both the "name" and "local" params.
|
|
|
|
*/
|
2019-04-03 18:20:14 +02:00
|
|
|
if(item->itemFlags == 0x11 && (!_strnicmp("name", ptr, len) || !_strnicmp("local", ptr, len)))
|
2012-06-25 21:46:26 +02:00
|
|
|
item_realloc(item, item->nItems+1);
|
2019-04-03 18:20:14 +02:00
|
|
|
if(!_strnicmp("keyword", ptr, len)) {
|
2010-01-27 09:12:25 +01:00
|
|
|
param = &item->keyword;
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!item->keyword && !_strnicmp("name", ptr, len)) {
|
2010-02-07 18:07:56 +01:00
|
|
|
/* Some HTML Help index files use an additional "name" parameter
|
|
|
|
* rather than the "keyword" parameter. In this case, the first
|
2010-07-25 03:21:26 +02:00
|
|
|
* occurrence of the "name" parameter is the keyword.
|
2010-02-07 18:07:56 +01:00
|
|
|
*/
|
|
|
|
param = &item->keyword;
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!_strnicmp("name", ptr, len)) {
|
2010-01-27 09:12:25 +01:00
|
|
|
item->itemFlags |= 0x01;
|
|
|
|
param = &item->items[item->nItems-1].name;
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!_strnicmp("local", ptr, len)) {
|
2010-01-27 09:12:25 +01:00
|
|
|
item->itemFlags |= 0x10;
|
|
|
|
param = &item->items[item->nItems-1].local;
|
|
|
|
}else {
|
|
|
|
WARN("unhandled param %s\n", debugstr_an(ptr, len));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr = get_attr(text, "value", &len);
|
|
|
|
if(!ptr) {
|
|
|
|
WARN("value attr not found\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-06-20 22:31:19 +02:00
|
|
|
*param = decode_html(ptr, len, code_page);
|
2010-01-27 09:12:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the object tag corresponding to a list item.
|
|
|
|
*
|
|
|
|
* At this step we look for all of the "param" child tags, using this information
|
|
|
|
* to build up the information about the list item. When we reach the </object>
|
|
|
|
* tag we know that we've finished parsing this list item.
|
|
|
|
*/
|
|
|
|
static IndexItem *parse_index_sitemap_object(HHInfo *info, stream_t *stream)
|
|
|
|
{
|
|
|
|
strbuf_t node, node_name;
|
|
|
|
IndexItem *item;
|
|
|
|
|
|
|
|
strbuf_init(&node);
|
|
|
|
strbuf_init(&node_name);
|
|
|
|
|
|
|
|
item = heap_alloc_zero(sizeof(IndexItem));
|
|
|
|
item->nItems = 0;
|
|
|
|
item->items = heap_alloc_zero(0);
|
|
|
|
item->itemFlags = 0x11;
|
|
|
|
|
|
|
|
while(next_node(stream, &node)) {
|
|
|
|
get_node_name(&node, &node_name);
|
|
|
|
|
|
|
|
TRACE("%s\n", node.buf);
|
|
|
|
|
2019-04-03 18:20:14 +02:00
|
|
|
if(!_strnicmp(node_name.buf, "param", -1)) {
|
2012-06-20 22:31:19 +02:00
|
|
|
parse_index_obj_node_param(item, node.buf, info->pCHMInfo->codePage);
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!_strnicmp(node_name.buf, "/object", -1)) {
|
2010-01-27 09:12:25 +01:00
|
|
|
break;
|
|
|
|
}else {
|
|
|
|
WARN("Unhandled tag! %s\n", node_name.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_zero(&node);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_free(&node);
|
|
|
|
strbuf_free(&node_name);
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the HTML list item node corresponding to a specific help entry.
|
|
|
|
*
|
|
|
|
* At this stage we look for the only child tag we expect to find under
|
|
|
|
* the list item: the <OBJECT> tag. We also only expect to find object
|
|
|
|
* tags with the "type" attribute set to "text/sitemap".
|
|
|
|
*/
|
|
|
|
static IndexItem *parse_li(HHInfo *info, stream_t *stream)
|
|
|
|
{
|
|
|
|
strbuf_t node, node_name;
|
|
|
|
IndexItem *ret = NULL;
|
|
|
|
|
|
|
|
strbuf_init(&node);
|
|
|
|
strbuf_init(&node_name);
|
|
|
|
|
|
|
|
while(next_node(stream, &node)) {
|
|
|
|
get_node_name(&node, &node_name);
|
|
|
|
|
|
|
|
TRACE("%s\n", node.buf);
|
|
|
|
|
2019-04-03 18:20:14 +02:00
|
|
|
if(!_strnicmp(node_name.buf, "object", -1)) {
|
2010-01-27 09:12:25 +01:00
|
|
|
const char *ptr;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
static const char sz_text_sitemap[] = "text/sitemap";
|
|
|
|
|
|
|
|
ptr = get_attr(node.buf, "type", &len);
|
|
|
|
|
|
|
|
if(ptr && len == sizeof(sz_text_sitemap)-1
|
|
|
|
&& !memcmp(ptr, sz_text_sitemap, len)) {
|
|
|
|
ret = parse_index_sitemap_object(info, stream);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}else {
|
|
|
|
WARN("Unhandled tag! %s\n", node_name.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_zero(&node);
|
|
|
|
}
|
2012-06-25 16:54:47 +02:00
|
|
|
if(!ret)
|
|
|
|
FIXME("Failed to parse <li> tag!\n");
|
2010-01-27 09:12:25 +01:00
|
|
|
|
|
|
|
strbuf_free(&node);
|
|
|
|
strbuf_free(&node_name);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse the HTML Help page corresponding to all of the Index items.
|
|
|
|
*
|
|
|
|
* At this high-level stage we locate out each HTML list item tag.
|
|
|
|
* Since there is no end-tag for the <LI> item, we must hope that
|
|
|
|
* the <LI> entry is parsed correctly or tags might get lost.
|
2010-02-07 18:08:18 +01:00
|
|
|
*
|
|
|
|
* Within each entry it is also possible to encounter an additional
|
|
|
|
* <UL> tag. When this occurs the tag indicates that the topics
|
|
|
|
* contained within it are related to the parent <LI> topic and
|
|
|
|
* should be inset by an indent.
|
2010-01-27 09:12:25 +01:00
|
|
|
*/
|
|
|
|
static void parse_hhindex(HHInfo *info, IStream *str, IndexItem *item)
|
|
|
|
{
|
|
|
|
stream_t stream;
|
|
|
|
strbuf_t node, node_name;
|
2010-02-07 18:08:18 +01:00
|
|
|
int indent_level = -1;
|
2010-01-27 09:12:25 +01:00
|
|
|
|
|
|
|
strbuf_init(&node);
|
|
|
|
strbuf_init(&node_name);
|
|
|
|
|
|
|
|
stream_init(&stream, str);
|
|
|
|
|
|
|
|
while(next_node(&stream, &node)) {
|
|
|
|
get_node_name(&node, &node_name);
|
|
|
|
|
|
|
|
TRACE("%s\n", node.buf);
|
|
|
|
|
2019-04-03 18:20:14 +02:00
|
|
|
if(!_strnicmp(node_name.buf, "li", -1)) {
|
2012-06-25 21:46:26 +02:00
|
|
|
IndexItem *new_item;
|
|
|
|
|
|
|
|
new_item = parse_li(info, &stream);
|
|
|
|
if(new_item && item->keyword && strcmpW(new_item->keyword, item->keyword) == 0) {
|
|
|
|
int num_items = item->nItems;
|
|
|
|
|
|
|
|
item_realloc(item, num_items+1);
|
|
|
|
memcpy(&item->items[num_items], &new_item->items[0], sizeof(IndexSubItem));
|
|
|
|
heap_free(new_item->keyword);
|
|
|
|
heap_free(new_item->items);
|
|
|
|
heap_free(new_item);
|
|
|
|
} else if(new_item) {
|
|
|
|
item->next = new_item;
|
2012-06-25 16:54:47 +02:00
|
|
|
item->next->merge = item->merge;
|
|
|
|
item = item->next;
|
|
|
|
item->indentLevel = indent_level;
|
|
|
|
}
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!_strnicmp(node_name.buf, "ul", -1)) {
|
2010-02-07 18:08:18 +01:00
|
|
|
indent_level++;
|
2019-04-03 18:20:14 +02:00
|
|
|
}else if(!_strnicmp(node_name.buf, "/ul", -1)) {
|
2010-02-07 18:08:18 +01:00
|
|
|
indent_level--;
|
2010-01-27 09:12:25 +01:00
|
|
|
}else {
|
|
|
|
WARN("Unhandled tag! %s\n", node_name.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_zero(&node);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_free(&node);
|
|
|
|
strbuf_free(&node_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize the HTML Help Index tab */
|
|
|
|
void InitIndex(HHInfo *info)
|
|
|
|
{
|
|
|
|
IStream *stream;
|
|
|
|
|
|
|
|
info->index = heap_alloc_zero(sizeof(IndexItem));
|
|
|
|
info->index->nItems = 0;
|
|
|
|
SetChmPath(&info->index->merge, info->pCHMInfo->szFile, info->WinType.pszIndex);
|
|
|
|
|
|
|
|
stream = GetChmStream(info->pCHMInfo, info->pCHMInfo->szFile, &info->index->merge);
|
|
|
|
if(!stream) {
|
|
|
|
TRACE("Could not get index stream\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
parse_hhindex(info, stream, info->index);
|
|
|
|
IStream_Release(stream);
|
|
|
|
|
|
|
|
fill_index_tree(info->tabs[TAB_INDEX].hwnd, info->index->next);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free all of the Index items, including all of the "sub-items" that
|
|
|
|
* correspond to different sub-topics.
|
|
|
|
*/
|
|
|
|
void ReleaseIndex(HHInfo *info)
|
|
|
|
{
|
|
|
|
IndexItem *item = info->index, *next;
|
|
|
|
int i;
|
|
|
|
|
2011-12-22 20:12:06 +01:00
|
|
|
if(!item) return;
|
2010-01-27 09:12:25 +01:00
|
|
|
/* Note: item->merge is identical for all items, only free once */
|
|
|
|
heap_free(item->merge.chm_file);
|
|
|
|
heap_free(item->merge.chm_index);
|
|
|
|
while(item) {
|
|
|
|
next = item->next;
|
|
|
|
|
|
|
|
heap_free(item->keyword);
|
|
|
|
for(i=0;i<item->nItems;i++) {
|
|
|
|
heap_free(item->items[i].name);
|
|
|
|
heap_free(item->items[i].local);
|
|
|
|
}
|
|
|
|
heap_free(item->items);
|
|
|
|
|
|
|
|
item = next;
|
|
|
|
}
|
|
|
|
}
|