| /* | 
 |  * 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 | 
 |  */ | 
 |  | 
 | #define NONAMELESSUNION | 
 | #define NONAMELESSSTRUCT | 
 |  | 
 | #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)); | 
 |  | 
 |         if(!item->keyword) | 
 |         { | 
 |             FIXME("HTML Help index item has no keyword.\n"); | 
 |             item = item->next; | 
 |             continue; | 
 |         } | 
 |         memset(&lvi, 0, sizeof(lvi)); | 
 |         lvi.iItem = index++; | 
 |         lvi.mask = LVIF_TEXT|LVIF_PARAM|LVIF_INDENT; | 
 |         lvi.iIndent = item->indentLevel; | 
 |         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; | 
 |     } | 
 | } | 
 |  | 
 | /* 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. | 
 |  */ | 
 | static void parse_index_obj_node_param(IndexItem *item, const char *text) | 
 | { | 
 |     const char *ptr; | 
 |     LPWSTR *param; | 
 |     int len, wlen; | 
 |  | 
 |     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. | 
 |      */ | 
 |     if(item->itemFlags == 0x11 && (!strncasecmp("name", ptr, len) || !strncasecmp("local", ptr, len))) { | 
 |         item->nItems++; | 
 |         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; | 
 |     } | 
 |     if(!strncasecmp("keyword", ptr, len)) { | 
 |         param = &item->keyword; | 
 |     }else if(!item->keyword && !strncasecmp("name", ptr, len)) { | 
 |         /* Some HTML Help index files use an additional "name" parameter | 
 |          * rather than the "keyword" parameter.  In this case, the first | 
 |          * occurance of the "name" parameter is the keyword. | 
 |          */ | 
 |         param = &item->keyword; | 
 |     }else if(!strncasecmp("name", ptr, len)) { | 
 |         item->itemFlags |= 0x01; | 
 |         param = &item->items[item->nItems-1].name; | 
 |     }else if(!strncasecmp("local", ptr, len)) { | 
 |         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; | 
 |     } | 
 |  | 
 |     wlen = MultiByteToWideChar(CP_ACP, 0, ptr, len, NULL, 0); | 
 |     *param = heap_alloc((wlen+1)*sizeof(WCHAR)); | 
 |     MultiByteToWideChar(CP_ACP, 0, ptr, len, *param, wlen); | 
 |     (*param)[wlen] = 0; | 
 | } | 
 |  | 
 | /* 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); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "param")) { | 
 |             parse_index_obj_node_param(item, node.buf); | 
 |         }else if(!strcasecmp(node_name.buf, "/object")) { | 
 |             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); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "object")) { | 
 |             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); | 
 |     } | 
 |  | 
 |     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. | 
 |  * | 
 |  * 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. | 
 |  */ | 
 | static void parse_hhindex(HHInfo *info, IStream *str, IndexItem *item) | 
 | { | 
 |     stream_t stream; | 
 |     strbuf_t node, node_name; | 
 |     int indent_level = -1; | 
 |  | 
 |     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); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "li")) { | 
 |             item->next = parse_li(info, &stream); | 
 |             item->next->merge = item->merge; | 
 |             item = item->next; | 
 |             item->indentLevel = indent_level; | 
 |         }else if(!strcasecmp(node_name.buf, "ul")) { | 
 |             indent_level++; | 
 |         }else if(!strcasecmp(node_name.buf, "/ul")) { | 
 |             indent_level--; | 
 |         }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; | 
 |  | 
 |     /* 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; | 
 |     } | 
 | } |