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