applied pseudo-patch #103306
[wxWidgets.git] / src / html / helpdata.cpp
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 #ifdef __GNUG__
13 #pragma implementation
14 #endif
15
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 #include "wx/defs.h"
24
25 #if wxUSE_HTML && wxUSE_STREAMS
26
27 #ifndef WXPRECOMP
28 #include "wx/wx.h"
29 #endif
30
31 #include "wx/html/helpdata.h"
32 #include "wx/tokenzr.h"
33 #include "wx/wfstream.h"
34 #include "wx/busyinfo.h"
35 #include "wx/encconv.h"
36 #include "wx/fontmap.h"
37 #include "wx/log.h"
38 #include "wx/html/htmlpars.h"
39 #include "wx/html/htmldefs.h"
40
41 #include "wx/arrimpl.cpp"
42 WX_DEFINE_OBJARRAY(wxHtmlBookRecArray)
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 char* ReadLine(char *line, char *buf)
50 {
51 char *writeptr = buf, *readptr = line;
52
53 while (*readptr != 0 && *readptr != '\r' && *readptr != '\n') *(writeptr++) = *(readptr++);
54 *writeptr = 0;
55 while (*readptr == '\r' || *readptr == '\n') readptr++;
56 if (*readptr == 0) return NULL;
57 else return readptr;
58 }
59
60
61
62 static int LINKAGEMODE IndexCompareFunc(const void *a, const void *b)
63 {
64 return wxStrcmp(((wxHtmlContentsItem*)a)->m_Name, ((wxHtmlContentsItem*)b)->m_Name);
65 }
66
67
68 //-----------------------------------------------------------------------------
69 // HP_Parser
70 //-----------------------------------------------------------------------------
71
72 class HP_Parser : public wxHtmlParser
73 {
74 public:
75 void AddText(const char* WXUNUSED(text)) { }
76 wxObject* GetProduct() { return NULL; }
77 };
78
79
80 //-----------------------------------------------------------------------------
81 // HP_TagHandler
82 //-----------------------------------------------------------------------------
83
84 class HP_TagHandler : public wxHtmlTagHandler
85 {
86 private:
87 wxString m_Name, m_Page;
88 int m_Level;
89 int m_ID;
90 int m_Index;
91 wxHtmlContentsItem *m_Items;
92 int m_ItemsCnt;
93 wxHtmlBookRecord *m_Book;
94
95 public:
96 HP_TagHandler(wxHtmlBookRecord *b) : wxHtmlTagHandler()
97 { m_Book = b; m_Items = NULL; m_ItemsCnt = 0; m_Name = m_Page = wxEmptyString;
98 m_Level = 0; m_ID = -1; }
99 wxString GetSupportedTags() { return wxT("UL,OBJECT,PARAM"); }
100 bool HandleTag(const wxHtmlTag& tag);
101 void WriteOut(wxHtmlContentsItem*& array, int& size);
102 void ReadIn(wxHtmlContentsItem* array, int size);
103 };
104
105
106 bool HP_TagHandler::HandleTag(const wxHtmlTag& tag)
107 {
108 if (tag.GetName() == wxT("UL"))
109 {
110 m_Level++;
111 ParseInner(tag);
112 m_Level--;
113 return TRUE;
114 }
115 else if (tag.GetName() == wxT("OBJECT"))
116 {
117 m_Name = m_Page = wxEmptyString;
118 ParseInner(tag);
119
120 if (!m_Page.IsEmpty())
121 /* should be 'if (tag.GetParam("TYPE") == "text/sitemap")'
122 but this works fine. Valid HHW's file may contain only two
123 object tags:
124
125 <OBJECT type="text/site properties">
126 <param name="ImageType" value="Folder">
127 </OBJECT>
128
129 or
130
131 <OBJECT type="text/sitemap">
132 <param name="Name" value="main page">
133 <param name="Local" value="another.htm">
134 </OBJECT>
135
136 We're interested in the latter. !m_Page.IsEmpty() is valid
137 condition because text/site properties does not contain Local param
138 */
139 {
140 if (m_ItemsCnt % wxHTML_REALLOC_STEP == 0)
141 m_Items = (wxHtmlContentsItem*) realloc(m_Items, (m_ItemsCnt + wxHTML_REALLOC_STEP) * sizeof(wxHtmlContentsItem));
142 m_Items[m_ItemsCnt].m_Level = m_Level;
143 m_Items[m_ItemsCnt].m_ID = m_ID;
144 m_Items[m_ItemsCnt].m_Page = new wxChar[m_Page.Length() + 1];
145 wxStrcpy(m_Items[m_ItemsCnt].m_Page, m_Page.c_str());
146 m_Items[m_ItemsCnt].m_Name = new wxChar [m_Name.Length() + 1];
147 wxStrcpy(m_Items[m_ItemsCnt].m_Name, m_Name.c_str());
148 m_Items[m_ItemsCnt].m_Book = m_Book;
149 m_ItemsCnt++;
150 }
151
152 return TRUE;
153 }
154 else
155 { // "PARAM"
156 if (m_Name == wxEmptyString && tag.GetParam(wxT("NAME")) == wxT("Name"))
157 {
158 m_Name = tag.GetParam(wxT("VALUE"));
159 if (m_Name.Find(wxT('&')) != -1)
160 {
161 #define ESCSEQ(escape, subst) \
162 { _T("&") _T(escape) _T(";"), _T("&") _T(escape) _T(" "), _T(subst) }
163 static wxChar* substitutions[][3] =
164 {
165 ESCSEQ("quot", "\""),
166 ESCSEQ("lt", "<"),
167 ESCSEQ("gt", ">"),
168
169 ESCSEQ("nbsp", " "),
170 ESCSEQ("iexcl", "!"),
171 ESCSEQ("cent", "\242"/*¢*/),
172
173 ESCSEQ("yen", " "),
174 ESCSEQ("brkbar", " "),
175 ESCSEQ("sect", " "),
176 ESCSEQ("uml", " "),
177
178 ESCSEQ("copy", "(c)"),
179 ESCSEQ("ordf", " "),
180 ESCSEQ("laquo", " "),
181 ESCSEQ("not", " "),
182
183 ESCSEQ("reg", "(r)"),
184
185 ESCSEQ("deg", " "),
186 ESCSEQ("plusm", " "),
187
188 ESCSEQ("acute", " "),
189 ESCSEQ("micro", " "),
190 ESCSEQ("para", " "),
191
192 ESCSEQ("ordm", " "),
193 ESCSEQ("raquo", " "),
194
195 ESCSEQ("iquest", " "),
196 ESCSEQ("Agrave", "\300"/*À*/),
197
198 ESCSEQ("Acirc", "\302"/*Â*/),
199 ESCSEQ("Atilde", "\303"/*Ã*/),
200 ESCSEQ("Auml", "\304"/*Ä*/),
201 ESCSEQ("Aring", " "),
202 ESCSEQ("AElig", " "),
203 ESCSEQ("Ccedil", "\347"/*ç*/),
204 ESCSEQ("Egrave", "\310"/*È*/),
205 ESCSEQ("Eacute", "\311"/*É*/),
206 ESCSEQ("Ecirc", "\312"/*Ê*/),
207 ESCSEQ("Euml", "\313"/*Ë*/),
208 ESCSEQ("Igrave", "\314"/*Ì*/),
209
210 ESCSEQ("Icirc", "\316"/*Î*/),
211 ESCSEQ("Iuml", "\317"/*Ï*/),
212
213 ESCSEQ("Ntilde", "\321"/*Ñ*/),
214 ESCSEQ("Ograve", "\322"/*Ò*/),
215
216 ESCSEQ("Ocirc", "\324"/*Ô*/),
217 ESCSEQ("Otilde", "\325"/*Õ*/),
218 ESCSEQ("Ouml", "\326"/*Ö*/),
219
220 ESCSEQ("Oslash", " "),
221 ESCSEQ("Ugrave", "\331"/*Ù*/),
222
223 ESCSEQ("Ucirc", " "),
224 ESCSEQ("Uuml", "\334"/*Ü*/),
225
226 ESCSEQ("szlig", "\247"/*§*/),
227 ESCSEQ("agrave;","à"),
228 ESCSEQ("aacute", "\341"/*á*/),
229 ESCSEQ("acirc", "\342"/*â*/),
230 ESCSEQ("atilde", "\343"/*ã*/),
231 ESCSEQ("auml", "\344"/*ä*/),
232 ESCSEQ("aring", "a"),
233 ESCSEQ("aelig", "ae"),
234 ESCSEQ("ccedil", "\347"/*ç*/),
235 ESCSEQ("egrave", "\350"/*è*/),
236 ESCSEQ("eacute", "\351"/*é*/),
237 ESCSEQ("ecirc", "\352"/*ê*/),
238 ESCSEQ("euml", "\353"/*ë*/),
239 ESCSEQ("igrave", "\354"/*ì*/),
240 ESCSEQ("iacute", "\355"/*í*/),
241 ESCSEQ("icirc", " "),
242 ESCSEQ("iuml", "\357"/*ï*/),
243 ESCSEQ("eth", " "),
244 ESCSEQ("ntilde", "\361"/*ñ*/),
245 ESCSEQ("ograve", "\362"/*ò*/),
246 ESCSEQ("oacute", "\363"/*ó*/),
247 ESCSEQ("ocirc", "\364"/*ô*/),
248 ESCSEQ("otilde", "\365"/*õ*/),
249 ESCSEQ("ouml", "\366"/*ö*/),
250 ESCSEQ("divide", " "),
251 ESCSEQ("oslash", " "),
252 ESCSEQ("ugrave", "\371"/*ù*/),
253 ESCSEQ("uacute", "\372"/*ú*/),
254 ESCSEQ("ucirc", "\373"/*û*/),
255 ESCSEQ("uuml", "\374"/*ü*/),
256
257 ESCSEQ("yuml", ""),
258
259 /* this one should ALWAYS stay the last one!!! */
260 ESCSEQ("amp", "&"),
261
262 { NULL, NULL, NULL }
263 };
264
265 for (int i = 0; substitutions[i][0] != NULL; i++)
266 {
267 m_Name.Replace(substitutions[i][0], substitutions[i][2], TRUE);
268 m_Name.Replace(substitutions[i][1], substitutions[i][2], TRUE);
269 }
270 }
271 }
272 if (tag.GetParam(wxT("NAME")) == wxT("Local")) m_Page = tag.GetParam(wxT("VALUE"));
273 if (tag.GetParam(wxT("NAME")) == wxT("ID")) tag.ScanParam(wxT("VALUE"), wxT("%i"), &m_ID);
274 return FALSE;
275 }
276 }
277
278
279
280 void HP_TagHandler::WriteOut(wxHtmlContentsItem*& array, int& size)
281 {
282 array = m_Items;
283 size = m_ItemsCnt;
284 m_Items = NULL;
285 m_ItemsCnt = 0;
286 }
287
288 void HP_TagHandler::ReadIn(wxHtmlContentsItem* array, int size)
289 {
290 m_Items = array;
291 m_ItemsCnt = size;
292 }
293
294
295
296
297 //-----------------------------------------------------------------------------
298 // wxHtmlHelpData
299 //-----------------------------------------------------------------------------
300
301 IMPLEMENT_DYNAMIC_CLASS(wxHtmlHelpData, wxObject)
302
303 wxHtmlHelpData::wxHtmlHelpData()
304 {
305 m_TempPath = wxEmptyString;
306
307 m_Contents = NULL;
308 m_ContentsCnt = 0;
309 m_Index = NULL;
310 m_IndexCnt = 0;
311 }
312
313 wxHtmlHelpData::~wxHtmlHelpData()
314 {
315 int i;
316
317 m_BookRecords.Empty();
318 if (m_Contents)
319 {
320 for (i = 0; i < m_ContentsCnt; i++)
321 {
322 delete[] m_Contents[i].m_Page;
323 delete[] m_Contents[i].m_Name;
324 }
325 free(m_Contents);
326 }
327 if (m_Index)
328 {
329 for (i = 0; i < m_IndexCnt; i++)
330 {
331 delete[] m_Index[i].m_Page;
332 delete[] m_Index[i].m_Name;
333 }
334 free(m_Index);
335 }
336 }
337
338 bool wxHtmlHelpData::LoadMSProject(wxHtmlBookRecord *book, wxFileSystem& fsys, const wxString& indexfile, const wxString& contentsfile)
339 {
340 wxFSFile *f;
341 char *buf;
342 int sz;
343 wxString string;
344
345 HP_Parser parser;
346 HP_TagHandler *handler = new HP_TagHandler(book);
347 parser.AddTagHandler(handler);
348
349 f = ( contentsfile.IsEmpty() ? (wxFSFile*) NULL : fsys.OpenFile(contentsfile) );
350 if (f)
351 {
352 sz = f->GetStream()->GetSize();
353 buf = new char[sz + 1];
354 buf[sz] = 0;
355 f->GetStream()->Read(buf, sz);
356 delete f;
357 handler->ReadIn(m_Contents, m_ContentsCnt);
358 parser.Parse(buf);
359 handler->WriteOut(m_Contents, m_ContentsCnt);
360 delete[] buf;
361 }
362 else
363 wxLogError(_("Cannot open contents file: %s"), contentsfile.c_str());
364
365 f = ( indexfile.IsEmpty() ? (wxFSFile*) NULL : fsys.OpenFile(indexfile) );
366 if (f)
367 {
368 sz = f->GetStream()->GetSize();
369 buf = new char[sz + 1];
370 buf[sz] = 0;
371 f->GetStream()->Read(buf, sz);
372 delete f;
373 handler->ReadIn(m_Index, m_IndexCnt);
374 parser.Parse(buf);
375 handler->WriteOut(m_Index, m_IndexCnt);
376 delete[] buf;
377 }
378 else if (!indexfile.IsEmpty())
379 wxLogError(_("Cannot open index file: %s"), indexfile.c_str());
380 return TRUE;
381 }
382
383
384
385
386 #if wxUSE_UNICODE
387
388 #define READ_STRING(f, s, lng) { char tmpc; for (int i = 0; i < lng; i++) { f->Read(&tmpc, 1); s[i] = (wxChar)tmpc;} }
389 #define WRITE_STRING(f, s, lng) { char tmpc; for (int i = 0; i < lng; i++) { tmpc = (char)s[i]; f->Write(&tmpc, 1);} }
390
391 #else
392
393 #define READ_STRING(f, s, lng) f->Read(s, lng * sizeof(char));
394 #define WRITE_STRING(f, s, lng) f->Write(s, lng * sizeof(char));
395
396 #endif
397
398
399 #define CURRENT_CACHED_BOOK_VERSION 1
400
401 bool wxHtmlHelpData::LoadCachedBook(wxHtmlBookRecord *book, wxInputStream *f)
402 {
403 int i, st;
404 wxInt32 x;
405 wxInt32 version;
406
407 /* load header - version info : */
408
409 f->Read(&x, sizeof(x));
410 version = wxINT32_SWAP_ON_BE(x);
411
412 if (version != CURRENT_CACHED_BOOK_VERSION)
413 {
414 wxLogError(_("Incorrect version of HTML help book"));
415 return FALSE;
416 // NOTE: when adding new version, please ensure backward compatibility!
417 }
418
419 /* load contents : */
420
421 f->Read(&x, sizeof(x));
422 st = m_ContentsCnt;
423 m_ContentsCnt += wxINT32_SWAP_ON_BE(x);
424 m_Contents = (wxHtmlContentsItem*) realloc(m_Contents,
425 (m_ContentsCnt / wxHTML_REALLOC_STEP + 1) *
426 wxHTML_REALLOC_STEP * sizeof(wxHtmlContentsItem));
427 for (i = st; i < m_ContentsCnt; i++)
428 {
429 f->Read(&x, sizeof(x));
430 m_Contents[i].m_Level = wxINT32_SWAP_ON_BE(x);
431 f->Read(&x, sizeof(x));
432 m_Contents[i].m_ID = wxINT32_SWAP_ON_BE(x);
433 f->Read(&x, sizeof(x)); x = wxINT32_SWAP_ON_BE(x);
434 m_Contents[i].m_Name = new wxChar[x];
435 READ_STRING(f, m_Contents[i].m_Name, x);
436 f->Read(&x, sizeof(x)); x = wxINT32_SWAP_ON_BE(x);
437 m_Contents[i].m_Page = new wxChar[x];
438 READ_STRING(f, m_Contents[i].m_Page, x);
439 m_Contents[i].m_Book = book;
440 }
441
442 /* load index : */
443
444 f->Read(&x, sizeof(x));
445 st = m_IndexCnt;
446 m_IndexCnt += wxINT32_SWAP_ON_BE(x);
447 m_Index = (wxHtmlContentsItem*) realloc(m_Index, (m_IndexCnt / wxHTML_REALLOC_STEP + 1) *
448 wxHTML_REALLOC_STEP * sizeof(wxHtmlContentsItem));
449 for (i = st; i < m_IndexCnt; i++)
450 {
451 f->Read(&x, sizeof(x)); x = wxINT32_SWAP_ON_BE(x);
452 m_Index[i].m_Name = new wxChar[x];
453 READ_STRING(f, m_Index[i].m_Name, x);
454 f->Read(&x, sizeof(x)); x = wxINT32_SWAP_ON_BE(x);
455 m_Index[i].m_Page = new wxChar[x];
456 READ_STRING(f, m_Index[i].m_Page, x);
457 m_Index[i].m_Book = book;
458 }
459 return TRUE;
460 }
461
462
463 bool wxHtmlHelpData::SaveCachedBook(wxHtmlBookRecord *book, wxOutputStream *f)
464 {
465 int i;
466 wxInt32 x;
467
468 /* save header - version info : */
469
470 x = wxINT32_SWAP_ON_BE(CURRENT_CACHED_BOOK_VERSION);
471 f->Write(&x, sizeof(x));
472
473 /* save contents : */
474
475 x = 0;
476 for (i = 0; i < m_ContentsCnt; i++) if (m_Contents[i].m_Book == book && m_Contents[i].m_Level > 0) x++;
477 x = wxINT32_SWAP_ON_BE(x);
478 f->Write(&x, sizeof(x));
479 for (i = 0; i < m_ContentsCnt; i++)
480 {
481 if (m_Contents[i].m_Book != book || m_Contents[i].m_Level == 0) continue;
482 x = wxINT32_SWAP_ON_BE(m_Contents[i].m_Level);
483 f->Write(&x, sizeof(x));
484 x = wxINT32_SWAP_ON_BE(m_Contents[i].m_ID);
485 f->Write(&x, sizeof(x));
486 x = wxINT32_SWAP_ON_BE(wxStrlen(m_Contents[i].m_Name) + 1);
487 f->Write(&x, sizeof(x));
488 WRITE_STRING(f, m_Contents[i].m_Name, x);
489 x = wxINT32_SWAP_ON_BE(wxStrlen(m_Contents[i].m_Page) + 1);
490 f->Write(&x, sizeof(x));
491 WRITE_STRING(f, m_Contents[i].m_Page, x);
492 }
493
494 /* save index : */
495
496 x = 0;
497 for (i = 0; i < m_IndexCnt; i++) if (m_Index[i].m_Book == book && m_Index[i].m_Level > 0) x++;
498 x = wxINT32_SWAP_ON_BE(x);
499 f->Write(&x, sizeof(x));
500 for (i = 0; i < m_IndexCnt; i++)
501 {
502 if (m_Index[i].m_Book != book || m_Index[i].m_Level == 0) continue;
503 x = wxINT32_SWAP_ON_BE(wxStrlen(m_Index[i].m_Name) + 1);
504 f->Write(&x, sizeof(x));
505 WRITE_STRING(f, m_Index[i].m_Name, x);
506 x = wxINT32_SWAP_ON_BE(wxStrlen(m_Index[i].m_Page) + 1);
507 f->Write(&x, sizeof(x));
508 WRITE_STRING(f, m_Index[i].m_Page, x);
509 }
510 return TRUE;
511 }
512
513
514 void wxHtmlHelpData::SetTempDir(const wxString& path)
515 {
516 if (path == wxEmptyString) m_TempPath = path;
517 else
518 {
519 if (wxIsAbsolutePath(path)) m_TempPath = path;
520 else m_TempPath = wxGetCwd() + _T("/") + path;
521
522 if (m_TempPath[m_TempPath.Length() - 1] != _T('/'))
523 m_TempPath << _T('/');
524 }
525 }
526
527
528
529 static wxString SafeFileName(const wxString& s)
530 {
531 wxString res(s);
532 res.Replace(wxT("#"), wxT("_"));
533 res.Replace(wxT(":"), wxT("_"));
534 res.Replace(wxT("\\"), wxT("_"));
535 res.Replace(wxT("/"), wxT("_"));
536 return res;
537 }
538
539 bool wxHtmlHelpData::AddBookParam(const wxFSFile& bookfile,
540 wxFontEncoding encoding,
541 const wxString& title, const wxString& contfile,
542 const wxString& indexfile, const wxString& deftopic,
543 const wxString& path)
544 {
545 wxFileSystem fsys;
546 wxFSFile *fi;
547 wxHtmlBookRecord *bookr;
548
549 int IndexOld = m_IndexCnt,
550 ContentsOld = m_ContentsCnt;
551
552 if (! path.IsEmpty())
553 fsys.ChangePathTo(path, TRUE);
554
555 bookr = new wxHtmlBookRecord(fsys.GetPath(), title, deftopic);
556
557 if (m_ContentsCnt % wxHTML_REALLOC_STEP == 0)
558 m_Contents = (wxHtmlContentsItem*) realloc(m_Contents, (m_ContentsCnt + wxHTML_REALLOC_STEP) * sizeof(wxHtmlContentsItem));
559 m_Contents[m_ContentsCnt].m_Level = 0;
560 m_Contents[m_ContentsCnt].m_ID = 0;
561 m_Contents[m_ContentsCnt].m_Page = new wxChar[deftopic.Length() + 1];
562 wxStrcpy(m_Contents[m_ContentsCnt].m_Page, deftopic.c_str());
563 m_Contents[m_ContentsCnt].m_Name = new wxChar [title.Length() + 1];
564 wxStrcpy(m_Contents[m_ContentsCnt].m_Name, title.c_str());
565 m_Contents[m_ContentsCnt].m_Book = bookr;
566
567 // store the contents index for later
568 int cont_start = m_ContentsCnt++;
569
570 // Try to find cached binary versions:
571 // 1. save file as book, but with .hhp.cached extension
572 // 2. same as 1. but in temp path
573 // 3. otherwise or if cache load failed, load it from MS.
574
575 fi = fsys.OpenFile(bookfile.GetLocation() + wxT(".cached"));
576
577 if (fi == NULL ||
578 fi->GetModificationTime() < bookfile.GetModificationTime() ||
579 !LoadCachedBook(bookr, fi->GetStream()))
580 {
581 if (fi != NULL) delete fi;
582 fi = fsys.OpenFile(m_TempPath + wxFileNameFromPath(bookfile.GetLocation()) + wxT(".cached"));
583 if (m_TempPath == wxEmptyString || fi == NULL ||
584 fi->GetModificationTime() < bookfile.GetModificationTime() ||
585 !LoadCachedBook(bookr, fi->GetStream()))
586 {
587 LoadMSProject(bookr, fsys, indexfile, contfile);
588 if (m_TempPath != wxEmptyString)
589 {
590 wxFileOutputStream *outs = new wxFileOutputStream(m_TempPath +
591 SafeFileName(wxFileNameFromPath(bookfile.GetLocation())) + wxT(".cached"));
592 SaveCachedBook(bookr, outs);
593 delete outs;
594 }
595 }
596 }
597
598 if (fi != NULL) delete fi;
599
600 // Now store the contents range
601 bookr->SetContentsRange(cont_start, m_ContentsCnt);
602
603 // Convert encoding, if neccessary:
604 if (encoding != wxFONTENCODING_SYSTEM)
605 {
606 wxFontEncodingArray a = wxEncodingConverter::GetPlatformEquivalents(encoding);
607 if (a.GetCount() != 0 && a[0] != encoding)
608 {
609 int i;
610 wxEncodingConverter conv;
611 conv.Init(encoding, a[0]);
612
613 for (i = IndexOld; i < m_IndexCnt; i++)
614 conv.Convert(m_Index[i].m_Name);
615 for (i = ContentsOld; i < m_ContentsCnt; i++)
616 conv.Convert(m_Contents[i].m_Name);
617 }
618 }
619
620 m_BookRecords.Add(bookr);
621 if (m_IndexCnt > 0)
622 qsort(m_Index, m_IndexCnt, sizeof(wxHtmlContentsItem), IndexCompareFunc);
623
624 return TRUE;
625 }
626
627
628 bool wxHtmlHelpData::AddBook(const wxString& book)
629 {
630 if (book.Right(4).Lower() == wxT(".zip") ||
631 book.Right(4).Lower() == wxT(".htb") /*html book*/)
632
633 {
634 wxFileSystem fsys;
635 wxString s;
636 bool rt = FALSE;
637
638 s = fsys.FindFirst(book + wxT("#zip:") + wxT("*.hhp"), wxFILE);
639 while (!s.IsEmpty())
640 {
641 if (AddBook(s)) rt = TRUE;
642 s = fsys.FindNext();
643 }
644
645 return rt;
646 }
647
648
649 else
650 {
651 wxFSFile *fi;
652 wxFileSystem fsys;
653 wxInputStream *s;
654 wxString bookFull;
655
656 int sz;
657 char *buff, *lineptr;
658 char linebuf[300];
659
660 wxString title = _("noname"),
661 safetitle,
662 start = wxEmptyString,
663 contents = wxEmptyString,
664 index = wxEmptyString,
665 charset = wxEmptyString;
666
667 if (wxIsAbsolutePath(book)) bookFull = book;
668 else bookFull = wxGetCwd() + "/" + book;
669
670 fi = fsys.OpenFile(bookFull);
671 if (fi == NULL)
672 {
673 wxLogError(_("Cannot open HTML help book: %s"), bookFull.c_str());
674 return FALSE;
675 }
676 fsys.ChangePathTo(bookFull);
677 s = fi->GetStream();
678 sz = s->GetSize();
679 buff = new char[sz + 1];
680 buff[sz] = 0;
681 s->Read(buff, sz);
682 lineptr = buff;
683
684 do {
685 lineptr = ReadLine(lineptr, linebuf);
686
687 if (strstr(linebuf, "Title=") == linebuf)
688 title = linebuf + strlen("Title=");
689 if (strstr(linebuf, "Default topic=") == linebuf)
690 start = linebuf + strlen("Default topic=");
691 if (strstr(linebuf, "Index file=") == linebuf)
692 index = linebuf + strlen("Index file=");
693 if (strstr(linebuf, "Contents file=") == linebuf)
694 contents = linebuf + strlen("Contents file=");
695 if (strstr(linebuf, "Charset=") == linebuf)
696 charset = linebuf + strlen("Charset=");
697 } while (lineptr != NULL);
698 delete[] buff;
699
700 wxFontEncoding enc;
701 if (charset == wxEmptyString) enc = wxFONTENCODING_SYSTEM;
702 else enc = wxTheFontMapper->CharsetToEncoding(charset);
703 bool rtval = AddBookParam(*fi, enc,
704 title, contents, index, start, fsys.GetPath());
705 delete fi;
706 return rtval;
707 }
708 }
709
710 wxString wxHtmlHelpData::FindPageByName(const wxString& x)
711 {
712 int cnt;
713 int i;
714 wxFileSystem fsys;
715 wxFSFile *f;
716 wxString url(wxEmptyString);
717
718 /* 1. try to open given file: */
719
720 cnt = m_BookRecords.GetCount();
721 for (i = 0; i < cnt; i++)
722 {
723 f = fsys.OpenFile(m_BookRecords[i].GetBasePath() + x);
724 if (f)
725 {
726 url = m_BookRecords[i].GetBasePath() + x;
727 delete f;
728 return url;
729 }
730 }
731
732
733 /* 2. try to find a book: */
734
735 for (i = 0; i < cnt; i++)
736 {
737 if (m_BookRecords[i].GetTitle() == x)
738 {
739 url = m_BookRecords[i].GetBasePath() + m_BookRecords[i].GetStart();
740 return url;
741 }
742 }
743
744 /* 3. try to find in contents: */
745
746 cnt = m_ContentsCnt;
747 for (i = 0; i < cnt; i++)
748 {
749 if (wxStrcmp(m_Contents[i].m_Name, x) == 0)
750 {
751 url = m_Contents[i].m_Book->GetBasePath() + m_Contents[i].m_Page;
752 return url;
753 }
754 }
755
756
757 /* 4. try to find in index: */
758
759 cnt = m_IndexCnt;
760 for (i = 0; i < cnt; i++)
761 {
762 if (wxStrcmp(m_Index[i].m_Name, x) == 0)
763 {
764 url = m_Index[i].m_Book->GetBasePath() + m_Index[i].m_Page;
765 return url;
766 }
767 }
768
769 return url;
770 }
771
772 wxString wxHtmlHelpData::FindPageById(int id)
773 {
774 int i;
775 wxString url(wxEmptyString);
776
777 for (i = 0; i < m_ContentsCnt; i++)
778 {
779 if (m_Contents[i].m_ID == id)
780 {
781 url = m_Contents[i].m_Book->GetBasePath() + m_Contents[i].m_Page;
782 return url;
783 }
784 }
785
786 return url;
787 }
788
789 //----------------------------------------------------------------------------------
790 // wxHtmlSearchStatus functions
791 //----------------------------------------------------------------------------------
792
793 wxHtmlSearchStatus::wxHtmlSearchStatus(wxHtmlHelpData* data, const wxString& keyword,
794 bool case_sensitive, bool whole_words_only,
795 const wxString& book)
796 {
797 m_Data = data;
798 m_Keyword = keyword;
799 wxHtmlBookRecord* bookr = NULL;
800 if (book != wxEmptyString)
801 {
802 // we have to search in a specific book. Find it first
803 int i, cnt = data->m_BookRecords.GetCount();
804 for (i = 0; i < cnt; i++)
805 if (data->m_BookRecords[i].GetTitle() == book)
806 {
807 bookr = &(data->m_BookRecords[i]);
808 m_CurIndex = bookr->GetContentsStart();
809 m_MaxIndex = bookr->GetContentsEnd();
810 break;
811 }
812 // check; we won't crash if the book doesn't exist, but it's Bad Anyway.
813 wxASSERT(bookr);
814 }
815 if (! bookr)
816 {
817 // no book specified; search all books
818 m_CurIndex = 0;
819 m_MaxIndex = m_Data->m_ContentsCnt;
820 }
821 m_Engine.LookFor(keyword, case_sensitive, whole_words_only);
822 m_Active = (m_CurIndex < m_MaxIndex);
823 m_LastPage = NULL;
824 }
825
826 bool wxHtmlSearchStatus::Search()
827 {
828 wxFSFile *file;
829 int i = m_CurIndex; // shortcut
830 bool found = FALSE;
831 wxChar *thepage;
832
833 if (!m_Active)
834 {
835 // sanity check. Illegal use, but we'll try to prevent a crash anyway
836 wxASSERT(m_Active);
837 return FALSE;
838 }
839
840 m_Name = wxEmptyString;
841 m_ContentsItem = NULL;
842 thepage = m_Data->m_Contents[i].m_Page;
843
844 m_Active = (++m_CurIndex < m_MaxIndex);
845 // check if it is same page with different anchor:
846 if (m_LastPage != NULL)
847 {
848 wxChar *p1, *p2;
849 for (p1 = thepage, p2 = m_LastPage;
850 *p1 != 0 && *p1 != _T('#') && *p1 == *p2; p1++, p2++) {}
851
852 m_LastPage = thepage;
853
854 if (*p1 == 0 || *p1 == _T('#'))
855 return FALSE;
856 }
857 else m_LastPage = thepage;
858
859 wxFileSystem fsys;
860 file = fsys.OpenFile(m_Data->m_Contents[i].m_Book->GetBasePath() + thepage);
861 if (file)
862 {
863 if (m_Engine.Scan(file->GetStream())) {
864 m_Name = m_Data->m_Contents[i].m_Name;
865 m_ContentsItem = m_Data->m_Contents + i;
866 found = TRUE;
867 }
868 delete file;
869 }
870 return found;
871 }
872
873
874
875
876
877
878
879
880 //--------------------------------------------------------------------------------
881 // wxSearchEngine
882 //--------------------------------------------------------------------------------
883
884 void wxSearchEngine::LookFor(const wxString& keyword, bool case_sensitive, bool whole_words_only)
885 {
886 m_CaseSensitive = case_sensitive;
887 m_WholeWords = whole_words_only;
888 if (m_Keyword) delete[] m_Keyword;
889 m_Keyword = new wxChar[keyword.Length() + 1];
890 wxStrcpy(m_Keyword, keyword.c_str());
891
892 if (!m_CaseSensitive)
893 {
894 for (int i = wxStrlen(m_Keyword) - 1; i >= 0; i--)
895 {
896 if ((m_Keyword[i] >= wxT('A')) && (m_Keyword[i] <= wxT('Z')))
897 m_Keyword[i] += wxT('a') - wxT('A');
898 }
899 }
900 }
901
902
903
904 #define WHITESPACE(c) (c == ' ' || c == '\n' || c == '\r' || c == '\t')
905
906 bool wxSearchEngine::Scan(wxInputStream *stream)
907 {
908 wxASSERT_MSG(m_Keyword != NULL, wxT("wxSearchEngine::LookFor must be called before scanning!"));
909
910 int i, j;
911 int lng = stream ->GetSize();
912 int wrd = wxStrlen(m_Keyword);
913 bool found = FALSE;
914 char *buf = new char[lng + 1];
915 stream->Read(buf, lng);
916 buf[lng] = 0;
917
918 if (!m_CaseSensitive)
919 for (i = 0; i < lng; i++)
920 if ((buf[i] >= 'A') && (buf[i] <= 'Z')) buf[i] += 'a' - 'A';
921
922 if (m_WholeWords)
923 {
924 for (i = 0; i < lng - wrd; i++)
925 {
926 if (WHITESPACE(buf[i])) continue;
927 j = 0;
928 while ((j < wrd) && (buf[i + j] == m_Keyword[j])) j++;
929 if (j == wrd && WHITESPACE(buf[i + j])) { found = TRUE; break; }
930 }
931 }
932
933 else
934 {
935 for (i = 0; i < lng - wrd; i++)
936 {
937 j = 0;
938 while ((j < wrd) && (buf[i + j] == m_Keyword[j])) j++;
939 if (j == wrd) { found = TRUE; break; }
940 }
941 }
942
943 delete[] buf;
944 return found;
945 }
946
947
948
949
950 #endif