| /* | 
 |  * Copyright 2007 Jacek Caban for CodeWeavers | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  */ | 
 |  | 
 | #define NONAMELESSUNION | 
 | #define NONAMELESSSTRUCT | 
 |  | 
 | #include "hhctrl.h" | 
 |  | 
 | #include "wine/debug.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); | 
 |  | 
 | #define BLOCK_SIZE 0x1000 | 
 |  | 
 | typedef enum { | 
 |     INSERT_NEXT, | 
 |     INSERT_CHILD | 
 | } insert_type_t; | 
 |  | 
 | static void free_content_item(ContentItem *item) | 
 | { | 
 |     ContentItem *next; | 
 |  | 
 |     while(item) { | 
 |         next = item->next; | 
 |  | 
 |         free_content_item(item->child); | 
 |  | 
 |         heap_free(item->name); | 
 |         heap_free(item->local); | 
 |         heap_free(item->merge.chm_file); | 
 |         heap_free(item->merge.chm_index); | 
 |  | 
 |         item = next; | 
 |     } | 
 | } | 
 |  | 
 | typedef struct { | 
 |     char *buf; | 
 |     int size; | 
 |     int len; | 
 | } strbuf_t; | 
 |  | 
 | static void strbuf_init(strbuf_t *buf) | 
 | { | 
 |     buf->size = 8; | 
 |     buf->len = 0; | 
 |     buf->buf = heap_alloc(buf->size); | 
 | } | 
 |  | 
 | static void strbuf_zero(strbuf_t *buf) | 
 | { | 
 |     buf->len = 0; | 
 | } | 
 |  | 
 | static void strbuf_free(strbuf_t *buf) | 
 | { | 
 |     heap_free(buf->buf); | 
 | } | 
 |  | 
 | static void strbuf_append(strbuf_t *buf, const char *data, int len) | 
 | { | 
 |     if(buf->len+len > buf->size) { | 
 |         buf->size = buf->len+len; | 
 |         buf->buf = heap_realloc(buf->buf, buf->size); | 
 |     } | 
 |  | 
 |     memcpy(buf->buf+buf->len, data, len); | 
 |     buf->len += len; | 
 | } | 
 |  | 
 | typedef struct { | 
 |     IStream *str; | 
 |     char buf[BLOCK_SIZE]; | 
 |     ULONG size; | 
 |     ULONG p; | 
 | } stream_t; | 
 |  | 
 | static void stream_init(stream_t *stream, IStream *str) | 
 | { | 
 |     memset(stream, 0, sizeof(stream_t)); | 
 |     stream->str = str; | 
 | } | 
 |  | 
 | static BOOL stream_chr(stream_t *stream, strbuf_t *buf, char c) | 
 | { | 
 |     BOOL b = TRUE; | 
 |     ULONG i; | 
 |  | 
 |     while(b) { | 
 |         for(i=stream->p; i<stream->size; i++) { | 
 |             if(stream->buf[i] == c) { | 
 |                 b = FALSE; | 
 |                 break; | 
 |             } | 
 |         } | 
 |  | 
 |         if(buf && i > stream->p) | 
 |             strbuf_append(buf, stream->buf+stream->p, i-stream->p); | 
 |         stream->p = i; | 
 |  | 
 |         if(stream->p == stream->size) { | 
 |             stream->p = 0; | 
 |             IStream_Read(stream->str, stream->buf, sizeof(stream->buf), &stream->size); | 
 |             if(!stream->size) | 
 |                 break; | 
 |         } | 
 |     } | 
 |  | 
 |     return stream->size != 0; | 
 | } | 
 |  | 
 | static void get_node_name(strbuf_t *node, strbuf_t *name) | 
 | { | 
 |     const char *ptr = node->buf+1; | 
 |  | 
 |     strbuf_zero(name); | 
 |  | 
 |     while(*ptr != '>' && !isspace(*ptr)) | 
 |         ptr++; | 
 |  | 
 |     strbuf_append(name, node->buf+1, ptr-node->buf-1); | 
 |     strbuf_append(name, "", 1); | 
 | } | 
 |  | 
 | static BOOL next_node(stream_t *stream, strbuf_t *buf) | 
 | { | 
 |     if(!stream_chr(stream, NULL, '<')) | 
 |         return FALSE; | 
 |  | 
 |     if(!stream_chr(stream, buf, '>')) | 
 |         return FALSE; | 
 |  | 
 |     strbuf_append(buf, ">", 2); | 
 |  | 
 |     return TRUE; | 
 | } | 
 |  | 
 | static const char *get_attr(const char *node, const char *name, int *len) | 
 | { | 
 |     const char *ptr, *ptr2; | 
 |     char name_buf[32]; | 
 |     int nlen; | 
 |  | 
 |     nlen = strlen(name); | 
 |     memcpy(name_buf, name, nlen); | 
 |     name_buf[nlen++] = '='; | 
 |     name_buf[nlen++] = '\"'; | 
 |     name_buf[nlen] = 0; | 
 |  | 
 |     ptr = strstr(node, name_buf); | 
 |     if(!ptr) { | 
 |         WARN("name not found\n"); | 
 |         return NULL; | 
 |     } | 
 |  | 
 |     ptr += nlen; | 
 |     ptr2 = strchr(ptr, '\"'); | 
 |     if(!ptr2) | 
 |         return NULL; | 
 |  | 
 |     *len = ptr2-ptr; | 
 |     return ptr; | 
 | } | 
 |  | 
 | static void parse_obj_node_param(ContentItem *item, ContentItem *hhc_root, const char *text) | 
 | { | 
 |     const char *ptr; | 
 |     LPWSTR *param, merge; | 
 |     int len, wlen; | 
 |  | 
 |     ptr = get_attr(text, "name", &len); | 
 |     if(!ptr) { | 
 |         WARN("name attr not found\n"); | 
 |         return; | 
 |     } | 
 |  | 
 |     if(!strncasecmp("name", ptr, len)) { | 
 |         param = &item->name; | 
 |     }else if(!strncasecmp("merge", ptr, len)) { | 
 |         param = &merge; | 
 |     }else if(!strncasecmp("local", ptr, len)) { | 
 |         param = &item->local; | 
 |     }else { | 
 |         WARN("unhandled param %s\n", debugstr_an(ptr, len)); | 
 |         return; | 
 |     } | 
 |  | 
 |     ptr = get_attr(text, "value", &len); | 
 |     if(!ptr) { | 
 |         WARN("value attr not found\n"); | 
 |         return; | 
 |     } | 
 |  | 
 |     wlen = MultiByteToWideChar(CP_ACP, 0, ptr, len, NULL, 0); | 
 |     *param = heap_alloc((wlen+1)*sizeof(WCHAR)); | 
 |     MultiByteToWideChar(CP_ACP, 0, ptr, len, *param, wlen); | 
 |     (*param)[wlen] = 0; | 
 |  | 
 |     if(param == &merge) { | 
 |         SetChmPath(&item->merge, hhc_root->merge.chm_file, merge); | 
 |         heap_free(merge); | 
 |     } | 
 | } | 
 |  | 
 | static ContentItem *parse_hhc(HHInfo*,IStream*,ContentItem*,insert_type_t*); | 
 |  | 
 | static ContentItem *insert_item(ContentItem *item, ContentItem *new_item, insert_type_t insert_type) | 
 | { | 
 |     if(!item) | 
 |         return new_item; | 
 |  | 
 |     if(!new_item) | 
 |         return item; | 
 |  | 
 |     switch(insert_type) { | 
 |     case INSERT_NEXT: | 
 |         item->next = new_item; | 
 |         return new_item; | 
 |     case INSERT_CHILD: | 
 |         if(item->child) { | 
 |             ContentItem *iter = item->child; | 
 |             while(iter->next) | 
 |                 iter = iter->next; | 
 |             iter->next = new_item; | 
 |         }else { | 
 |             item->child = new_item; | 
 |         } | 
 |         return item; | 
 |     } | 
 |  | 
 |     return NULL; | 
 | } | 
 |  | 
 | static ContentItem *parse_sitemap_object(HHInfo *info, stream_t *stream, ContentItem *hhc_root, | 
 |         insert_type_t *insert_type) | 
 | { | 
 |     strbuf_t node, node_name; | 
 |     ContentItem *item; | 
 |  | 
 |     *insert_type = INSERT_NEXT; | 
 |  | 
 |     strbuf_init(&node); | 
 |     strbuf_init(&node_name); | 
 |  | 
 |     item = heap_alloc_zero(sizeof(ContentItem)); | 
 |  | 
 |     while(next_node(stream, &node)) { | 
 |         get_node_name(&node, &node_name); | 
 |  | 
 |         TRACE("%s\n", node.buf); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "/object")) | 
 |             break; | 
 |         if(!strcasecmp(node_name.buf, "param")) | 
 |             parse_obj_node_param(item, hhc_root, node.buf); | 
 |  | 
 |         strbuf_zero(&node); | 
 |     } | 
 |  | 
 |     strbuf_free(&node); | 
 |     strbuf_free(&node_name); | 
 |  | 
 |     if(item->merge.chm_index) { | 
 |         IStream *merge_stream; | 
 |  | 
 |         merge_stream = GetChmStream(info->pCHMInfo, item->merge.chm_file, &item->merge); | 
 |         if(merge_stream) { | 
 |             item->child = parse_hhc(info, merge_stream, hhc_root, insert_type); | 
 |             IStream_Release(merge_stream); | 
 |         }else { | 
 |             WARN("Could not get %s::%s stream\n", debugstr_w(item->merge.chm_file), | 
 |                  debugstr_w(item->merge.chm_file)); | 
 |  | 
 |             if(!item->name) { | 
 |                 free_content_item(item); | 
 |                 item = NULL; | 
 |             } | 
 |         } | 
 |  | 
 |     } | 
 |  | 
 |     return item; | 
 | } | 
 |  | 
 | static ContentItem *parse_ul(HHInfo *info, stream_t *stream, ContentItem *hhc_root) | 
 | { | 
 |     strbuf_t node, node_name; | 
 |     ContentItem *ret = NULL, *prev = NULL, *new_item = NULL; | 
 |     insert_type_t it; | 
 |  | 
 |     strbuf_init(&node); | 
 |     strbuf_init(&node_name); | 
 |  | 
 |     while(next_node(stream, &node)) { | 
 |         get_node_name(&node, &node_name); | 
 |  | 
 |         TRACE("%s\n", node.buf); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "object")) { | 
 |             const char *ptr; | 
 |             int len; | 
 |  | 
 |             static const char sz_text_sitemap[] = "text/sitemap"; | 
 |  | 
 |             ptr = get_attr(node.buf, "type", &len); | 
 |  | 
 |             if(ptr && len == sizeof(sz_text_sitemap)-1 | 
 |                && !memcmp(ptr, sz_text_sitemap, len)) { | 
 |                 new_item = parse_sitemap_object(info, stream, hhc_root, &it); | 
 |                 prev = insert_item(prev, new_item, it); | 
 |                 if(!ret) | 
 |                     ret = prev; | 
 |             } | 
 |         }else if(!strcasecmp(node_name.buf, "ul")) { | 
 |             new_item = parse_ul(info, stream, hhc_root); | 
 |             insert_item(prev, new_item, INSERT_CHILD); | 
 |         }else if(!strcasecmp(node_name.buf, "/ul")) { | 
 |             break; | 
 |         } | 
 |  | 
 |         strbuf_zero(&node); | 
 |     } | 
 |  | 
 |     strbuf_free(&node); | 
 |     strbuf_free(&node_name); | 
 |  | 
 |     return ret; | 
 | } | 
 |  | 
 | static ContentItem *parse_hhc(HHInfo *info, IStream *str, ContentItem *hhc_root, | 
 |         insert_type_t *insert_type) | 
 | { | 
 |     stream_t stream; | 
 |     strbuf_t node, node_name; | 
 |     ContentItem *ret = NULL, *prev = NULL; | 
 |  | 
 |     *insert_type = INSERT_NEXT; | 
 |  | 
 |     strbuf_init(&node); | 
 |     strbuf_init(&node_name); | 
 |  | 
 |     stream_init(&stream, str); | 
 |  | 
 |     while(next_node(&stream, &node)) { | 
 |         get_node_name(&node, &node_name); | 
 |  | 
 |         TRACE("%s\n", node.buf); | 
 |  | 
 |         if(!strcasecmp(node_name.buf, "ul")) { | 
 |             ContentItem *item = parse_ul(info, &stream, hhc_root); | 
 |             prev = insert_item(prev, item, INSERT_CHILD); | 
 |             if(!ret) | 
 |                 ret = prev; | 
 |             *insert_type = INSERT_CHILD; | 
 |         } | 
 |  | 
 |         strbuf_zero(&node); | 
 |     } | 
 |  | 
 |     strbuf_free(&node); | 
 |     strbuf_free(&node_name); | 
 |  | 
 |     return ret; | 
 | } | 
 |  | 
 | static void insert_content_item(HWND hwnd, ContentItem *parent, ContentItem *item) | 
 | { | 
 |     TVINSERTSTRUCTW tvis; | 
 |  | 
 |     memset(&tvis, 0, sizeof(tvis)); | 
 |     tvis.u.item.mask = TVIF_TEXT|TVIF_PARAM; | 
 |     tvis.u.item.cchTextMax = strlenW(item->name)+1; | 
 |     tvis.u.item.pszText = item->name; | 
 |     tvis.u.item.lParam = (LPARAM)item; | 
 |     tvis.hParent = parent ? parent->id : 0; | 
 |     tvis.hInsertAfter = TVI_LAST; | 
 |  | 
 |     item->id = (HTREEITEM)SendMessageW(hwnd, TVM_INSERTITEMW, 0, (LPARAM)&tvis); | 
 | } | 
 |  | 
 | static void fill_content_tree(HWND hwnd, ContentItem *parent, ContentItem *item) | 
 | { | 
 |     while(item) { | 
 |         if(item->name) { | 
 |             insert_content_item(hwnd, parent, item); | 
 |             fill_content_tree(hwnd, item, item->child); | 
 |         }else { | 
 |             fill_content_tree(hwnd, parent, item->child); | 
 |         } | 
 |         item = item->next; | 
 |     } | 
 | } | 
 |  | 
 | static void set_item_parents(ContentItem *parent, ContentItem *item) | 
 | { | 
 |     while(item) { | 
 |         item->parent = parent; | 
 |         set_item_parents(item, item->child); | 
 |         item = item->next; | 
 |     } | 
 | } | 
 |  | 
 | void InitContent(HHInfo *info) | 
 | { | 
 |     IStream *stream; | 
 |     insert_type_t insert_type; | 
 |  | 
 |     info->content = heap_alloc_zero(sizeof(ContentItem)); | 
 |     SetChmPath(&info->content->merge, info->pCHMInfo->szFile, info->WinType.pszToc); | 
 |  | 
 |     stream = GetChmStream(info->pCHMInfo, info->pCHMInfo->szFile, &info->content->merge); | 
 |     if(!stream) { | 
 |         TRACE("Could not get content stream\n"); | 
 |         return; | 
 |     } | 
 |  | 
 |     info->content->child = parse_hhc(info, stream, info->content, &insert_type); | 
 |     IStream_Release(stream); | 
 |  | 
 |     set_item_parents(NULL, info->content); | 
 |     fill_content_tree(info->tabs[TAB_CONTENTS].hwnd, NULL, info->content); | 
 | } | 
 |  | 
 | void ReleaseContent(HHInfo *info) | 
 | { | 
 |     free_content_item(info->content); | 
 | } |