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 
   8 // Copyright:   (c) Harm van der Heijden and Vaclav Slavik 
   9 // Licence:     wxWindows licence 
  10 ///////////////////////////////////////////////////////////////////////////// 
  12 // For compilers that support precompilation, includes "wx.h". 
  13 #include "wx/wxprec.h" 
  19 #if wxUSE_HTML && wxUSE_STREAMS 
  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/html/htmlpars.h" 
  35 #include "wx/html/htmldefs.h" 
  36 #include "wx/html/htmlfilt.h" 
  37 #include "wx/filename.h" 
  39 #include "wx/arrimpl.cpp" 
  40 WX_DEFINE_OBJARRAY(wxHtmlBookRecArray
) 
  41 WX_DEFINE_OBJARRAY(wxHtmlHelpDataItems
) 
  43 //----------------------------------------------------------------------------- 
  44 // static helper functions 
  45 //----------------------------------------------------------------------------- 
  47 // Reads one line, stores it into buf and returns pointer to new line or NULL. 
  48 static const wxChar
* ReadLine(const wxChar 
*line
, wxChar 
*buf
, size_t bufsize
) 
  50     wxChar 
*writeptr 
= buf
; 
  51     wxChar 
*endptr 
= buf 
+ bufsize 
- 1; 
  52     const wxChar 
