]>
Commit | Line | Data |
---|---|---|
1 | ///////////////////////////////////////////////////////////////////////////// | |
2 | // Name: helpdata.cpp | |
3 | // Purpose: wxHtmlHelpData | |
4 | // Notes: Based on htmlhelp.cpp, implementing a monolithic | |
5 | // HTML Help controller class, by Vaclav Slavik | |
6 | // Author: Harm van der Heijden and Vaclav Slavik | |
7 | // RCS-ID: $Id$ | |
8 | // Copyright: (c) Harm van der Heijden and Vaclav Slavik | |
9 | // Licence: wxWindows licence | |
10 | ///////////////////////////////////////////////////////////////////////////// | |
11 | ||
12 | // For compilers that support precompilation, includes "wx.h". | |
13 | #include "wx/wxprec.h" | |
14 | ||
15 | #ifdef __BORLANDC__ | |
16 | #pragma hdrstop | |
17 | #endif | |
18 | ||
19 | #include "wx/defs.h" | |
20 | ||
21 | #if wxUSE_HTML && wxUSE_STREAMS | |
22 | ||
23 | #ifndef WXPRECOMP | |
24 | #include "wx/intl.h" | |
25 | #include "wx/log.h" | |
26 | #endif | |
27 | ||
28 | #include <ctype.h> | |
29 | ||
30 | #include "wx/html/helpdata.h" | |
31 | #include "wx/tokenzr.h" | |
32 | #include "wx/wfstream.h" | |
33 | #include "wx/busyinfo.h" | |
34 | #include "wx/encconv.h" | |
35 | #include "wx/fontmap.h" | |
36 | #include "wx/log.h" | |
37 | #include "wx/html/htmlpars.h" | |
38 | #include "wx/html/htmldefs.h" | |
39 | #include "wx/html/htmlfilt.h" | |
40 | #include "wx/filename.h" | |
41 | ||
42 | #include "wx/arrimpl.cpp" | |
43 | WX_DEFINE_OBJARRAY(wxHtmlBookRecArray) | |
44 | WX_DEFINE_OBJARRAY(wxHtmlHelpDataItems) | |
45 | ||
46 | //----------------------------------------------------------------------------- | |
47 | // static helper functions | |
48 | //----------------------------------------------------------------------------- | |
49 | ||
50 | // Reads one line, stores it into buf and returns pointer to new line or NULL. | |
51 | static const wxChar* ReadLine(const wxChar *line, wxChar *buf, size_t bufsize) | |
52 | { | |
53 | wxChar *writeptr = buf; | |
54 | wxChar *endptr = buf + bufsize - 1; | |
55 | const wxChar *readptr = line; | |
56 | ||
57 | while (*readptr != 0 && *readptr != _T('\r') && *readptr != _T('\n') && | |
58 | writeptr != endptr) | |
59 | *(writeptr++) = *(readptr++); | |
60 | *writeptr = 0; | |
61 | while (*readptr == _T('\r') || *readptr == _T('\n')) | |
62 | readptr++; | |
63 | if (*readptr == 0) | |
64 | return NULL; | |
65 | else | |
66 | return readptr; | |
67 | } | |
68 | ||
69 | ||
70 | ||
71 | static int | |
72 | wxHtmlHelpIndexCompareFunc(wxHtmlHelpDataItem **a, wxHtmlHelpDataItem **b) | |
73 | { | |
74 | wxHtmlHelpDataItem *ia = *a; | |
75 | wxHtmlHelpDataItem *ib = *b; | |
76 | ||
77 | if (ia == NULL) | |
78 | return -1; | |
79 | if (ib == NULL) | |
80 | return 1; | |
81 | ||
82 | if (ia->parent == ib->parent) | |
83 | { | |
84 | return ia->name.CmpNoCase(ib->name); | |
85 | } | |
86 | else if (ia->level == ib->level) | |
87 | { | |
88 | return wxHtmlHelpIndexCompareFunc(&ia->parent, &ib->parent); | |
89 | } | |
90 | else | |
91 | { | |
92 | wxHtmlHelpDataItem *ia2 = ia; | |
93 | wxHtmlHelpDataItem *ib2 = ib; | |
94 | ||
95 | while (ia2->level > ib2->level) | |
96 | { | |
97 | ia2 = ia2->parent; | |
98 | } | |
99 | while (ib2->level > ia2->level) | |
100 | { | |
101 | ib2 = ib2->parent; | |
102 | } | |
103 | ||
104 | wxASSERT(ia2); | |
105 | wxASSERT(ib2); | |
106 | int res = wxHtmlHelpIndexCompareFunc(&ia2, &ib2); | |
107 | if (res != 0) | |
108 | return res; | |
109 | else if (ia->level > ib->level) | |
110 | return 1; | |
111 | else | |
112 | return -1; | |
113 | } | |
114 | } | |
115 | ||
116 | //----------------------------------------------------------------------------- | |
117 | // HP_Parser | |
118 | //----------------------------------------------------------------------------- | |
119 | ||
120 | class HP_Parser : public wxHtmlParser | |
121 | { | |
122 | public: | |
123 | HP_Parser() | |
124 | { | |
125 | GetEntitiesParser()->SetEncoding(wxFONTENCODING_ISO8859_1); | |
126 | } | |
127 | ||
128 | wxObject* GetProduct() { return NULL; } | |
129 | ||
130 | protected: | |
131 | virtual void AddText(const wxChar* WXUNUSED(txt)) {} | |
132 | ||
133 | DECLARE_NO_COPY_CLASS(HP_Parser) | |
134 | }; | |
135 | ||
136 | ||
137 | //----------------------------------------------------------------------------- | |
138 | // HP_TagHandler | |
139 | //----------------------------------------------------------------------------- | |
140 | ||
141 | class HP_TagHandler : public wxHtmlTagHandler | |
142 | { | |
143 | private: | |
144 | wxString m_name, m_page; | |
145 | int m_level; | |
146 | int m_id; | |
147 | int m_index; | |
148 | int m_count; | |
149 | wxHtmlHelpDataItem *m_parentItem; | |
150 | wxHtmlBookRecord *m_book; | |
151 | ||
152 | wxHtmlHelpDataItems *m_data; | |
153 | ||
154 | public: | |
155 | HP_TagHandler(wxHtmlBookRecord *b) : wxHtmlTagHandler() | |
156 | { | |
157 | m_data = NULL; | |
158 | m_book = b; | |
159 | m_name = m_page = wxEmptyString; | |
160 | m_level = 0; | |
161 | m_id = wxID_ANY; | |
162 | m_count = 0; | |
163 | m_parentItem = NULL; | |
164 | } | |
165 | wxString GetSupportedTags() { return wxT("UL,OBJECT,PARAM"); } | |
166 | bool HandleTag(const wxHtmlTag& tag); | |
167 | ||
168 | void Reset(wxHtmlHelpDataItems& data) | |
169 | { | |
170 | m_data = &data; | |
171 | m_count = 0; | |
172 | m_level = 0; | |
173 | m_parentItem = NULL; | |
174 | } | |
175 | ||
176 | DECLARE_NO_COPY_CLASS(HP_TagHandler) | |
177 | }; | |
178 | ||
179 | ||
180 | bool HP_TagHandler::HandleTag(const wxHtmlTag& tag) | |
181 | { | |
182 | if (tag.GetName() == wxT("UL")) | |
183 | { | |
184 | wxHtmlHelpDataItem *oldparent = m_parentItem; | |
185 | m_level++; | |
186 | m_parentItem = (m_count > 0) ? &(*m_data)[m_data->size()-1] : NULL; | |
187 | ParseInner(tag); | |
188 | m_level--; | |
189 | m_parentItem = oldparent; | |
190 | return true; | |
191 | } | |
192 | else if (tag.GetName() == wxT("OBJECT")) | |
193 | { | |
194 | m_name = m_page = wxEmptyString; | |
195 | ParseInner(tag); | |
196 | ||
197 | #if 0 | |
198 | if (!page.IsEmpty()) | |
199 | /* Valid HHW's file may contain only two object tags: | |
200 | ||
201 | <OBJECT type="text/site properties"> | |
202 | <param name="ImageType" value="Folder"> | |
203 | </OBJECT> | |
204 | ||
205 | or | |
206 | ||
207 | <OBJECT type="text/sitemap"> | |
208 | <param name="Name" value="main page"> | |
209 | <param name="Local" value="another.htm"> | |
210 | </OBJECT> | |
211 | ||
212 | We're interested in the latter. !page.IsEmpty() is valid | |
213 | condition because text/site properties does not contain Local param | |
214 | */ | |
215 | #endif | |
216 | if (tag.GetParam(wxT("TYPE")) == wxT("text/sitemap")) | |
217 | { | |
218 | wxHtmlHelpDataItem *item = new wxHtmlHelpDataItem(); | |
219 | item->parent = m_parentItem; | |
220 | item->level = m_level; | |
221 | item->id = m_id; | |
222 | item->page = m_page; | |
223 | item->name = m_name; | |
224 | ||
225 | item->book = m_book; | |
226 | m_data->Add(item); | |
227 | m_count++; | |
228 | } | |
229 | ||
230 | return true; | |
231 | } | |
232 | else | |
233 | { // "PARAM" | |
234 | if (m_name.empty() && tag.GetParam(wxT("NAME")) == wxT("Name")) | |
235 | m_name = tag.GetParam(wxT("VALUE")); | |
236 | if (tag.GetParam(wxT("NAME")) == wxT("Local")) | |
237 | m_page = tag.GetParam(wxT("VALUE")); | |
238 | if (tag.GetParam(wxT("NAME")) == wxT("ID")) | |
239 | tag.GetParamAsInt(wxT("VALUE"), &m_id); | |
240 | return false; | |
241 | } | |
242 | } | |
243 | ||
244 | ||
245 | //----------------------------------------------------------------------------- | |
246 | // wxHtmlHelpData | |
247 | //----------------------------------------------------------------------------- | |
248 | ||
249 | wxString wxHtmlBookRecord::GetFullPath(const wxString &page) const | |
250 | { | |
251 | if (wxIsAbsolutePath(page)) | |
252 | return page; | |
253 | else | |
254 | return m_BasePath + page; | |
255 | } | |
256 | ||
257 | wxString wxHtmlHelpDataItem::GetIndentedName() const | |
258 | { | |
259 | wxString s; | |
260 | for (int i = 1; i < level; i++) | |
261 | s << _T(" "); | |
262 | s << name; | |
263 | return s; | |
264 | } | |
265 | ||
266 | ||
267 | IMPLEMENT_DYNAMIC_CLASS(wxHtmlHelpData, wxObject) | |
268 | ||
269 | wxHtmlHelpData::wxHtmlHelpData() | |
270 | { | |
271 | #if WXWIN_COMPATIBILITY_2_4 | |
272 | m_cacheContents = NULL; | |
273 | m_cacheIndex = NULL; | |
274 | #endif | |
275 | } | |
276 | ||
277 | wxHtmlHelpData::~wxHtmlHelpData() | |
278 | { | |
279 | #if WXWIN_COMPATIBILITY_2_4 | |
280 | CleanCompatibilityData(); | |
281 | #endif | |
282 | } | |
283 | ||
284 | bool wxHtmlHelpData::LoadMSProject(wxHtmlBookRecord *book, wxFileSystem& fsys, | |
285 | const wxString& indexfile, | |
286 | const wxString& contentsfile) | |
287 | { | |
288 | wxFSFile *f; | |
289 | wxHtmlFilterHTML filter; | |
290 | wxString buf; | |
291 | wxString string; | |
292 | ||
293 | HP_Parser parser; | |
294 | HP_TagHandler *handler = new HP_TagHandler(book); | |
295 | parser.AddTagHandler(handler); | |
296 | ||
297 | f = ( contentsfile.IsEmpty() ? (wxFSFile*) NULL : fsys.OpenFile(contentsfile) ); | |
298 | if (f) | |
299 | { | |
300 | buf.clear(); | |
301 | buf = filter.ReadFile(*f); | |
302 | delete f; | |
303 | handler->Reset(m_contents); | |
304 | parser.Parse(buf); | |
305 | } | |
306 | else | |
307 | { | |
308 | wxLogError(_("Cannot open contents file: %s"), contentsfile.c_str()); | |
309 | } | |
310 | ||
311 | f = ( indexfile.IsEmpty() ? (wxFSFile*) NULL : fsys.OpenFile(indexfile) ); | |
312 | if (f) | |
313 | { | |
314 | buf.clear(); | |
315 | buf = filter.ReadFile(*f); | |
316 | delete f; | |
317 | handler->Reset(m_index); | |
318 | parser.Parse(buf); | |
319 | } | |
320 | else if (!indexfile.IsEmpty()) | |
321 | { | |
322 | wxLogError(_("Cannot open index file: %s"), indexfile.c_str()); | |
323 | } | |
324 | return true; | |
325 | } | |
326 | ||
327 | inline static void CacheWriteInt32(wxOutputStream *f, wxInt32 value) | |
328 | { | |
329 | wxInt32 x = wxINT32_SWAP_ON_BE(value); | |
330 | f->Write(&x, sizeof(x)); | |
331 | } | |
332 | ||
333 | inline static wxInt32 CacheReadInt32(wxInputStream *f) | |
334 | { | |
335 | wxInt32 x; | |
336 | f->Read(&x, sizeof(x)); | |
337 | return wxINT32_SWAP_ON_BE(x); | |
338 | } | |
339 | ||
340 | inline static void CacheWriteString(wxOutputStream *f, const wxString& str) | |
341 | { | |
342 | const wxWX2MBbuf mbstr = str.mb_str(wxConvUTF8); | |
343 | size_t len = strlen((const char*)mbstr)+1; | |
344 | CacheWriteInt32(f, len); | |
345 | f->Write((const char*)mbstr, len); | |
346 | } | |
347 | ||
348 | inline static wxString CacheReadString(wxInputStream *f) | |
349 | { | |
350 | size_t len = (size_t)CacheReadInt32(f); | |
351 | wxCharBuffer str(len-1); | |
352 | f->Read(str.data(), len); | |
353 | return wxString(str, wxConvUTF8); | |
354 | } | |
355 | ||
356 | #define CURRENT_CACHED_BOOK_VERSION 5 | |
357 | ||
358 | // Additional flags to detect incompatibilities of the runtime environment: | |
359 | #define CACHED_BOOK_FORMAT_FLAGS \ | |
360 | (wxUSE_UNICODE << 0) | |
361 | ||
362 | ||
363 | bool wxHtmlHelpData::LoadCachedBook(wxHtmlBookRecord *book, wxInputStream *f) | |
364 | { | |
365 | int i, st, newsize; | |
366 | wxInt32 version; | |
367 | ||
368 | /* load header - version info : */ | |
369 | version = CacheReadInt32(f); | |
370 | ||
371 | if (version != CURRENT_CACHED_BOOK_VERSION) | |
372 | { | |
373 | // NB: We can just silently return false here and don't worry about | |
374 | // it anymore, because AddBookParam will load the MS project in | |
375 | // absence of (properly versioned) .cached file and automatically | |
376 | // create new .cached file immediately afterward. | |
377 | return false; | |
378 | } | |
379 | ||
380 | if (CacheReadInt32(f) != CACHED_BOOK_FORMAT_FLAGS) | |
381 | return false; | |
382 | ||
383 | /* load contents : */ | |
384 | st = m_contents.size(); | |
385 | newsize = st + CacheReadInt32(f); | |
386 | m_contents.Alloc(newsize); | |
387 | for (i = st; i < newsize; i++) | |
388 | { | |
389 | wxHtmlHelpDataItem *item = new wxHtmlHelpDataItem; | |
390 | item->level = CacheReadInt32(f); | |
391 | item->id = CacheReadInt32(f); | |
392 | item->name = CacheReadString(f); | |
393 | item->page = CacheReadString(f); | |
394 | item->book = book; | |
395 | m_contents.Add(item); | |
396 | } | |
397 | ||
398 | /* load index : */ | |
399 | st = m_index.size(); | |
400 | newsize = st + CacheReadInt32(f); | |
401 | m_index.Alloc(newsize); | |
402 | for (i = st; i < newsize; i++) | |
403 | { | |
404 | wxHtmlHelpDataItem *item = new wxHtmlHelpDataItem; | |
405 | item->name = CacheReadString(f); | |
406 | item->page = CacheReadString(f); | |
407 | item->level = CacheReadInt32(f); | |
408 | item->book = book; | |
409 | int parentShift = CacheReadInt32(f); | |
410 | if (parentShift != 0) | |
411 | item->parent = &m_index[m_index.size() - parentShift]; | |
412 | m_index.Add(item); | |
413 | } | |
414 | return true; | |
415 | } | |
416 | ||
417 | ||
418 | bool wxHtmlHelpData::SaveCachedBook(wxHtmlBookRecord *book, wxOutputStream *f) | |
419 | { | |
420 | int i; | |
421 | wxInt32 cnt; | |
422 | ||
423 | /* save header - version info : */ | |
424 | CacheWriteInt32(f, CURRENT_CACHED_BOOK_VERSION); | |
425 | CacheWriteInt32(f, CACHED_BOOK_FORMAT_FLAGS); | |
426 | ||
427 | /* save contents : */ | |
428 | int len = m_contents.size(); | |
429 | for (cnt = 0, i = 0; i < len; i++) | |
430 | if (m_contents[i].book == book && m_contents[i].level > 0) | |
431 | cnt++; | |
432 | CacheWriteInt32(f, cnt); | |
433 | ||
434 | for (i = 0; i < len; i++) | |
435 | { | |
436 | if (m_contents[i].book != book || m_contents[i].level == 0) | |
437 | continue; | |
438 | CacheWriteInt32(f, m_contents[i].level); | |
439 | CacheWriteInt32(f, m_contents[i].id); | |
440 | CacheWriteString(f, m_contents[i].name); | |
441 | CacheWriteString(f, m_contents[i].page); | |
442 | } | |
443 | ||
444 | /* save index : */ | |
445 | len = m_index.size(); | |
446 | for (cnt = 0, i = 0; i < len; i++) | |
447 | if (m_index[i].book == book && m_index[i].level > 0) | |
448 | cnt++; | |
449 | CacheWriteInt32(f, cnt); | |
450 | ||
451 | for (i = 0; i < len; i++) | |
452 | { | |
453 | if (m_index[i].book != book || m_index[i].level == 0) | |
454 | continue; | |
455 | CacheWriteString(f, m_index[i].name); | |
456 | CacheWriteString(f, m_index[i].page); | |
457 | CacheWriteInt32(f, m_index[i].level); | |
458 | // save distance to parent item, if any: | |
459 | if (m_index[i].parent == NULL) | |
460 | { | |
461 | CacheWriteInt32(f, 0); | |
462 | } | |
463 | else | |
464 | { | |
465 | int cnt2 = 0; | |
466 | wxHtmlHelpDataItem *parent = m_index[i].parent; | |
467 | for (int j = i-1; j >= 0; j--) | |
468 | { | |
469 | if (m_index[j].book == book && m_index[j].level > 0) | |
470 | cnt2++; | |
471 | if (&m_index[j] == parent) | |
472 | break; | |
473 | } | |
474 | wxASSERT(cnt2 > 0); | |
475 | CacheWriteInt32(f, cnt2); | |
476 | } | |
477 | } | |
478 | return true; | |
479 | } | |
480 | ||
481 | ||
482 | void wxHtmlHelpData::SetTempDir(const wxString& path) | |
483 | { | |
484 | if (path.empty()) | |
485 | m_tempPath = path; | |
486 | else | |
487 | { | |
488 | if (wxIsAbsolutePath(path)) m_tempPath = path; | |
489 | else m_tempPath = wxGetCwd() + _T("/") + path; | |
490 | ||
491 | if (m_tempPath[m_tempPath.Length() - 1] != _T('/')) | |
492 | m_tempPath << _T('/'); | |
493 | } | |
494 | } | |
495 | ||
496 | ||
497 | ||
498 | static wxString SafeFileName(const wxString& s) | |
499 | { | |
500 | wxString res(s); | |
501 | res.Replace(wxT("#"), wxT("_")); | |
502 | res.Replace(wxT(":"), wxT("_")); | |
503 | res.Replace(wxT("\\"), wxT("_")); | |
504 | res.Replace(wxT("/"), wxT("_")); | |
505 | return res; | |
506 | } | |
507 | ||
508 | bool wxHtmlHelpData::AddBookParam(const wxFSFile& bookfile, | |
509 | wxFontEncoding encoding, | |
510 | const wxString& title, const wxString& contfile, | |
511 | const wxString& indexfile, const wxString& deftopic, | |
512 | const wxString& path) | |
513 | { | |
514 | wxFileSystem fsys; | |
515 | wxFSFile *fi; | |
516 | wxHtmlBookRecord *bookr; | |
517 | ||
518 | int IndexOld = m_index.size(), | |
519 | ContentsOld = m_contents.size(); | |
520 | ||
521 | if (!path.IsEmpty()) | |
522 | fsys.ChangePathTo(path, true); | |
523 | ||
524 | size_t booksCnt = m_bookRecords.GetCount(); | |
525 | for (size_t i = 0; i < booksCnt; i++) | |
526 | { | |
527 | if ( m_bookRecords[i].GetBookFile() == bookfile.GetLocation() ) | |
528 | return true; // book is (was) loaded | |
529 | } | |
530 | ||
531 | bookr = new wxHtmlBookRecord(bookfile.GetLocation(), fsys.GetPath(), title, deftopic); | |
532 | ||
533 | wxHtmlHelpDataItem *bookitem = new wxHtmlHelpDataItem; | |
534 | bookitem->level = 0; | |
535 | bookitem->id = 0; | |
536 | bookitem->page = deftopic; | |
537 | bookitem->name = title; | |
538 | bookitem->book = bookr; | |
539 | ||
540 | // store the contents index for later | |
541 | int cont_start = m_contents.size(); | |
542 | ||
543 | m_contents.Add(bookitem); | |
544 | ||
545 | // Try to find cached binary versions: | |
546 | // 1. save file as book, but with .hhp.cached extension | |
547 | // 2. same as 1. but in temp path | |
548 | // 3. otherwise or if cache load failed, load it from MS. | |
549 | ||
550 | fi = fsys.OpenFile(bookfile.GetLocation() + wxT(".cached")); | |
551 | ||
552 | if (fi == NULL || | |
553 | #if wxUSE_DATETIME | |
554 | fi->GetModificationTime() < bookfile.GetModificationTime() || | |
555 | #endif // wxUSE_DATETIME | |
556 | !LoadCachedBook(bookr, fi->GetStream())) | |
557 | { | |
558 | if (fi != NULL) delete fi; | |
559 | fi = fsys.OpenFile(m_tempPath + wxFileNameFromPath(bookfile.GetLocation()) + wxT(".cached")); | |
560 | if (m_tempPath.empty() || fi == NULL || | |
561 | #if wxUSE_DATETIME | |
562 | fi->GetModificationTime() < bookfile.GetModificationTime() || | |
563 | #endif // wxUSE_DATETIME | |
564 | !LoadCachedBook(bookr, fi->GetStream())) | |
565 | { | |
566 | LoadMSProject(bookr, fsys, indexfile, contfile); | |
567 | if (!m_tempPath.empty()) | |
568 | { | |
569 | wxFileOutputStream *outs = new wxFileOutputStream(m_tempPath + | |
570 | SafeFileName(wxFileNameFromPath(bookfile.GetLocation())) + wxT(".cached")); | |
571 | SaveCachedBook(bookr, outs); | |
572 | delete outs; | |
573 | } | |
574 | } | |
575 | } | |
576 | ||
577 | if (fi != NULL) delete fi; | |
578 | ||
579 | // Now store the contents range | |
580 | bookr->SetContentsRange(cont_start, m_contents.size()); | |
581 | ||
582 | #if wxUSE_WCHAR_T | |
583 | // MS HTML Help files [written by MS HTML Help Workshop] are broken | |
584 | // in that the data are iso-8859-1 (including HTML entities), but must | |
585 | // be interpreted as being in language's windows charset. Correct the | |
586 | // differences here and also convert to wxConvLocal in ANSI build | |
587 | if (encoding != wxFONTENCODING_SYSTEM) | |
588 | { | |
589 | #if wxUSE_UNICODE | |
590 | #define CORRECT_STR(str, conv) \ | |
591 | str = wxString((str).mb_str(wxConvISO8859_1), conv) | |
592 | #else | |
593 | #define CORRECT_STR(str, conv) \ | |
594 | str = wxString((str).wc_str(conv), wxConvLocal) | |
595 | #endif | |
596 | wxCSConv conv(encoding); | |
597 | size_t IndexCnt = m_index.size(); | |
598 | size_t ContentsCnt = m_contents.size(); | |
599 | size_t i; | |
600 | for (i = IndexOld; i < IndexCnt; i++) | |
601 | { | |
602 | CORRECT_STR(m_index[i].name, conv); | |
603 | } | |
604 | for (i = ContentsOld; i < ContentsCnt; i++) | |
605 | { | |
606 | CORRECT_STR(m_contents[i].name, conv); | |
607 | } | |
608 | #undef CORRECT_STR | |
609 | } | |
610 | #else | |
611 | wxUnusedVar(IndexOld); | |
612 | wxUnusedVar(ContentsOld); | |
613 | wxASSERT_MSG(encoding == wxFONTENCODING_SYSTEM, wxT("Help files need charset conversion, but wxUSE_WCHAR_T is 0")); | |
614 | #endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T | |
615 | ||
616 | m_bookRecords.Add(bookr); | |
617 | if (!m_index.empty()) | |
618 | { | |
619 | m_index.Sort(wxHtmlHelpIndexCompareFunc); | |
620 | } | |
621 | ||
622 | return true; | |
623 | } | |
624 | ||
625 | ||
626 | bool wxHtmlHelpData::AddBook(const wxString& book) | |
627 | { | |
628 | wxString extension(book.Right(4).Lower()); | |
629 | if (extension == wxT(".zip") || | |
630 | #if wxUSE_LIBMSPACK | |
631 | extension == wxT(".chm") /*compressed html help book*/ || | |
632 | #endif | |
633 | extension == wxT(".htb") /*html book*/) | |
634 | { | |
635 | wxFileSystem fsys; | |
636 | wxString s; | |
637 | bool rt = false; | |
638 | ||
639 | #if wxUSE_LIBMSPACK | |
640 | if (extension == wxT(".chm")) | |
641 | s = fsys.FindFirst(book + wxT("#chm:*.hhp"), wxFILE); | |
642 | else | |
643 | #endif | |
644 | s = fsys.FindFirst(book + wxT("#zip:*.hhp"), wxFILE); | |
645 | ||
646 | while (!s.IsEmpty()) | |
647 | { | |
648 | if (AddBook(s)) rt = true; | |
649 | s = fsys.FindNext(); | |
650 | } | |
651 | ||
652 | return rt; | |
653 | } | |
654 | ||
655 | wxFSFile *fi; | |
656 | wxFileSystem fsys; | |
657 | ||
658 | wxString title = _("noname"), | |
659 | safetitle, | |
660 | start = wxEmptyString, | |
661 | contents = wxEmptyString, | |
662 | index = wxEmptyString, | |
663 | charset = wxEmptyString; | |
664 | ||
665 | fi = fsys.OpenFile(book); | |
666 | if (fi == NULL) | |
667 | { | |
668 | wxLogError(_("Cannot open HTML help book: %s"), book.c_str()); | |
669 | return false; | |
670 | } | |
671 | fsys.ChangePathTo(book); | |
672 | ||
673 | const wxChar *lineptr; | |
674 | wxChar linebuf[300]; | |
675 | wxString tmp; | |
676 | wxHtmlFilterPlainText filter; | |
677 | tmp = filter.ReadFile(*fi); | |
678 | lineptr = tmp.c_str(); | |
679 | ||
680 | do | |
681 | { | |
682 | lineptr = ReadLine(lineptr, linebuf, 300); | |
683 | ||
684 | for (wxChar *ch = linebuf; *ch != wxT('\0') && *ch != wxT('='); ch++) | |
685 | *ch = (wxChar)wxTolower(*ch); | |
686 | ||
687 | if (wxStrstr(linebuf, _T("title=")) == linebuf) | |
688 | title = linebuf + wxStrlen(_T("title=")); | |
689 | if (wxStrstr(linebuf, _T("default topic=")) == linebuf) | |
690 | start = linebuf + wxStrlen(_T("default topic=")); | |
691 | if (wxStrstr(linebuf, _T("index file=")) == linebuf) | |
692 | index = linebuf + wxStrlen(_T("index file=")); | |
693 | if (wxStrstr(linebuf, _T("contents file=")) == linebuf) | |
694 | contents = linebuf + wxStrlen(_T("contents file=")); | |
695 | if (wxStrstr(linebuf, _T("charset=")) == linebuf) | |
696 | charset = linebuf + wxStrlen(_T("charset=")); | |
697 | } while (lineptr != NULL); | |
698 | ||
699 | wxFontEncoding enc = wxFONTENCODING_SYSTEM; | |
700 | #if wxUSE_FONTMAP | |
701 | if (charset != wxEmptyString) | |
702 | enc = wxFontMapper::Get()->CharsetToEncoding(charset); | |
703 | #endif | |
704 | ||
705 | bool rtval = AddBookParam(*fi, enc, | |
706 | title, contents, index, start, fsys.GetPath()); | |
707 | delete fi; | |
708 | ||
709 | #if WXWIN_COMPATIBILITY_2_4 | |
710 | CleanCompatibilityData(); | |
711 | #endif | |
712 | ||
713 | return rtval; | |
714 | } | |
715 | ||
716 | wxString wxHtmlHelpData::FindPageByName(const wxString& x) | |
717 | { | |
718 | int cnt; | |
719 | int i; | |
720 | wxFileSystem fsys; | |
721 | wxFSFile *f; | |
722 | ||
723 | /* 1. try to open given file: */ | |
724 | ||
725 | cnt = m_bookRecords.GetCount(); | |
726 | for (i = 0; i < cnt; i++) | |
727 | { | |
728 | f = fsys.OpenFile(m_bookRecords[i].GetFullPath(x)); | |
729 | if (f) | |
730 | { | |
731 | wxString url = m_bookRecords[i].GetFullPath(x); | |
732 | delete f; | |
733 | return url; | |
734 | } | |
735 | } | |
736 | ||
737 | ||
738 | /* 2. try to find a book: */ | |
739 | ||
740 | for (i = 0; i < cnt; i++) | |
741 | { | |
742 | if (m_bookRecords[i].GetTitle() == x) | |
743 | return m_bookRecords[i].GetFullPath(m_bookRecords[i].GetStart()); | |
744 | } | |
745 | ||
746 | /* 3. try to find in contents: */ | |
747 | ||
748 | cnt = m_contents.size(); | |
749 | for (i = 0; i < cnt; i++) | |
750 | { | |
751 | if (m_contents[i].name == x) | |
752 | return m_contents[i].GetFullPath(); | |
753 | } | |
754 | ||
755 | ||
756 | /* 4. try to find in index: */ | |
757 | ||
758 | cnt = m_index.size(); | |
759 | for (i = 0; i < cnt; i++) | |
760 | { | |
761 | if (m_index[i].name == x) | |
762 | return m_index[i].GetFullPath(); | |
763 | } | |
764 | ||
765 | return wxEmptyString; | |
766 | } | |
767 | ||
768 | wxString wxHtmlHelpData::FindPageById(int id) | |
769 | { | |
770 | size_t cnt = m_contents.size(); | |
771 | for (size_t i = 0; i < cnt; i++) | |
772 | { | |
773 | if (m_contents[i].id == id) | |
774 | { | |
775 | return m_contents[i].GetFullPath(); | |
776 | } | |
777 | } | |
778 | ||
779 | return wxEmptyString; | |
780 | } | |
781 | ||
782 | #if WXWIN_COMPATIBILITY_2_4 | |
783 | wxHtmlContentsItem::wxHtmlContentsItem() | |
784 | : m_Level(0), m_ID(wxID_ANY), m_Name(NULL), m_Page(NULL), m_Book(NULL), | |
785 | m_autofree(false) | |
786 | { | |
787 | } | |
788 | ||
789 | wxHtmlContentsItem::wxHtmlContentsItem(const wxHtmlHelpDataItem& d) | |
790 | { | |
791 | m_autofree = true; | |
792 | m_Level = d.level; | |
793 | m_ID = d.id; | |
794 | m_Name = wxStrdup(d.name.c_str()); | |
795 | m_Page = wxStrdup(d.page.c_str()); | |
796 | m_Book = d.book; | |
797 | } | |
798 | ||
799 | wxHtmlContentsItem& wxHtmlContentsItem::operator=(const wxHtmlContentsItem& d) | |
800 | { | |
801 | if (m_autofree) | |
802 | { | |
803 | free(m_Name); | |
804 | free(m_Page); | |
805 | } | |
806 | m_autofree = true; | |
807 | m_Level = d.m_Level; | |
808 | m_ID = d.m_ID; | |
809 | m_Name = d.m_Name ? wxStrdup(d.m_Name) : NULL; | |
810 | m_Page = d.m_Page ? wxStrdup(d.m_Page) : NULL; | |
811 | m_Book = d.m_Book; | |
812 | return *this; | |
813 | } | |
814 | ||
815 | wxHtmlContentsItem::~wxHtmlContentsItem() | |
816 | { | |
817 | if (m_autofree) | |
818 | { | |
819 | free(m_Name); | |
820 | free(m_Page); | |
821 | } | |
822 | } | |
823 | ||
824 | wxHtmlContentsItem* wxHtmlHelpData::GetContents() | |
825 | { | |
826 | if (!m_cacheContents && !m_contents.empty()) | |
827 | { | |
828 | size_t len = m_contents.size(); | |
829 | m_cacheContents = new wxHtmlContentsItem[len]; | |
830 | for (size_t i = 0; i < len; i++) | |
831 | m_cacheContents[i] = m_contents[i]; | |
832 | } | |
833 | return m_cacheContents; | |
834 | } | |
835 | ||
836 | int wxHtmlHelpData::GetContentsCnt() | |
837 | { | |
838 | return m_contents.size(); | |
839 | } | |
840 | ||
841 | wxHtmlContentsItem* wxHtmlHelpData::GetIndex() | |
842 | { | |
843 | if (!m_cacheContents && !m_index.empty()) | |
844 | { | |
845 | size_t len = m_index.size(); | |
846 | m_cacheContents = new wxHtmlContentsItem[len]; | |
847 | for (size_t i = 0; i < len; i++) | |
848 | m_cacheContents[i] = m_index[i]; | |
849 | } | |
850 | return m_cacheContents; | |
851 | } | |
852 | ||
853 | int wxHtmlHelpData::GetIndexCnt() | |
854 | { | |
855 | return m_index.size(); | |
856 | } | |
857 | ||
858 | void wxHtmlHelpData::CleanCompatibilityData() | |
859 | { | |
860 | delete[] m_cacheContents; | |
861 | m_cacheContents = NULL; | |
862 | delete[] m_cacheIndex; | |
863 | m_cacheIndex = NULL; | |
864 | } | |
865 | #endif // WXWIN_COMPATIBILITY_2_4 | |
866 | ||
867 | //---------------------------------------------------------------------------------- | |
868 | // wxHtmlSearchStatus functions | |
869 | //---------------------------------------------------------------------------------- | |
870 | ||
871 | wxHtmlSearchStatus::wxHtmlSearchStatus(wxHtmlHelpData* data, const wxString& keyword, | |
872 | bool case_sensitive, bool whole_words_only, | |
873 | const wxString& book) | |
874 | { | |
875 | m_Data = data; | |
876 | m_Keyword = keyword; | |
877 | wxHtmlBookRecord* bookr = NULL; | |
878 | if (book != wxEmptyString) | |
879 | { | |
880 | // we have to search in a specific book. Find it first | |
881 | int i, cnt = data->m_bookRecords.GetCount(); | |
882 | for (i = 0; i < cnt; i++) | |
883 | if (data->m_bookRecords[i].GetTitle() == book) | |
884 | { | |
885 | bookr = &(data->m_bookRecords[i]); | |
886 | m_CurIndex = bookr->GetContentsStart(); | |
887 | m_MaxIndex = bookr->GetContentsEnd(); | |
888 | break; | |
889 | } | |
890 | // check; we won't crash if the book doesn't exist, but it's Bad Anyway. | |
891 | wxASSERT(bookr); | |
892 | } | |
893 | if (! bookr) | |
894 | { | |
895 | // no book specified; search all books | |
896 | m_CurIndex = 0; | |
897 | m_MaxIndex = m_Data->m_contents.size(); | |
898 | } | |
899 | m_Engine.LookFor(keyword, case_sensitive, whole_words_only); | |
900 | m_Active = (m_CurIndex < m_MaxIndex); | |
901 | } | |
902 | ||
903 | #if WXWIN_COMPATIBILITY_2_4 | |
904 | wxHtmlContentsItem* wxHtmlSearchStatus::GetContentsItem() | |
905 | { | |
906 | static wxHtmlContentsItem it; | |
907 | it = wxHtmlContentsItem(*m_CurItem); | |
908 | return ⁢ | |
909 | } | |
910 | #endif | |
911 | ||
912 | bool wxHtmlSearchStatus::Search() | |
913 | { | |
914 | wxFSFile *file; | |
915 | int i = m_CurIndex; // shortcut | |
916 | bool found = false; | |
917 | wxString thepage; | |
918 | ||
919 | if (!m_Active) | |
920 | { | |
921 | // sanity check. Illegal use, but we'll try to prevent a crash anyway | |
922 | wxASSERT(m_Active); | |
923 | return false; | |
924 | } | |
925 | ||
926 | m_Name = wxEmptyString; | |
927 | m_CurItem = NULL; | |
928 | thepage = m_Data->m_contents[i].page; | |
929 | ||
930 | m_Active = (++m_CurIndex < m_MaxIndex); | |
931 | // check if it is same page with different anchor: | |
932 | if (!m_LastPage.empty()) | |
933 | { | |
934 | const wxChar *p1, *p2; | |
935 | for (p1 = thepage.c_str(), p2 = m_LastPage.c_str(); | |
936 | *p1 != 0 && *p1 != _T('#') && *p1 == *p2; p1++, p2++) {} | |
937 | ||
938 | m_LastPage = thepage; | |
939 | ||
940 | if (*p1 == 0 || *p1 == _T('#')) | |
941 | return false; | |
942 | } | |
943 | else m_LastPage = thepage; | |
944 | ||
945 | wxFileSystem fsys; | |
946 | file = fsys.OpenFile(m_Data->m_contents[i].book->GetFullPath(thepage)); | |
947 | if (file) | |
948 | { | |
949 | if (m_Engine.Scan(*file)) | |
950 | { | |
951 | m_Name = m_Data->m_contents[i].name; | |
952 | m_CurItem = &m_Data->m_contents[i]; | |
953 | found = true; | |
954 | } | |
955 | delete file; | |
956 | } | |
957 | return found; | |
958 | } | |
959 | ||
960 | ||
961 | ||
962 | ||
963 | ||
964 | ||
965 | ||
966 | ||
967 | //-------------------------------------------------------------------------------- | |
968 | // wxHtmlSearchEngine | |
969 | //-------------------------------------------------------------------------------- | |
970 | ||
971 | void wxHtmlSearchEngine::LookFor(const wxString& keyword, bool case_sensitive, bool whole_words_only) | |
972 | { | |
973 | m_CaseSensitive = case_sensitive; | |
974 | m_WholeWords = whole_words_only; | |
975 | m_Keyword = keyword; | |
976 | ||
977 | if (!m_CaseSensitive) | |
978 | m_Keyword.LowerCase(); | |
979 | } | |
980 | ||
981 | ||
982 | static inline bool WHITESPACE(wxChar c) | |
983 | { | |
984 | return c == _T(' ') || c == _T('\n') || c == _T('\r') || c == _T('\t'); | |
985 | } | |
986 | ||
987 | bool wxHtmlSearchEngine::Scan(const wxFSFile& file) | |
988 | { | |
989 | wxASSERT_MSG(!m_Keyword.empty(), wxT("wxHtmlSearchEngine::LookFor must be called before scanning!")); | |
990 | ||
991 | int i, j; | |
992 | int wrd = m_Keyword.Length(); | |
993 | bool found = false; | |
994 | wxHtmlFilterHTML filter; | |
995 | wxString tmp = filter.ReadFile(file); | |
996 | int lng = tmp.length(); | |
997 | const wxChar *buf = tmp.c_str(); | |
998 | ||
999 | if (!m_CaseSensitive) | |
1000 | tmp.LowerCase(); | |
1001 | ||
1002 | const wxChar *kwd = m_Keyword.c_str(); | |
1003 | ||
1004 | if (m_WholeWords) | |
1005 | { | |
1006 | for (i = 0; i < lng - wrd; i++) | |
1007 | { | |
1008 | if (WHITESPACE(buf[i])) continue; | |
1009 | j = 0; | |
1010 | while ((j < wrd) && (buf[i + j] == kwd[j])) j++; | |
1011 | if (j == wrd && WHITESPACE(buf[i + j])) { found = true; break; } | |
1012 | } | |
1013 | } | |
1014 | ||
1015 | else | |
1016 | { | |
1017 | for (i = 0; i < lng - wrd; i++) | |
1018 | { | |
1019 | j = 0; | |
1020 | while ((j < wrd) && (buf[i + j] == kwd[j])) j++; | |
1021 | if (j == wrd) { found = true; break; } | |
1022 | } | |
1023 | } | |
1024 | ||
1025 | return found; | |
1026 | } | |
1027 | ||
1028 | ||
1029 | ||
1030 | #endif |