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