*readptr 
= line
; 
  54     while (*readptr 
!= 0 && *readptr 
!= wxT('\r') && *readptr 
!= wxT('\n') && 
  56         *(writeptr
++) = *(readptr
++); 
  58     while (*readptr 
== wxT('\r') || *readptr 
== wxT('\n')) 
  69 wxHtmlHelpIndexCompareFunc(wxHtmlHelpDataItem 
**a
, wxHtmlHelpDataItem 
**b
) 
  71     wxHtmlHelpDataItem 
*ia 
= *a
; 
  72     wxHtmlHelpDataItem 
*ib 
= *b
; 
  79     if (ia
->parent 
== ib
->parent
) 
  81         return ia
->name
.CmpNoCase(ib
->name
); 
  83     else if (ia
->level 
== ib
->level
) 
  85         return wxHtmlHelpIndexCompareFunc(&ia
->parent
, &ib
->parent
); 
  89         wxHtmlHelpDataItem 
*ia2 
= ia
; 
  90         wxHtmlHelpDataItem 
*ib2 
= ib
; 
  92         while (ia2
->level 
> ib2
->level
) 
  96         while (ib2
->level 
> ia2
->level
) 
 103         int res 
= wxHtmlHelpIndexCompareFunc(&ia2
, &ib2
); 
 106         else if (ia
->level 
> ib
->level
) 
 113 //----------------------------------------------------------------------------- 
 115 //----------------------------------------------------------------------------- 
 117 class HP_Parser 
: public wxHtmlParser
 
 122         GetEntitiesParser()->SetEncoding(wxFONTENCODING_ISO8859_1
); 
 125     wxObject
* GetProduct() { return NULL
; } 
 128     virtual void AddText(const wxString
& WXUNUSED(txt
)) {} 
 130     wxDECLARE_NO_COPY_CLASS(HP_Parser
); 
 134 //----------------------------------------------------------------------------- 
 136 //----------------------------------------------------------------------------- 
 138 class HP_TagHandler 
: public wxHtmlTagHandler
 
 141         wxString m_name
, m_page
; 
 146         wxHtmlHelpDataItem 
*m_parentItem
; 
 147         wxHtmlBookRecord 
*m_book
; 
 149         wxHtmlHelpDataItems 
*m_data
; 
 152         HP_TagHandler(wxHtmlBookRecord 
*b
) : wxHtmlTagHandler() 
 156             m_name 
= m_page 
= wxEmptyString
; 
 162         wxString 
GetSupportedTags() { return wxT("UL,OBJECT,PARAM"); } 
 163         bool HandleTag(const wxHtmlTag
& tag
); 
 165         void Reset(wxHtmlHelpDataItems
& data
) 
 173     wxDECLARE_NO_COPY_CLASS(HP_TagHandler
); 
 177 bool HP_TagHandler::HandleTag(const wxHtmlTag
& tag
) 
 179     if (tag
.GetName() == wxT("UL")) 
 181         wxHtmlHelpDataItem 
*oldparent 
= m_parentItem
; 
 183         m_parentItem 
= (m_count 
> 0) ? &(*m_data
)[m_data
->size()-1] : NULL
; 
 186         m_parentItem 
= oldparent
; 
 189     else if (tag
.GetName() == wxT("OBJECT")) 
 191         m_name 
= m_page 
= wxEmptyString
; 
 196         /* Valid HHW's file may contain only two object tags: 
 198            <OBJECT type="text/site properties"> 
 199                <param name="ImageType" value="Folder"> 
 204            <OBJECT type="text/sitemap"> 
 205                <param name="Name" value="main page"> 
 206                <param name="Local" value="another.htm"> 
 209            We're interested in the latter. !page.IsEmpty() is valid 
 210            condition because text/site properties does not contain Local param 
 213         if (tag
.GetParam(wxT("TYPE")) == wxT("text/sitemap")) 
 215             wxHtmlHelpDataItem 
*item 
= new wxHtmlHelpDataItem(); 
 216             item
->parent 
= m_parentItem
; 
 217             item
->level 
= m_level
; 
 231         if (m_name
.empty() && tag
.GetParam(wxT("NAME")) == wxT("Name")) 
 232             m_name 
= tag
.GetParam(wxT("VALUE")); 
 233         if (tag
.GetParam(wxT("NAME")) == wxT("Local")) 
 234             m_page 
= tag
.GetParam(wxT("VALUE")); 
 235         if (tag
.GetParam(wxT("NAME")) == wxT("ID")) 
 236             tag
.GetParamAsInt(wxT("VALUE"), &m_id
); 
 242 //----------------------------------------------------------------------------- 
 244 //----------------------------------------------------------------------------- 
 246 wxString 
wxHtmlBookRecord::GetFullPath(const wxString 
&page
) const 
 248     if (wxIsAbsolutePath(page
)) 
 251         return m_BasePath 
+ page
; 
 254 wxString 
wxHtmlHelpDataItem::GetIndentedName() const 
 257     for (int i 
= 1; i 
< level
; i
++) 
 264 IMPLEMENT_DYNAMIC_CLASS(wxHtmlHelpData
, wxObject
) 
 266 wxHtmlHelpData::wxHtmlHelpData() 
 270 wxHtmlHelpData::~wxHtmlHelpData() 
 274 bool wxHtmlHelpData::LoadMSProject(wxHtmlBookRecord 
*book
, wxFileSystem
& fsys
, 
 275                                    const wxString
& indexfile
, 
 276                                    const wxString
& contentsfile
) 
 279     wxHtmlFilterHTML filter
; 
 284     HP_TagHandler 
*handler 
= new HP_TagHandler(book
); 
 285     parser
.AddTagHandler(handler
); 
 287     f 
= ( contentsfile
.empty() ? NULL 
: fsys
.OpenFile(contentsfile
) ); 
 291         buf 
= filter
.ReadFile(*f
); 
 293         handler
->Reset(m_contents
); 
 298         wxLogError(_("Cannot open contents file: %s"), contentsfile
.c_str()); 
 301     f 
= ( indexfile
.empty() ? NULL 
: fsys
.OpenFile(indexfile
) ); 
 305         buf 
= filter
.ReadFile(*f
); 
 307         handler
->Reset(m_index
); 
 310     else if (!indexfile
.empty()) 
 312         wxLogError(_("Cannot open index file: %s"), indexfile
.c_str()); 
 317 inline static void CacheWriteInt32(wxOutputStream 
*f
, wxInt32 value
) 
 319     wxInt32 x 
= wxINT32_SWAP_ON_BE(value
); 
 320     f
->Write(&x
, sizeof(x
)); 
 323 inline static wxInt32 
CacheReadInt32(wxInputStream 
*f
) 
 326     f
->Read(&x
, sizeof(x
)); 
 327     return wxINT32_SWAP_ON_BE(x
); 
 330 inline static void CacheWriteString(wxOutputStream 
*f
, const wxString
& str
) 
 332     const wxWX2MBbuf mbstr 
= str
.mb_str(wxConvUTF8
); 
 333     size_t len 
= strlen((const char*)mbstr
)+1; 
 334     CacheWriteInt32(f
, len
); 
 335     f
->Write((const char*)mbstr
, len
); 
 338 inline static wxString 
CacheReadString(wxInputStream 
*f
) 
 340     size_t len 
= (size_t)CacheReadInt32(f
); 
 341     wxCharBuffer 
str(len
-1); 
 342     f
->Read(str
.data(), len
); 
 343     return wxString(str
, wxConvUTF8
); 
 346 #define CURRENT_CACHED_BOOK_VERSION     5 
 348 // Additional flags to detect incompatibilities of the runtime environment: 
 349 #define CACHED_BOOK_FORMAT_FLAGS \ 
 353 bool wxHtmlHelpData::LoadCachedBook(wxHtmlBookRecord 
*book
, wxInputStream 
*f
) 
 358     /* load header - version info : */ 
 359     version 
= CacheReadInt32(f
); 
 361     if (version 
!= CURRENT_CACHED_BOOK_VERSION
) 
 363         // NB: We can just silently return false here and don't worry about 
 364         //     it anymore, because AddBookParam will load the MS project in 
 365         //     absence of (properly versioned) .cached file and automatically 
 366         //     create new .cached file immediately afterward. 
 370     if (CacheReadInt32(f
) != CACHED_BOOK_FORMAT_FLAGS
) 
 373     /* load contents : */ 
 374     st 
= m_contents
.size(); 
 375     newsize 
= st 
+ CacheReadInt32(f
); 
 376     m_contents
.Alloc(newsize
); 
 377     for (i 
= st
; i 
< newsize
; i
++) 
 379         wxHtmlHelpDataItem 
*item 
= new wxHtmlHelpDataItem
; 
 380         item
->level 
= CacheReadInt32(f
); 
 381         item
->id 
= CacheReadInt32(f
); 
 382         item
->name 
= CacheReadString(f
); 
 383         item
->page 
= CacheReadString(f
); 
 385         m_contents
.Add(item
); 
 390     newsize 
= st 
+ CacheReadInt32(f
); 
 391     m_index
.Alloc(newsize
); 
 392     for (i 
= st
; i 
< newsize
; i
++) 
 394         wxHtmlHelpDataItem 
*item 
= new wxHtmlHelpDataItem
; 
 395         item
->name 
= CacheReadString(f
); 
 396         item
->page 
= CacheReadString(f
); 
 397         item
->level 
= CacheReadInt32(f
); 
 399         int parentShift 
= CacheReadInt32(f
); 
 400         if (parentShift 
!= 0) 
 401             item
->parent 
= &m_index
[m_index
.size() - parentShift
]; 
 408 bool wxHtmlHelpData::SaveCachedBook(wxHtmlBookRecord 
*book
, wxOutputStream 
*f
) 
 413     /* save header - version info : */ 
 414     CacheWriteInt32(f
, CURRENT_CACHED_BOOK_VERSION
); 
 415     CacheWriteInt32(f
, CACHED_BOOK_FORMAT_FLAGS
); 
 417     /* save contents : */ 
 418     int len 
= m_contents
.size(); 
 419     for (cnt 
= 0, i 
= 0; i 
< len
; i
++) 
 420         if (m_contents
[i
].book 
== book 
&& m_contents
[i
].level 
> 0) 
 422     CacheWriteInt32(f
, cnt
); 
 424     for (i 
= 0; i 
< len
; i
++) 
 426         if (m_contents
[i
].book 
!= book 
|| m_contents
[i
].level 
== 0) 
 428         CacheWriteInt32(f
, m_contents
[i
].level
); 
 429         CacheWriteInt32(f
, m_contents
[i
].id
); 
 430         CacheWriteString(f
, m_contents
[i
].name
); 
 431         CacheWriteString(f
, m_contents
[i
].page
); 
 435     len 
= m_index
.size(); 
 436     for (cnt 
= 0, i 
= 0; i 
< len
; i
++) 
 437         if (m_index
[i
].book 
== book 
&& m_index
[i
].level 
> 0) 
 439     CacheWriteInt32(f
, cnt
); 
 441     for (i 
= 0; i 
< len
; i
++) 
 443         if (m_index
[i
].book 
!= book 
|| m_index
[i
].level 
== 0) 
 445         CacheWriteString(f
, m_index
[i
].name
); 
 446         CacheWriteString(f
, m_index
[i
].page
); 
 447         CacheWriteInt32(f
, m_index
[i
].level
); 
 448         // save distance to parent item, if any: 
 449         if (m_index
[i
].parent 
== NULL
) 
 451             CacheWriteInt32(f
, 0); 
 456             wxHtmlHelpDataItem 
*parent 
= m_index
[i
].parent
; 
 457             for (int j 
= i
-1; j 
>= 0; j
--) 
 459                 if (m_index
[j
].book 
== book 
&& m_index
[j
].level 
> 0) 
 461                 if (&m_index
[j
] == parent
) 
 465             CacheWriteInt32(f
, cnt2
); 
 472 void wxHtmlHelpData::SetTempDir(const wxString
& path
) 
 478         if (wxIsAbsolutePath(path
)) m_tempPath 
= path
; 
 479         else m_tempPath 
= wxGetCwd() + wxT("/") + path
; 
 481         if (m_tempPath
[m_tempPath
.length() - 1] != wxT('/')) 
 482             m_tempPath 
<< wxT('/'); 
 488 static wxString 
SafeFileName(const wxString
& s
) 
 491     res
.Replace(wxT("#"), wxT("_")); 
 492     res
.Replace(wxT(":"), wxT("_")); 
 493     res
.Replace(wxT("\\"), wxT("_")); 
 494     res
.Replace(wxT("/"), wxT("_")); 
 498 bool wxHtmlHelpData::AddBookParam(const wxFSFile
& bookfile
, 
 499                                   wxFontEncoding encoding
, 
 500                                   const wxString
& title
, const wxString
& contfile
, 
 501                                   const wxString
& indexfile
, const wxString
& deftopic
, 
 502                                   const wxString
& path
) 
 506     wxHtmlBookRecord 
*bookr
; 
 508     int IndexOld 
= m_index
.size(), 
 509         ContentsOld 
= m_contents
.size(); 
 512         fsys
.ChangePathTo(path
, true); 
 514     size_t booksCnt 
= m_bookRecords
.GetCount(); 
 515     for (size_t i 
= 0; i 
< booksCnt
; i
++) 
 517         if ( m_bookRecords
[i
].GetBookFile() == bookfile
.GetLocation() ) 
 518             return true; // book is (was) loaded 
 521     bookr 
= new wxHtmlBookRecord(bookfile
.GetLocation(), fsys
.GetPath(), title
, deftopic
); 
 523     wxHtmlHelpDataItem 
*bookitem 
= new wxHtmlHelpDataItem
; 
 526     bookitem
->page 
= deftopic
; 
 527     bookitem
->name 
= title
; 
 528     bookitem
->book 
= bookr
; 
 530     // store the contents index for later 
 531     int cont_start 
= m_contents
.size(); 
 533     m_contents
.Add(bookitem
); 
 535     // Try to find cached binary versions: 
 536     // 1. save file as book, but with .hhp.cached extension 
 537     // 2. same as 1. but in temp path 
 538     // 3. otherwise or if cache load failed, load it from MS. 
 540     fi 
= fsys
.OpenFile(bookfile
.GetLocation() + wxT(".cached")); 
 544           fi
->GetModificationTime() < bookfile
.GetModificationTime() || 
 545 #endif // wxUSE_DATETIME 
 546           !LoadCachedBook(bookr
, fi
->GetStream())) 
 548         if (fi 
!= NULL
) delete fi
; 
 549         fi 
