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