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 } 296