= fsys
.OpenFile(m_tempPath 
+ wxFileNameFromPath(bookfile
.GetLocation()) + wxT(".cached")); 
 550         if (m_tempPath
.empty() || fi 
== NULL 
|| 
 552             fi
->GetModificationTime() < bookfile
.GetModificationTime() || 
 553 #endif // wxUSE_DATETIME 
 554             !LoadCachedBook(bookr
, fi
->GetStream())) 
 556             LoadMSProject(bookr
, fsys
, indexfile
, contfile
); 
 557             if (!m_tempPath
.empty()) 
 559                 wxFileOutputStream 
*outs 
= new wxFileOutputStream(m_tempPath 
+ 
 560                                                   SafeFileName(wxFileNameFromPath(bookfile
.GetLocation())) + wxT(".cached")); 
 561                 SaveCachedBook(bookr
, outs
); 
 567     if (fi 
!= NULL
) delete fi
; 
 569     // Now store the contents range 
 570     bookr
->SetContentsRange(cont_start
, m_contents
.size()); 
 573     // MS HTML Help files [written by MS HTML Help Workshop] are broken 
 574     // in that the data are iso-8859-1 (including HTML entities), but must 
 575     // be interpreted as being in language's windows charset. Correct the 
 576     // differences here and also convert to wxConvLocal in ANSI build 
 577     if (encoding 
!= wxFONTENCODING_SYSTEM
) 
 580             #define CORRECT_STR(str, conv) \ 
 581                 str = wxString((str).mb_str(wxConvISO8859_1), conv) 
 583             #define CORRECT_STR(str, conv) \ 
 584                 str = wxString((str).wc_str(conv), wxConvLocal) 
 586         wxCSConv 
