hhctrl.ocx: Add Search capability.
[wine] / dlls / hhctrl.ocx / search.c
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     buffer = heap_alloc(0);
101     while(next_node(&stream, &node)) {
102         get_node_name(&node, &node_name);
103
104         if(next_content(&stream, &content) && content.len > 1)
105         {
106             char *text = &content.buf[1];
107             int textlen = content.len-1;
108
109             if(!strcasecmp(node_name.buf, "title"))
110             {
111                 int wlen = MultiByteToWideChar(CP_ACP, 0, text, textlen, NULL, 0);
112                 title = heap_alloc((wlen+1)*sizeof(WCHAR));
113                 MultiByteToWideChar(CP_ACP, 0, text, textlen, title, wlen);
114                 title[wlen] = 0;
115             }
116
117             buffer = heap_realloc(buffer, buffer_size + textlen + 1);
118             memcpy(&buffer[buffer_size], text, textlen);
119             buffer[buffer_size + textlen] = '\0';
120             buffer_size += textlen;
121         }
122
123         strbuf_zero(&node);
124         strbuf_zero(&content);
125     }
126
127     /* Convert the buffer to lower case for comparison against the
128      * requested text (already in lower case).
129      */
130     for(i=0;i<buffer_size;i++)
131         buffer[i] = tolower(buffer[i]);
132
133     /* Search the decoded buffer for the requested text */
134     if(strstr(buffer, needle))
135         found = TRUE;
136
137     strbuf_free(&node);
138     strbuf_free(&content);
139     strbuf_free(&node_name);
140
141 cleanup:
142     heap_free(buffer);
143     if(temp_stream)
144         IStream_Release(temp_stream);
145     if(!found)
146     {
147         heap_free(title);
148         return NULL;
149     }
150     return title;
151 }
152
153 /* Search all children of a CHM storage object for the requested text and
154  * return the last found search item.
155  */
156 static SearchItem *SearchCHM_Storage(SearchItem *item, IStorage *pStorage,
157                                      const char *needle)
158 {
159     const WCHAR szHTMext[] = {'.','h','t','m',0};
160     IEnumSTATSTG *elem = NULL;
161     WCHAR *filename = NULL;
162     STATSTG entries;
163     HRESULT hres;
164     ULONG retr;
165
166     hres = IStorage_EnumElements(pStorage, 0, NULL, 0, &elem);
167     if(hres != S_OK)
168     {
169         FIXME("Could not enumerate '/' storage elements: %08x\n", hres);
170         return NULL;
171     }
172     while (IEnumSTATSTG_Next(elem, 1, &entries, &retr) == NOERROR)
173     {
174         switch(entries.type) {
175         case STGTY_STORAGE:
176             item = SearchCHM_Folder(item, pStorage, entries.pwcsName, needle);
177             break;
178         case STGTY_STREAM:
179             filename = entries.pwcsName;
180             while(strchrW(filename, '/'))
181                 filename = strchrW(filename, '/')+1;
182             if(strstrW(filename, szHTMext))
183             {
184                 WCHAR *title = SearchCHM_File(pStorage, filename, needle);
185
186                 if(title)
187                 {
188                     item->next = alloc_search_item(title, entries.pwcsName);
189                     item = item->next;
190                 }
191             }
192             break;
193         default:
194             FIXME("Unhandled IStorage stream element.\n");
195         }
196     }
197     return item;
198 }
199
200 /* Open a CHM storage object (folder) by name and find all items with
201  * the requested text.  The last found item is returned.
202  */
203 static SearchItem *SearchCHM_Folder(SearchItem *item, IStorage *pStorage,
204                                     const WCHAR *folder, const char *needle)
205 {
206     IStorage *temp_storage = NULL;
207     HRESULT hres;
208
209     hres = IStorage_OpenStorage(pStorage, folder, NULL, STGM_READ, NULL, 0, &temp_storage);
210     if(FAILED(hres))
211     {
212         FIXME("Could not open '%s' storage object: %08x\n", debugstr_w(folder), hres);
213         return NULL;
214     }
215     item = SearchCHM_Storage(item, temp_storage, needle);
216
217     IStorage_Release(temp_storage);
218     return item;
219 }
220
221 /* Search the entire CHM file for the requested text and add all of
222  * the found items to a ListView for the user to choose the item
223  * they want.
224  */
225 void InitSearch(HHInfo *info, const char *needle)
226 {
227     CHMInfo *chm = info->pCHMInfo;
228     SearchItem *root_item = alloc_search_item(NULL, NULL);
229
230     SearchCHM_Storage(root_item, chm->pStorage, needle);
231     fill_search_tree(info->search.hwndList, root_item->next);
232     if(info->search.root)
233         ReleaseSearch(info);
234     info->search.root = root_item;
235 }
236
237 /* Free all of the found Search items. */
238 void ReleaseSearch(HHInfo *info)
239 {
240     SearchItem *item = info->search.root;
241
242     info->search.root = NULL;
243     while(item) {
244         heap_free(item->filename);
245         item = item->next;
246     }
247 }