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