conv(encoding
); 
 587         size_t IndexCnt 
= m_index
.size(); 
 588         size_t ContentsCnt 
= m_contents
.size(); 
 590         for (i 
= IndexOld
; i 
< IndexCnt
; i
++) 
 592             CORRECT_STR(m_index
[i
].name
, conv
); 
 594         for (i 
= ContentsOld
; i 
< ContentsCnt
; i
++) 
 596             CORRECT_STR(m_contents
[i
].name
, conv
); 
 601     wxUnusedVar(IndexOld
); 
 602     wxUnusedVar(ContentsOld
); 
 603     wxASSERT_MSG(encoding 
== wxFONTENCODING_SYSTEM
, wxT("Help files need charset conversion, but wxUSE_WCHAR_T is 0")); 
 604 #endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T 
 606     m_bookRecords
.Add(bookr
); 
 607     if (!m_index
.empty()) 
 609         m_index
.Sort(wxHtmlHelpIndexCompareFunc
); 
 616 bool wxHtmlHelpData::AddBook(const wxString
& book
) 
 618     wxString 
extension(book
.Right(4).Lower()); 
 619     if (extension 
== wxT(".zip") || 
 621         extension 
== wxT(".chm") /*compressed html help book*/ || 
 623         extension 
== wxT(".htb") /*html book*/) 
 630         if (extension 
== wxT(".chm")) 
 631             s 
