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