1 /* 2 * Copyright 2007 Jacek Caban for CodeWeavers 3 * Copyright 2011 Owen Rudge for CodeWeavers 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 22 #include "hhctrl.h" 23 #include "stream.h" 24 #include "resource.h" 25 26 #include "wine/debug.h" 27 28 WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); 29 30 typedef enum { 31 INSERT_NEXT, 32 INSERT_CHILD 33 } insert_type_t; 34 35 static void free_content_item(ContentItem *item) 36 { 37 ContentItem *next; 38 39 while(item) { 40 next = item->next; 41 42 free_content_item(item->child); 43 44 heap_free(item->name); 45 heap_free(item->local); 46 heap_free(item->merge.chm_file); 47 heap_free(item->merge.chm_index); 48 49 item = next; 50 } 51 } 52 53 static void parse_obj_node_param(ContentItem *item, ContentItem *hhc_root, const char *text, UINT code_page) 54 { 55 const char *ptr; 56 LPWSTR *param, merge; 57 int len; 58 59 ptr = get_attr(text, "name", &len); 60 if(!ptr) { 61 WARN("name attr not found\n"); 62 return; 63 } 64 65 if(!_strnicmp("name", ptr, len)) { 66 param = &item->name; 67 }else if(!_strnicmp("merge", ptr, len)) { 68 param = &merge; 69 }else if(!_strnicmp("local", ptr, len)) { 70 param = &item->local; 71 }else { 72 WARN("unhandled param %s\n", debugstr_an(ptr, len)); 73 return; 74 } 75 76 ptr = get_attr(text, "value", &len); 77 if(!ptr) { 78 WARN("value attr not found\n"); 79 return; 80 } 81 82 /* 83 * "merge" parameter data (referencing another CHM file) can be incorporated into the "local" parameter 84 * by specifying the filename in the format: 85 * MS-ITS:file.chm::/local_path.htm 86 */ 87 if(param == &item->local && strstr(ptr, "::")) 88 { 89 const char *local = strstr(ptr, "::")+2; 90 int local_len = len-(local-ptr); 91 92 item->local = decode_html(local, local_len, code_page); 93 param = &merge; 94 } 95 96 *param = decode_html(ptr, len, code_page); 97 98 if(param == &merge) { 99 SetChmPath(&item->merge, hhc_root->merge.chm_file, merge); 100 heap_free(merge); 101 } 102 } 103 104 static ContentItem *parse_hhc(HHInfo*,IStream*,ContentItem*,insert_type_t*); 105 106 static ContentItem *insert_item(ContentItem *item, ContentItem *new_item, insert_type_t insert_type) 107 { 108 if(!item) 109 return new_item; 110 111 if(!new_item) 112 return item; 113 114 switch(insert_type) { 115 case INSERT_NEXT: 116 item->next = new_item; 117 return new_item; 118 case INSERT_CHILD: 119 if(item->child) { 120 ContentItem *iter = item->child; 121 while(iter->next) 122 iter = iter->next; 123 iter->next = new_item; 124 }else { 125 item->child = new_item; 126 } 127 return item; 128 } 129 130 return NULL; 131 } 132 133 static ContentItem *parse_sitemap_object(HHInfo *info, stream_t *stream, ContentItem *hhc_root, 134 insert_type_t *insert_type) 135 { 136 strbuf_t node, node_name; 137 ContentItem *item; 138 139 *insert_type = INSERT_NEXT; 140 141 strbuf_init(&node); 142 strbuf_init(&node_name); 143 144 item = heap_alloc_zero(sizeof(ContentItem)); 145 146 while(next_node(stream, &node)) { 147 get_node_name(&node, &node_name); 148 149 TRACE("%s\n", node.buf); 150 151 if(!_strnicmp(node_name.buf, "/object", -1)) 152 break; 153 if(!_strnicmp(node_name.buf, "param", -1)) 154 parse_obj_node_param(item, hhc_root, node.buf, info->pCHMInfo->codePage); 155 156 strbuf_zero(&node); 157 } 158 159 strbuf_free(&node); 160 strbuf_free(&node_name); 161 162 if(item->merge.chm_index) { 163 IStream *merge_stream; 164 165 merge_stream = GetChmStream(info->pCHMInfo, item->merge.chm_file, &item->merge); 166 if(merge_stream) { 167 item->child = parse_hhc(info, merge_stream, hhc_root, insert_type); 168 IStream_Release(merge_stream); 169 }else { 170 WARN("Could not get %s::%s stream\n", debugstr_w(item->merge.chm_file), 171 debugstr_w(item->merge.chm_file)); 172 173 if(!item->name) { 174 free_content_item(item); 175 item = NULL; 176 } 177 } 178 179 } 180 181 return item; 182 } 183 184 static ContentItem *parse_ul(HHInfo *info, stream_t *stream, ContentItem *hhc_root) 185 { 186 strbuf_t node, node_name; 187 ContentItem *ret = NULL, *prev = NULL, *new_item = NULL; 188 insert_type_t it; 189 190 strbuf_init(&node); 191 strbuf_init(&node_name); 192 193 while(next_node(stream, &node)) { 194 get_node_name(&node, &node_name); 195 196 TRACE("%s\n", node.buf); 197 198 if(!_strnicmp(node_name.buf, "object", -1)) { 199 const char *ptr; 200 int len; 201 202 static const char sz_text_sitemap[] = "text/sitemap"; 203 204 ptr = get_attr(node.buf, "type", &len); 205 206 if(ptr && len == sizeof(sz_text_sitemap)-1 207 && !memcmp(ptr, sz_text_sitemap, len)) { 208 new_item = parse_sitemap_object(info, stream, hhc_root, &it); 209 prev = insert_item(prev, new_item, it); 210 if(!ret) 211 ret = prev; 212 } 213 }else if(!_strnicmp(node_name.buf, "ul", -1)) { 214 new_item = parse_ul(info, stream, hhc_root); 215 insert_item(prev, new_item, INSERT_CHILD); 216 }else if(!_strnicmp(node_name.buf, "/ul", -1)) { 217 break; 218 } 219 220 strbuf_zero(&node); 221 } 222 223 strbuf_free(&node); 224 strbuf_free(&node_name); 225 226 return ret; 227 } 228 229 static ContentItem *parse_hhc(HHInfo *info, IStream *str, ContentItem *hhc_root, 230 insert_type_t *insert_type) 231 { 232 stream_t stream; 233 strbuf_t node, node_name; 234 ContentItem *ret = NULL, *prev = NULL; 235 236 *insert_type = INSERT_NEXT; 237 238 strbuf_init(&node); 239 strbuf_init(&node_name); 240 241 stream_init(&stream, str); 242 243 while(next_node(&stream, &node)) { 244 get_node_name(&node, &node_name); 245 246 TRACE("%s\n", node.buf); 247 248 if(!_strnicmp(node_name.buf, "ul", -1)) { 249 ContentItem *item = parse_ul(info, &stream, hhc_root); 250 prev = insert_item(prev, item, INSERT_CHILD); 251 if(!ret) 252 ret = prev; 253 *insert_type = INSERT_CHILD; 254 } 255 256 strbuf_zero(&node); 257 } 258 259 strbuf_free(&node); 260 strbuf_free(&node_name); 261 262 return ret; 263 } 264 265 static void insert_content_item(HWND hwnd, ContentItem *parent, ContentItem *item) 266 { 267 TVINSERTSTRUCTW tvis; 268 269 memset(&tvis, 0, sizeof(tvis)); 270 tvis.u.item.mask = TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE; 271 tvis.u.item.cchTextMax = lstrlenW(item->name)+1; 272 tvis.u.item.pszText = item->name; 273 tvis.u.item.lParam = (LPARAM)item; 274 tvis.u.item.iImage = item->child ? HHTV_FOLDER : HHTV_DOCUMENT; 275 tvis.u.item.iSelectedImage = item->child ? HHTV_FOLDER : HHTV_DOCUMENT; 276 tvis.hParent = parent ? parent->id : 0; 277 tvis.hInsertAfter = TVI_LAST; 278 279 item->id = (HTREEITEM)SendMessageW(hwnd, TVM_INSERTITEMW, 0, (LPARAM)&tvis); 280 } 281 282 static void fill_content_tree(HWND hwnd, ContentItem *parent, ContentItem *item) 283 { 284 while(item) { 285 if(item->name) { 286 insert_content_item(hwnd, parent, item); 287 fill_content_tree(hwnd, item, item->child); 288 }else { 289 fill_content_tree(hwnd, parent, item->child); 290 } 291 item = item->next; 292 } 293 } 294 295 static void set_item_parents(ContentItem *parent, ContentItem *item) 296 { 297 while(item) { 298 item->parent = parent; 299 set_item_parents(item, item->child); 300 item = item->next; 301 } 302 } 303 304 void InitContent(HHInfo *info) 305 { 306 IStream *stream; 307 insert_type_t insert_type; 308 309 info->content = heap_alloc_zero(sizeof(ContentItem)); 310 SetChmPath(&info->content->merge, info->pCHMInfo->szFile, info->WinType.pszToc); 311 312 stream = GetChmStream(info->pCHMInfo, info->pCHMInfo->szFile, &info->content->merge); 313 if(!stream) { 314 TRACE("Could not get content stream\n"); 315 return; 316 } 317 318 info->content->child = parse_hhc(info, stream, info->content, &insert_type); 319 IStream_Release(stream); 320 321 set_item_parents(NULL, info->content); 322 fill_content_tree(info->tabs[TAB_CONTENTS].hwnd, NULL, info->content); 323 } 324 325 void ReleaseContent(HHInfo *info) 326 { 327 free_content_item(info->content); 328 } 329 330 void ActivateContentTopic(HWND hWnd, LPCWSTR filename, ContentItem *item) 331 { 332 if (lstrcmpiW(item->local, filename) == 0) 333 { 334 SendMessageW(hWnd, TVM_SELECTITEM, TVGN_CARET, (LPARAM) item->id); 335 return; 336 } 337 338 if (item->next) 339 ActivateContentTopic(hWnd, filename, item->next); 340 341 if (item->child) 342 ActivateContentTopic(hWnd, filename, item->child); 343 } 344