= fsys
.FindFirst(book 
+ wxT("#chm:*.hhp"), wxFILE
); 
 634             s 
= fsys
.FindFirst(book 
+ wxT("#zip:*.hhp"), wxFILE
); 
 638             if (AddBook(s
)) rt 
= true; 
 648     wxString title 
= _("noname"), 
 650              start 
= wxEmptyString
, 
 651              contents 
= wxEmptyString
, 
 652              index 
= wxEmptyString
, 
 653              charset 
= wxEmptyString
; 
 655     fi 
= fsys
.OpenFile(book
); 
 658         wxLogError(_("Cannot open HTML help book: %s"), book
.c_str()); 
 661     fsys
.ChangePathTo(book
); 
 663     const wxChar 
*lineptr
; 
 666     wxHtmlFilterPlainText filter
; 
 667     tmp 
= filter
.ReadFile(*fi
); 
 668     lineptr 
= tmp
.c_str(); 
 672         lineptr 
= ReadLine(lineptr
, linebuf
, 300); 
 674         for (wxChar 
*ch 
= linebuf
; *ch 
!= wxT('\0') && *ch 
!= wxT('='); ch
++) 
 675            *ch 
= (wxChar
)wxTolower(*ch
); 
 677         if (wxStrstr(linebuf
, wxT("title=")) == linebuf
) 
 678             title 
= linebuf 
+ wxStrlen(wxT("title=")); 
 679         if (wxStrstr(linebuf
, wxT("default topic=")) == linebuf
) 
 680             start 
= linebuf 
+ wxStrlen(wxT("default topic=")); 
 681         if (wxStrstr(linebuf
, wxT("index file=")) == linebuf
) 
 682             index 
= linebuf 
+ wxStrlen(wxT("index file=")); 
 683         if (wxStrstr(linebuf
, wxT("contents file=")) == linebuf
) 
 684             contents 
