1 /* 2 * Copyright 2010 Erich Hoover 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 17 */ 18 19 #define NONAMELESSUNION 20 #define NONAMELESSSTRUCT 21 22 #include "hhctrl.h" 23 #include "stream.h" 24 25 #include "wine/debug.h" 26 27 WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); 28 29 static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage, 30 const WCHAR *folder, const char *needle); 31 32 /* Allocate a ListView entry for a search result. */ 33 static SearchItem *alloc_search_item(WCHAR *title, const WCHAR *filename) 34 { 35 int filename_len = filename ? (strlenW(filename)+1)*sizeof(WCHAR) : 0; 36 SearchItem *item; 37 38 item = heap_alloc_zero(sizeof(SearchItem)); 39 if(filename) 40 { 41 item->filename = heap_alloc(filename_len); 42 memcpy(item->filename, filename, filename_len); 43 } 44 item->title = title; /* Already allocated */ 45 46 return item; 47 } 48 49 /* Fill the ListView object corresponding to the found Search tab items */ 50 static void fill_search_tree(HWND hwndList, SearchItem *item) 51 { 52 int index = 0; 53 LVITEMW lvi; 54 55 SendMessageW(hwndList, LVM_DELETEALLITEMS, 0, 0); 56 while(item) { 57 TRACE("list debug: %s\n", debugstr_w(item->filename)); 58 59 memset(&lvi, 0, sizeof(lvi)); 60 lvi.iItem = index++; 61 lvi.mask = LVIF_TEXT|LVIF_PARAM; 62 lvi.cchTextMax = strlenW(item->title)+1; 63 lvi.pszText = item->title; 64 lvi.lParam = (LPARAM)item; 65 item->id = (HTREEITEM)SendMessageW(hwndList, LVM_INSERTITEMW, 0, (LPARAM)&lvi); 66 item = item->next; 67 } 68 } 69 70 /* Search the CHM storage stream (an HTML file) for the requested text. 71 * 72 * Before searching the HTML file all HTML tags are removed so that only 73 * the content of the document is scanned. If the search string is found 74 * then the title of the document is returned. 75 */ 76 static WCHAR *SearchCHM_File(IStorage *pStorage, const WCHAR *file, const char *needle) 77 { 78 char *buffer = heap_alloc(BLOCK_SIZE); 79 strbuf_t content, node, node_name; 80 IStream *temp_stream = NULL; 81 DWORD i, buffer_size = 0; 82 WCHAR *title = NULL; 83 BOOL found = FALSE; 84 stream_t stream; 85 HRESULT hres; 86 87 hres = IStorage_OpenStream(pStorage, file, NULL, STGM_READ, 0, &temp_stream); 88 if(FAILED(hres)) { 89 FIXME("Could not open '%s' stream: %08x\n", debugstr_w(file), hres); 90 goto cleanup; 91 } 92 93 strbuf_init(&node); 94 strbuf_init(&content); 95 strbuf_init(&node_name); 96 97 stream_init(&stream, temp_stream); 98 99 /* Remove all HTML formatting and record the title */ 100 while(next_node(&stream, &node)) { 101 get_node_name(&node, &node_name); 102 103 if(next_content(&stream, &content) && content.len > 1) 104 { 105 char *text = &content.buf[1]; 106 int textlen = content.len-1; 107 108 if(!strcasecmp(node_name.buf, "title")) 109 { 110 int wlen = MultiByteToWideChar(CP_ACP, 0, text, textlen, NULL, 0); 111 title = heap_alloc((wlen+1)*sizeof(WCHAR)); 112 MultiByteToWideChar(CP_ACP, 0, text, textlen, title, wlen); 113 title[wlen] = 0; 114 } 115 116 buffer = heap_realloc(buffer, buffer_size + textlen + 1); 117 memcpy(&buffer[buffer_size], text, textlen); 118 buffer[buffer_size + textlen] = '\0'; 119 buffer_size += textlen; 120 } 121 122 strbuf_zero(&node); 123 strbuf_zero(&content); 124 } 125 126 /* Convert the buffer to lower case for comparison against the 127 * requested text (already in lower case). 128 */ 129 for(i=0;i<buffer_size;i++) 130 buffer[i] = tolower(buffer[i]); 131 132 /* Search the decoded buffer for the requested text */ 133 if(strstr(buffer, needle)) 134 found = TRUE; 135 136 strbuf_free(&node); 137 strbuf_free(&content); 138 strbuf_free(&node_name); 139 140 cleanup: 141 heap_free(buffer); 142 if(temp_stream) 143 IStream_Release(temp_stream); 144 if(!found) 145 { 146 heap_free(title); 147 return NULL; 148 } 149 return title; 150 } 151 152 /* Search all children of a CHM storage object for the requested text and 153 * return the last found search item. 154 */ 155 static SearchItem *SearchCHM_Storage(SearchItem *item, IStorage *pStorage, 156 const char *needle) 157 { 158 const WCHAR szHTMext[] = {'.','h','t','m',0}; 159 IEnumSTATSTG *elem = NULL; 160 WCHAR *filename = NULL; 161 STATSTG entries; 162 HRESULT hres; 163 ULONG retr; 164 165 hres = IStorage_EnumElements(pStorage, 0, NULL, 0, &elem); 166 if(hres != S_OK) 167 { 168 FIXME("Could not enumerate '/' storage elements: %08x\n", hres); 169 return NULL; 170 } 171 while (IEnumSTATSTG_Next(elem, 1, &entries, &retr) == NOERROR) 172 { 173 switch(entries.type) { 174 case STGTY_STORAGE: 175 item = SearchCHM_Folder(item, pStorage, entries.pwcsName, needle); 176 break; 177 case STGTY_STREAM: 178 filename = entries.pwcsName; 179 while(strchrW(filename, '/')) 180 filename = strchrW(filename, '/')+1; 181 if(strstrW(filename, szHTMext)) 182 { 183 WCHAR *title = SearchCHM_File(pStorage, filename, needle); 184 185 if(title) 186 { 187 item->next = alloc_search_item(title, entries.pwcsName); 188 item = item->next; 189 } 190 } 191 break; 192 default: 193 FIXME("Unhandled IStorage stream element.\n"); 194 } 195 } 196 return item; 197 } 198 199 /* Open a CHM storage object (folder) by name and find all items with 200 * the requested text. The last found item is returned. 201 */ 202 static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage, 203 const WCHAR *folder, const char *needle) 204 { 205 IStorage *temp_storage = NULL; 206 HRESULT hres; 207 208 hres = IStorage_OpenStorage(pStorage, folder, NULL, STGM_READ, NULL, 0, &temp_storage); 209 if(FAILED(hres)) 210 { 211 FIXME("Could not open '%s' storage object: %08x\n", debugstr_w(folder), hres); 212 return NULL; 213 } 214 item = SearchCHM_Storage(item, temp_storage, needle); 215 216 IStorage_Release(temp_storage); 217 return item; 218 } 219 220 /* Search the entire CHM file for the requested text and add all of 221 * the found items to a ListView for the user to choose the item 222 * they want. 223 */ 224 void InitSearch(HHInfo *info, const char *needle) 225 { 226 CHMInfo *chm = info->pCHMInfo; 227 SearchItem *root_item = alloc_search_item(NULL, NULL); 228 229 SearchCHM_Storage(root_item, chm->pStorage, needle); 230 fill_search_tree(info->search.hwndList, root_item->next); 231 if(info->search.root) 232 ReleaseSearch(info); 233 info->search.root = root_item; 234 } 235 236 /* Free all of the found Search items. */ 237 void ReleaseSearch(HHInfo *info) 238 { 239 SearchItem *item = info->search.root; 240 241 info->search.root = NULL; 242 while(item) { 243 heap_free(item->filename); 244 item = item->next; 245 } 246 } 247