xref: /reactos/dll/win32/hhctrl.ocx/index.c (revision 321bcc05)
1 /*
2  * Copyright 2007 Jacek Caban for CodeWeavers
3  * Copyright 2010 Erich Hoover
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "hhctrl.h"
21 
22 /* Fill the TreeView object corresponding to the Index items */
23 static void fill_index_tree(HWND hwnd, IndexItem *item)
24 {
25     int index = 0;
26     LVITEMW lvi;
27 
28     while(item) {
29         TRACE("tree debug: %s\n", debugstr_w(item->keyword));
30 
31         if(!item->keyword)
32         {
33             FIXME("HTML Help index item has no keyword.\n");
34             item = item->next;
35             continue;
36         }
37         memset(&lvi, 0, sizeof(lvi));
38         lvi.iItem = index++;
39         lvi.mask = LVIF_TEXT|LVIF_PARAM|LVIF_INDENT;
40         lvi.iIndent = item->indentLevel;
41         lvi.cchTextMax = strlenW(item->keyword)+1;
42         lvi.pszText = item->keyword;
43         lvi.lParam = (LPARAM)item;
44         item->id = (HTREEITEM)SendMessageW(hwnd, LVM_INSERTITEMW, 0, (LPARAM)&lvi);
45         item = item->next;
46     }
47 }
48 
49 static void item_realloc(IndexItem *item, int num_items)
50 {
51     item->nItems = num_items;
52     item->items = heap_realloc(item->items, sizeof(IndexSubItem)*item->nItems);
53     item->items[item->nItems-1].name = NULL;
54     item->items[item->nItems-1].local = NULL;
55     item->itemFlags = 0x00;
56 }
57 
58 /* Parse the attributes correspond to a list item, including sub-topics.
59  *
60  * Each list item has, at minimum, a param of type "keyword" and two
61  * parameters corresponding to a "sub-topic."  For each sub-topic there
62  * must be a "name" param and a "local" param, if there is only one
63  * sub-topic then there isn't really a sub-topic, the index will jump
64  * directly to the requested item.
65  */
66 static void parse_index_obj_node_param(IndexItem *item, const char *text, UINT code_page)
67 {
68     const char *ptr;
69     LPWSTR *param;
70     int len;
71 
72     ptr = get_attr(text, "name", &len);
73     if(!ptr) {
74         WARN("name attr not found\n");
75         return;
76     }
77 
78     /* Allocate a new sub-item, either on the first run or whenever a
79      * sub-topic has filled out both the "name" and "local" params.
80      */
81     if(item->itemFlags == 0x11 && (!strncasecmp("name", ptr, len) || !strncasecmp("local", ptr, len)))
82         item_realloc(item, item->nItems+1);
83     if(!strncasecmp("keyword", ptr, len)) {
84         param = &item->keyword;
85     }else if(!item->keyword && !strncasecmp("name", ptr, len)) {
86         /* Some HTML Help index files use an additional "name" parameter
87          * rather than the "keyword" parameter.  In this case, the first
88          * occurrence of the "name" parameter is the keyword.
89          */
90         param = &item->keyword;
91     }else if(!strncasecmp("name", ptr, len)) {
92         item->itemFlags |= 0x01;
93         param = &item->items[item->nItems-1].name;
94     }else if(!strncasecmp("local", ptr, len)) {
95         item->itemFlags |= 0x10;
96         param = &item->items[item->nItems-1].local;
97     }else {
98         WARN("unhandled param %s\n", debugstr_an(ptr, len));
99         return;
100     }
101 
102     ptr = get_attr(text, "value", &len);
103     if(!ptr) {
104         WARN("value attr not found\n");
105         return;
106     }
107 
108     *param = decode_html(ptr, len, code_page);
109 }
110 
111 /* Parse the object tag corresponding to a list item.
112  *
113  * At this step we look for all of the "param" child tags, using this information
114  * to build up the information about the list item.  When we reach the </object>
115  * tag we know that we've finished parsing this list item.
116  */
117 static IndexItem *parse_index_sitemap_object(HHInfo *info, stream_t *stream)
118 {
119     strbuf_t node, node_name;
120     IndexItem *item;
121 
122     strbuf_init(&node);
123     strbuf_init(&node_name);
124 
125     item = heap_alloc_zero(sizeof(IndexItem));
126     item->nItems = 0;
127     item->items = heap_alloc_zero(0);
128     item->itemFlags = 0x11;
129 
130     while(next_node(stream, &node)) {
131         get_node_name(&node, &node_name);
132 
133         TRACE("%s\n", node.buf);
134 
135         if(!strcasecmp(node_name.buf, "param")) {
136             parse_index_obj_node_param(item, node.buf, info->pCHMInfo->codePage);
137         }else if(!strcasecmp(node_name.buf, "/object")) {
138             break;
139         }else {
140             WARN("Unhandled tag! %s\n", node_name.buf);
141         }
142 
143         strbuf_zero(&node);
144     }
145 
146     strbuf_free(&node);
147     strbuf_free(&node_name);
148 
149     return item;
150 }
151 
152 /* Parse the HTML list item node corresponding to a specific help entry.
153  *
154  * At this stage we look for the only child tag we expect to find under
155  * the list item: the <OBJECT> tag.  We also only expect to find object
156  * tags with the "type" attribute set to "text/sitemap".
157  */
158 static IndexItem *parse_li(HHInfo *info, stream_t *stream)
159 {
160     strbuf_t node, node_name;
161     IndexItem *ret = NULL;
162 
163     strbuf_init(&node);
164     strbuf_init(&node_name);
165 
166     while(next_node(stream, &node)) {
167         get_node_name(&node, &node_name);
168 
169         TRACE("%s\n", node.buf);
170 
171         if(!strcasecmp(node_name.buf, "object")) {
172             const char *ptr;
173             int len;
174 
175             static const char sz_text_sitemap[] = "text/sitemap";
176 
177             ptr = get_attr(node.buf, "type", &len);
178 
179             if(ptr && len == sizeof(sz_text_sitemap)-1
180                && !memcmp(ptr, sz_text_sitemap, len)) {
181                 ret = parse_index_sitemap_object(info, stream);
182                 break;
183             }
184         }else {
185             WARN("Unhandled tag! %s\n", node_name.buf);
186         }
187 
188         strbuf_zero(&node);
189     }
190     if(!ret)
191         FIXME("Failed to parse <li> tag!\n");
192 
193     strbuf_free(&node);
194     strbuf_free(&node_name);
195 
196     return ret;
197 }
198 
199 /* Parse the HTML Help page corresponding to all of the Index items.
200  *
201  * At this high-level stage we locate out each HTML list item tag.
202  * Since there is no end-tag for the <LI> item, we must hope that
203  * the <LI> entry is parsed correctly or tags might get lost.
204  *
205  * Within each entry it is also possible to encounter an additional
206  * <UL> tag.  When this occurs the tag indicates that the topics
207  * contained within it are related to the parent <LI> topic and
208  * should be inset by an indent.
209  */
210 static void parse_hhindex(HHInfo *info, IStream *str, IndexItem *item)
211 {
212     stream_t stream;
213     strbuf_t node, node_name;
214     int indent_level = -1;
215 
216     strbuf_init(&node);
217     strbuf_init(&node_name);
218 
219     stream_init(&stream, str);
220 
221     while(next_node(&stream, &node)) {
222         get_node_name(&node, &node_name);
223 
224         TRACE("%s\n", node.buf);
225 
226         if(!strcasecmp(node_name.buf, "li")) {
227             IndexItem *new_item;
228 
229             new_item = parse_li(info, &stream);
230             if(new_item && item->keyword && strcmpW(new_item->keyword, item->keyword) == 0) {
231                 int num_items = item->nItems;
232 
233                 item_realloc(item, num_items+1);
234                 memcpy(&item->items[num_items], &new_item->items[0], sizeof(IndexSubItem));
235                 heap_free(new_item->keyword);
236                 heap_free(new_item->items);
237                 heap_free(new_item);
238             } else if(new_item) {
239                 item->next = new_item;
240                 item->next->merge = item->merge;
241                 item = item->next;
242                 item->indentLevel = indent_level;
243             }
244         }else if(!strcasecmp(node_name.buf, "ul")) {
245             indent_level++;
246         }else if(!strcasecmp(node_name.buf, "/ul")) {
247             indent_level--;
248         }else {
249             WARN("Unhandled tag! %s\n", node_name.buf);
250         }
251 
252         strbuf_zero(&node);
253     }
254 
255     strbuf_free(&node);
256     strbuf_free(&node_name);
257 }
258 
259 /* Initialize the HTML Help Index tab */
260 void InitIndex(HHInfo *info)
261 {
262     IStream *stream;
263 
264     info->index = heap_alloc_zero(sizeof(IndexItem));
265     info->index->nItems = 0;
266     SetChmPath(&info->index->merge, info->pCHMInfo->szFile, info->WinType.pszIndex);
267 
268     stream = GetChmStream(info->pCHMInfo, info->pCHMInfo->szFile, &info->index->merge);
269     if(!stream) {
270         TRACE("Could not get index stream\n");
271         return;
272     }
273 
274     parse_hhindex(info, stream, info->index);
275     IStream_Release(stream);
276 
277     fill_index_tree(info->tabs[TAB_INDEX].hwnd, info->index->next);
278 }
279 
280 /* Free all of the Index items, including all of the "sub-items" that
281  * correspond to different sub-topics.
282  */
283 void ReleaseIndex(HHInfo *info)
284 {
285     IndexItem *item = info->index, *next;
286     int i;
287 
288     if(!item) return;
289     /* Note: item->merge is identical for all items, only free once */
290     heap_free(item->merge.chm_file);
291     heap_free(item->merge.chm_index);
292     while(item) {
293         next = item->next;
294 
295         heap_free(item->keyword);
296         for(i=0;i<item->nItems;i++) {
297             heap_free(item->items[i].name);
298             heap_free(item->items[i].local);
299         }
300         heap_free(item->items);
301 
302         item = next;
303     }
304 }
305