= linebuf 
+ wxStrlen(wxT("contents file=")); 
 685         if (wxStrstr(linebuf
, wxT("charset=")) == linebuf
) 
 686             charset 
= linebuf 
+ wxStrlen(wxT("charset=")); 
 687     } while (lineptr 
!= NULL
); 
 689     wxFontEncoding enc 
= wxFONTENCODING_SYSTEM
; 
 691     if (charset 
!= wxEmptyString
) 
 692         enc 
= wxFontMapper::Get()->CharsetToEncoding(charset
); 
 695     bool rtval 
= AddBookParam(*fi
, enc
, 
 696                               title
, contents
, index
, start
, fsys
.GetPath()); 
 702 wxString 
wxHtmlHelpData::FindPageByName(const wxString
& x
) 
 706     bool has_non_ascii 
= false; 
 707     wxString::const_iterator it
; 
 708     for (it 
= x
.begin(); it 
!= x
.end(); ++it
) 
 713             has_non_ascii 
= true; 
 718     int cnt 
= m_bookRecords
.GetCount(); 
 724       // 1. try to open given file: 
 725       for (i 
= 0; i 
< cnt
; i
++) 
 727         f 
= fsys
.OpenFile(m_bookRecords
[i
].GetFullPath(x
)); 
 730             wxString url 
= m_bookRecords
[i
].GetFullPath(x
); 
 738     // 2. try to find a book: 
 739     for (i 
= 0; i 
< cnt
; i
++) 
 741         if (m_bookRecords
[i
].GetTitle() == x
) 
 742             return m_bookRecords
[i
].GetFullPath(m_bookRecords
[i
].GetStart()); 
 745     // 3. try to find in contents: 
 746     cnt 
= m_contents
.size(); 
 747     for (i 
= 0; i 
< cnt
; i
++) 
 749         if (m_contents
[i
].name 
== x
) 
 750             return m_contents
[i
].GetFullPath(); 
 754     // 4. try to find in index: 
 755     cnt 
= m_index
.size(); 
 756     for (i 
= 0; i 
< cnt
; i
++) 
 758         if (m_index
[i
].name 
== x
) 
 759             return m_index
[i
].GetFullPath(); 
 762     // 4b. if still not found, try case-insensitive comparison 
 763     for (i 
= 0; i 
< cnt
; i
++) 
 765         if (m_index
[i
].name
.CmpNoCase(x
) == 0) 
 766             return m_index
[i
].GetFullPath(); 
 769     return wxEmptyString
; 
 772 wxString 
wxHtmlHelpData::FindPageById(int id
) 
 774     size_t cnt 
= m_contents
.size(); 
 775     for (size_t i 
= 0; i 
< cnt
; i
++) 
 777         if (m_contents
[i
].id 
== id
) 
 779             return m_contents
[i
].GetFullPath(); 
 783     return wxEmptyString
; 
 787 //---------------------------------------------------------------------------------- 
 788 // wxHtmlSearchStatus functions 
 789 //---------------------------------------------------------------------------------- 
 791 wxHtmlSearchStatus::wxHtmlSearchStatus(wxHtmlHelpData
* data
, const wxString
& keyword
, 
 792                                        bool case_sensitive
, bool whole_words_only
, 
 793                                        const wxString
& book
) 
 797     wxHtmlBookRecord
* bookr 
= NULL
; 
 798     if (book 
!= wxEmptyString
) 
 800         // we have to search in a specific book. Find it first 
 801         int i
, cnt 
= data
->m_bookRecords
.GetCount(); 
 802         for (i 
= 0; i 
< cnt
; i
++) 
 803             if (data
->m_bookRecords
[i
].GetTitle() == book
) 
 805                 bookr 
= &(data
->m_bookRecords
[i
]); 
 806                 m_CurIndex 
= bookr
->GetContentsStart(); 
 807                 m_MaxIndex 
= bookr
->GetContentsEnd(); 
 810         // check; we won't crash if the book doesn't exist, but it's Bad Anyway. 
 815         // no book specified; search all books 
 817         m_MaxIndex 
= m_Data
->m_contents
.size(); 
 819     m_Engine
