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