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