[HHCTRL.OCX] Sync with Wine Staging 4.18. CORE-16441
[reactos.git] / dll / win32 / 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 #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 }