| /* | |
| * Copyright 2010 Erich Hoover | |
| * | |
| * 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 "stream.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); | |
| static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage, | |
| const WCHAR *folder, const char *needle); | |
| /* Allocate a ListView entry for a search result. */ | |
| static SearchItem *alloc_search_item(WCHAR *title, const WCHAR *filename) | |
| { | |
| int filename_len = filename ? (strlenW(filename)+1)*sizeof(WCHAR) : 0; | |
| SearchItem *item; | |
| item = heap_alloc_zero(sizeof(SearchItem)); | |
| if(filename) | |
| { | |
| item->filename = heap_alloc(filename_len); | |
| memcpy(item->filename, filename, filename_len); | |
| } | |
| item->title = title; /* Already allocated */ | |
| return item; | |
| } | |
| /* Fill the ListView object corresponding to the found Search tab items */ | |
| static void fill_search_tree(HWND hwndList, SearchItem *item) | |
| { | |
| int index = 0; | |
| LVITEMW lvi; | |
| SendMessageW(hwndList, LVM_DELETEALLITEMS, 0, 0); | |
| while(item) { | |
| TRACE("list debug: %s\n", debugstr_w(item->filename)); | |
| memset(&lvi, 0, sizeof(lvi)); | |
| lvi.iItem = index++; | |
| lvi.mask = LVIF_TEXT|LVIF_PARAM; | |
| lvi.cchTextMax = strlenW(item->title)+1; | |
| lvi.pszText = item->title; | |
| lvi.lParam = (LPARAM)item; | |
| item->id = (HTREEITEM)SendMessageW(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&lvi); | |
| item = item->next; | |
| } | |
| } | |
| /* Search the CHM storage stream (an HTML file) for the requested text. | |
| * | |
| * Before searching the HTML file all HTML tags are removed so that only | |
| * the content of the document is scanned. If the search string is found | |
| * then the title of the document is returned. | |
| */ | |
| static WCHAR *SearchCHM_File(IStorage *pStorage, const WCHAR *file, const char *needle) | |
| { | |
| char *buffer = heap_alloc(BLOCK_SIZE); | |
| strbuf_t content, node, node_name; | |
| IStream *temp_stream = NULL; | |
| DWORD i, buffer_size = 0; | |
| WCHAR *title = NULL; | |
| BOOL found = FALSE; | |
| stream_t stream; | |
| HRESULT hres; | |
| hres = IStorage_OpenStream(pStorage, file, NULL, STGM_READ, 0, &temp_stream); | |
| if(FAILED(hres)) { | |
| FIXME("Could not open '%s' stream: %08x\n", debugstr_w(file), hres); | |
| goto cleanup; | |
| } | |
| strbuf_init(&node); | |
| strbuf_init(&content); | |
| strbuf_init(&node_name); | |
| stream_init(&stream, temp_stream); | |
| /* Remove all HTML formatting and record the title */ | |
| while(next_node(&stream, &node)) { | |
| get_node_name(&node, &node_name); | |
| if(next_content(&stream, &content) && content.len > 1) | |
| { | |
| char *text = &content.buf[1]; | |
| int textlen = content.len-1; | |
| if(!strcasecmp(node_name.buf, "title")) | |
| { | |
| int wlen = MultiByteToWideChar(CP_ACP, 0, text, textlen, NULL, 0); | |
| title = heap_alloc((wlen+1)*sizeof(WCHAR)); | |
| MultiByteToWideChar(CP_ACP, 0, text, textlen, title, wlen); | |
| title[wlen] = 0; | |
| } | |
| buffer = heap_realloc(buffer, buffer_size + textlen + 1); | |
| memcpy(&buffer[buffer_size], text, textlen); | |
| buffer[buffer_size + textlen] = '\0'; | |
| buffer_size += textlen; | |
| } | |
| strbuf_zero(&node); | |
| strbuf_zero(&content); | |
| } | |
| /* Convert the buffer to lower case for comparison against the | |
| * requested text (already in lower case). | |
| */ | |
| for(i=0;i<buffer_size;i++) | |
| buffer[i] = tolower(buffer[i]); | |
| /* Search the decoded buffer for the requested text */ | |
| if(strstr(buffer, needle)) | |
| found = TRUE; | |
| strbuf_free(&node); | |
| strbuf_free(&content); | |
| strbuf_free(&node_name); | |
| cleanup: | |
| heap_free(buffer); | |
| if(temp_stream) | |
| IStream_Release(temp_stream); | |
| if(!found) | |
| { | |
| heap_free(title); | |
| return NULL; | |
| } | |
| return title; | |
| } | |
| /* Search all children of a CHM storage object for the requested text and | |
| * return the last found search item. | |
| */ | |
| static SearchItem *SearchCHM_Storage(SearchItem *item, IStorage *pStorage, | |
| const char *needle) | |
| { | |
| const WCHAR szHTMext[] = {'.','h','t','m',0}; | |
| IEnumSTATSTG *elem = NULL; | |
| WCHAR *filename = NULL; | |
| STATSTG entries; | |
| HRESULT hres; | |
| ULONG retr; | |
| hres = IStorage_EnumElements(pStorage, 0, NULL, 0, &elem); | |
| if(hres != S_OK) | |
| { | |
| FIXME("Could not enumerate '/' storage elements: %08x\n", hres); | |
| return NULL; | |
| } | |
| while (IEnumSTATSTG_Next(elem, 1, &entries, &retr) == NOERROR) | |
| { | |
| switch(entries.type) { | |
| case STGTY_STORAGE: | |
| item = SearchCHM_Folder(item, pStorage, entries.pwcsName, needle); | |
| break; | |
| case STGTY_STREAM: | |
| filename = entries.pwcsName; | |
| while(strchrW(filename, '/')) | |
| filename = strchrW(filename, '/')+1; | |
| if(strstrW(filename, szHTMext)) | |
| { | |
| WCHAR *title = SearchCHM_File(pStorage, filename, needle); | |
| if(title) | |
| { | |
| item->next = alloc_search_item(title, entries.pwcsName); | |
| item = item->next; | |
| } | |
| } | |
| break; | |
| default: | |
| FIXME("Unhandled IStorage stream element.\n"); | |
| } | |
| } | |
| return item; | |
| } | |
| /* Open a CHM storage object (folder) by name and find all items with | |
| * the requested text. The last found item is returned. | |
| */ | |
| static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage, | |
| const WCHAR *folder, const char *needle) | |
| { | |
| IStorage *temp_storage = NULL; | |
| HRESULT hres; | |
| hres = IStorage_OpenStorage(pStorage, folder, NULL, STGM_READ, NULL, 0, &temp_storage); | |
| if(FAILED(hres)) | |
| { | |
| FIXME("Could not open '%s' storage object: %08x\n", debugstr_w(folder), hres); | |
| return NULL; | |
| } | |
| item = SearchCHM_Storage(item, temp_storage, needle); | |
| IStorage_Release(temp_storage); | |
| return item; | |
| } | |
| /* Search the entire CHM file for the requested text and add all of | |
| * the found items to a ListView for the user to choose the item | |
| * they want. | |
| */ | |
| void InitSearch(HHInfo *info, const char *needle) | |
| { | |
| CHMInfo *chm = info->pCHMInfo; | |
| SearchItem *root_item = alloc_search_item(NULL, NULL); | |
| SearchCHM_Storage(root_item, chm->pStorage, needle); | |
| fill_search_tree(info->search.hwndList, root_item->next); | |
| if(info->search.root) | |
| ReleaseSearch(info); | |
| info->search.root = root_item; | |
| } | |
| /* Free all of the found Search items. */ | |
| void ReleaseSearch(HHInfo *info) | |
| { | |
| SearchItem *item = info->search.root; | |
| info->search.root = NULL; | |
| while(item) { | |
| heap_free(item->filename); | |
| item = item->next; | |
| } | |
| } |