.LookFor(keyword
, case_sensitive
, whole_words_only
); 
 820     m_Active 
= (m_CurIndex 
< m_MaxIndex
); 
 823 bool wxHtmlSearchStatus::Search() 
 826     int i 
= m_CurIndex
;  // shortcut 
 832         // sanity check. Illegal use, but we'll try to prevent a crash anyway 
 837     m_Name 
= wxEmptyString
; 
 839     thepage 
= m_Data
->m_contents
[i
].page
; 
 841     m_Active 
= (++m_CurIndex 
< m_MaxIndex
); 
 842     // check if it is same page with different anchor: 
 843     if (!m_LastPage
.empty()) 
 845         const wxChar 
*p1
, *p2
; 
 846         for (p1 
= thepage
.c_str(), p2 
= m_LastPage
.c_str(); 
 847              *p1 
!= 0 && *p1 
!= wxT('#') && *p1 
== *p2
; p1
++, p2
++) {} 
 849         m_LastPage 
= thepage
; 
 851         if (*p1 
== 0 || *p1 
== wxT('#')) 
 854     else m_LastPage 
= thepage
; 
 857     file 
= fsys
.OpenFile(m_Data
->m_contents
[i
].book
->GetFullPath(thepage
)); 
 860         if (m_Engine
.Scan(*file
)) 
 862             m_Name 
= m_Data
->m_contents
[i
].name
; 
 863             m_CurItem 
= &m_Data
->m_contents
[i
]; 
 878 //-------------------------------------------------------------------------------- 
 879 // wxHtmlSearchEngine 
 880 //-------------------------------------------------------------------------------- 
 882 void wxHtmlSearchEngine::LookFor(const wxString
& keyword
, bool case_sensitive
, bool whole_words_only
) 
 884     m_CaseSensitive 
= case_sensitive
; 
 885     m_WholeWords 
= whole_words_only
; 
 888     if (!m_CaseSensitive
) 
 889         m_Keyword
.LowerCase(); 
 893 static inline bool WHITESPACE(wxChar c
) 
 895     return c 
== wxT(' ') || c 
== wxT('\n') || c 
== wxT('\r') || c 
== wxT('\t'); 
 898 // replace continuous spaces by one single space 
 899 static inline wxString 
CompressSpaces(const wxString 
& str
) 
 902     buf
.reserve( str
.size() ); 
 904     bool space_counted 
= false; 
 905     for( const wxChar 
* pstr 
= str
.c_str(); *pstr
; ++pstr 
) 
 908         if( WHITESPACE( ch 
) ) 
 915             space_counted 
= true; 
 919             space_counted 
= false; 
 927 bool wxHtmlSearchEngine::Scan(const wxFSFile
& file
) 
 929     wxASSERT_MSG(!m_Keyword
.empty(), wxT("wxHtmlSearchEngine::LookFor must be called before scanning!")); 
 931     wxHtmlFilterHTML filter
; 
 932     wxString bufStr 
= filter
.ReadFile(file
); 
 934     if (!m_CaseSensitive
) 
 937     {   // remove html tags 
 939         bufStrCopy
.reserve( bufStr
.size() ); 
 940         bool insideTag 
= false; 
 941         for (const wxChar 
* pBufStr 
= bufStr
.c_str(); *pBufStr
; ++pBufStr
) 
 949                     // replace the tag by an empty space 
 955             else if (c 
== wxT('<')) 
 957                 wxChar nextCh 
= *(pBufStr 
+ 1); 
 958                 if (nextCh 
== wxT('/') || !WHITESPACE(nextCh
)) 
 966         bufStr
.swap( bufStrCopy 
); 
 969     wxString keyword 
= m_Keyword
; 
 973         // insert ' ' at the beginning and at the end 
 974         keyword
.insert( 0, wxT(" ") ); 
 975         keyword
.append( wxT(" ") ); 
 976         bufStr
.insert( 0, wxT(" ") ); 
 977         bufStr
.append( wxT(" ") ); 
 980     // remove continuous spaces 
 981     keyword 
= CompressSpaces( keyword 
); 
 982     bufStr 
= CompressSpaces( bufStr 
); 
 984     // finally do the search 
 985     return bufStr
.find( keyword 
) != wxString::npos
;