1
//--------------------------------------------------------------------------------------
2
// LenMus Phonascus: The teacher of music
3
// Copyright (c) 2002-2007 Cecilio Salmeron
5
// This file was initially is based on file helpdata.h from wxWidgets 2.6.3 project
6
// although now it must be something totally different!!
7
// wxWidgets licence is compatible with GNU GPL.
8
// Author: Harm van der Heijden and Vaclav Slavik
9
// Copyright (c) Harm van der Heijden and Vaclav Slavik
14
// This program is free software; you can redistribute it and/or modify it under the
15
// terms of the GNU General Public License as published by the Free Software Foundation;
16
// either version 2 of the License, or (at your option) any later version.
18
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
19
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
20
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
22
// You should have received a copy of the GNU General Public License along with this
23
// program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
24
// Fifth Floor, Boston, MA 02110-1301, USA.
26
// For any comment, suggestion or feature request, please contact the manager of
27
// the project at cecilios@users.sourceforge.net
29
//-------------------------------------------------------------------------------------
31
#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
32
#pragma implementation "BookData.h"
35
// For compilers that support precompilation, includes "wx.h".
36
#include "wx/wxprec.h"
43
#include "wx/zipstrm.h"
47
// access to paths. Needed only for ByPass code
48
#include "../globals/Paths.h"
49
extern lmPaths* g_pPaths;
52
//in Win95/98/Me release we need to change from utf-8 to local encoding
53
// include TheApp to have access to locale info
54
#include "..\app\TheApp.h"
58
// BookData object stores and manages all book indexes.
59
// Html pages are not processed. When a page display is requested, the page is
60
// directtly loaded by the wxHtmlWindowd, LoadPage() method.
65
static int wxHtmlHelpIndexCompareFunc(lmBookIndexItem **a, lmBookIndexItem **b)
67
lmBookIndexItem *ia = *a;
68
lmBookIndexItem *ib = *b;
75
if (ia->parent == ib->parent)
77
return ia->title.CmpNoCase(ib->title);
79
else if (ia->level == ib->level)
81
return wxHtmlHelpIndexCompareFunc(&ia->parent, &ib->parent);
85
lmBookIndexItem *ia2 = ia;
86
lmBookIndexItem *ib2 = ib;
88
while (ia2->level > ib2->level)
92
while (ib2->level > ia2->level)
99
int res = wxHtmlHelpIndexCompareFunc(&ia2, &ib2);
102
else if (ia->level > ib->level)
110
//-----------------------------------------------------------------------------
112
//-----------------------------------------------------------------------------
114
lmBookRecord::lmBookRecord(const wxString& bookfile, const wxString& basepath,
115
const wxString& title, const wxString& start)
117
m_sBookFile = bookfile;
118
m_sBasePath = basepath;
120
m_sCoverPage = start;
121
// for debugging, give the contents index obvious default values
122
m_ContentsStart = m_ContentsEnd = -1;
125
lmBookRecord::~lmBookRecord()
129
wxString lmBookRecord::GetFullPath(const wxString &page) const
131
// returns full filename of page (which is part of the book),
132
// i.e. with book's basePath prepended. If page is already absolute
133
// path, basePath is _not_ prepended.
135
if (wxIsAbsolutePath(page))
138
return m_sBasePath + page;
141
//-----------------------------------------------------------------------------
143
//-----------------------------------------------------------------------------
145
wxString lmBookIndexItem::GetIndentedName() const
148
for (int i = 1; i < level; i++)
155
//-----------------------------------------------------------------------------
157
//-----------------------------------------------------------------------------
159
IMPLEMENT_DYNAMIC_CLASS(lmBookData, wxObject)
161
lmBookData::lmBookData()
163
m_pParser = new lmXmlParser();
166
lmBookData::~lmBookData()
170
for(i = m_bookRecords.GetCount(); i > 0; i--) {
171
delete m_bookRecords[i-1];
172
m_bookRecords.RemoveAt(i-1);
174
for (i = m_index.GetCount(); i > 0; i--) {
176
m_index.RemoveAt(i-1);
178
for(i = m_contents.GetCount(); i > 0; i--) {
179
delete m_contents[i-1];
180
m_contents.RemoveAt(i-1);
182
for(i = m_pagelist.GetCount(); i > 0; i--) {
183
delete m_pagelist[i-1];
184
m_pagelist.RemoveAt(i-1);
189
void lmBookData::SetTempDir(const wxString& path)
195
if (wxIsAbsolutePath(path)) m_tempPath = path;
196
else m_tempPath = wxGetCwd() + _T("/") + path;
198
if (m_tempPath[m_tempPath.Length() - 1] != _T('/'))
199
m_tempPath << _T('/');
203
bool lmBookData::AddBook(const wxFileName& oFilename)
205
//Reads a book (either a .lmb or .toc file) and loads its content
206
//Returns true if success.
208
if (oFilename.GetExt() == _T("lmb")) {
209
//add html page names to the pagelist table
210
AddBookPagesToList(oFilename);
213
// Process the TOC file (.toc)
214
lmBookRecord* pBookr = ProcessTOCFile(oFilename);
216
return false; //error
219
// process an optional index file
220
wxFileName* pFN = new wxFileName(oFilename);
221
pFN->SetExt(_T("idx"));
222
bool fSuccess = true;
223
if (pFN->FileExists())
224
fSuccess = ProcessIndexFile(*pFN, pBookr);
230
bool lmBookData::AddBookPagesToList(const wxFileName& oFilename)
232
// Returns true if error.
235
wxString sBookPath = oFilename.GetFullPath();
236
wxFFileInputStream in(sBookPath);
237
wxZipInputStream zip(in);
239
wxLogMessage(_T("Loading eBook. Error: can not open file '%s'."), sBookPath);
243
// loop to get all files
244
wxZipEntry* pEntry = zip.GetNextEntry();
248
wxString sPageName = pEntry->GetName();
249
if (sPageName.Find(_T(".htm")) != wxNOT_FOUND) {
250
//add entry to pagelist
251
//wxLogMessage(_T("[lmBookData::AddBookPagesToList] Adding page '%s'"), sPageName);
252
lmPageIndexItem *pItem = new lmPageIndexItem();
253
pItem->page = sPageName;
254
pItem->book = sBookPath;
255
m_pagelist.Add(pItem);
257
delete pEntry; //we have ownership of entry object
258
pEntry = zip.GetNextEntry();
261
return false; //no error
265
bool lmBookData::ProcessIndexFile(const wxFileName& oFilename, lmBookRecord* pBookr)
267
// Returns true if success.
270
wxLogMessage(_T("[lmBookData::ProcessIndexFile] Processing file %s"),
271
oFilename.GetFullPath() );
273
wxString sTitle = wxEmptyString,
274
sDefaultPage = wxEmptyString,
275
sContentsFile = wxEmptyString,
276
sIndexFile = wxEmptyString;
278
// load the XML file as tree of nodes
280
if (!xdoc.Load(oFilename.GetFullPath()) ) {
281
wxLogMessage(_T("Loading eBook. Error parsing index file %s"),
282
oFilename.GetFullPath() );
283
return false; //error
286
//Verify type of document. Must be <lmBookIndex>
287
wxXmlNode *pNode = xdoc.GetRoot();
288
wxString sTag = _T("lmBookIndex");
289
wxString sElement = pNode->GetName();
290
if (sElement != sTag) {
291
wxLogMessage(_T("Loading eBook. Error: First tag is not <%s> but <%s>"),
293
return false; //error
296
//process children nodes: <entry>
297
pNode = m_pParser->GetFirstChild(pNode);
298
wxXmlNode* pElement = pNode;
299
sElement = pElement->GetName();
301
if (sElement != sTag) {
302
wxLogMessage(_T("Loading eBook. Error: Expected tag <%s> but found <%s>"),
304
return false; //error
306
ProcessIndexEntries(pElement, pBookr);
309
if (!m_index.empty()) {
310
m_index.Sort(wxHtmlHelpIndexCompareFunc);
317
void lmBookData::ProcessIndexEntries(wxXmlNode* pNode, lmBookRecord *pBookr)
319
// Parse the index entries and adds its data to the m_index array
320
// pNode points to <entry> node
322
//get first index entry
323
wxXmlNode* pElement = pNode;
324
wxString sTag = _T("entry");
326
if (sTag == pElement->GetName()) {
327
lmBookIndexItem *pItem = new lmBookIndexItem();
328
pItem->parent = NULL;
329
pItem->level = 1; //todo
330
pItem->id = m_pParser->GetAttribute(pElement, _T("id"));
331
pItem->page = m_pParser->GetAttribute(pElement, _T("page"));
332
pItem->title = m_pParser->GetText(pElement);
333
pItem->titlenum = wxEmptyString;
334
pItem->image = wxEmptyString;
335
pItem->pBookRecord = pBookr;
340
pNode = m_pParser->GetNextSibling(pNode);
346
lmBookRecord* lmBookData::ProcessTOCFile(const wxFileName& oFilename)
348
// Returns ptr to created book record if success, NULL if failure
350
//wxLogMessage(_T("[lmBookData::ProcessTOCFile] Processing file %s"),
351
// oFilename.GetFullPath());
353
wxString sTitle = wxEmptyString,
354
sPage = wxEmptyString,
355
sContents = wxEmptyString,
356
sIndex = wxEmptyString;
359
// wxXmlDocument::Load(filename) uses a wxTextStreamFile and it doesn't support
360
// virtual files. So, when using LMB files we have to allocate
361
// a wxZipTextStream and pass it to wxXmlDocument::Load(stream)
364
wxString sFullName, sFileName, sPath, sNameExt;
365
bool fLmbFile = false;
366
if (oFilename.GetExt() == _T("toc"))
368
sFullName = oFilename.GetFullPath();
369
sFileName = oFilename.GetName();
370
sPath = oFilename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR , wxPATH_NATIVE);
371
fOK = xdoc.Load(sFullName);
373
else if (oFilename.GetExt() == _T("lmb"))
375
//lenmus compressed book (zip file)
376
sFileName = oFilename.GetName();
377
sPath = oFilename.GetFullPath() + _T("#zip:");
378
sNameExt = sFileName + _T(".toc");
379
sFullName = sPath + sNameExt;
381
// convert the local name we are looking for into the zip internal format
382
wxString sInternalName = wxZipEntry::GetInternalName( sNameExt );
385
wxFFileInputStream in( oFilename.GetFullPath() );
386
wxZipInputStream zip(in);
388
wxLogMessage(_T("Loading eBook. Error: TOC file '%s' not found."), oFilename.GetFullPath());
389
return (lmBookRecord*) NULL; //error
392
// call GetNextEntry() until the required internal name is found
393
wxZipEntry* pEntry = (wxZipEntry*)NULL;
395
if (pEntry) delete pEntry; //delete previous entry
396
pEntry = zip.GetNextEntry(); //now we have ownership of object *pEntry
398
while (pEntry && pEntry->GetInternalName() != sInternalName);
401
wxLogMessage(_T("Loading eBook. Error: TOC file '%s' not found."), sFullName);
402
return (lmBookRecord*) NULL; //error
404
zip.OpenEntry(*pEntry);
406
fOK = xdoc.Load(zip); //asumes utf-8
411
wxLogMessage(_T("Loading eBook. Error in TOC file '%s'. Extension is neither LMB nor TOC."), oFilename.GetFullPath());
412
return (lmBookRecord*) NULL; //error
415
// load the XML file as tree of nodes
418
wxLogMessage(_T("Loading eBook. Error parsing TOC file ") + sFullName);
419
return (lmBookRecord*) NULL; //error
422
//Verify type of document. Must be <lmBookTOC>
423
wxXmlNode *pNode = xdoc.GetRoot();
424
wxString sTag = _T("lmBookTOC");
425
wxString sElement = pNode->GetName();
426
if (sElement != sTag) {
427
wxLogMessage(_T("Loading eBook. Error: First tag is not <%s> but <%s>"),
429
return (lmBookRecord*) NULL; //error
433
pNode = m_pParser->GetFirstChild(pNode);
434
wxXmlNode* pElement = pNode;
435
sElement = pElement->GetName();
437
if (sElement != sTag) {
438
wxLogMessage(_T("Loading eBook. Error: Expected tag <%s> but found <%s>"),
440
return (lmBookRecord*) NULL; //error
442
sTitle = m_pParser->GetText(pNode);
444
//change encoding from utf-8 to local encoding if Win95/98/Me release
446
wxString sLang = wxGetApp().GetLanguageCanonicalName();
448
if (sLang == _T("en")) {
449
sCharset = _T("iso-8859-1");
451
else if (sLang == _T("es")) {
452
sCharset = _T("iso-8859-1");
454
else if (sLang == _T("fr")) {
455
sCharset = _T("iso-8859-1");
457
else if (sLang == _T("tr")) {
458
sCharset = _T("iso-8859-9"); //cp-1254
463
wxCSConv convLocal(sCharset);
464
wxCSConv conv(_T("utf-8"));
465
sTitle = wxString(sTitle.wc_str(conv), convLocal);
468
// next node: coverpage
469
pNode = m_pParser->GetNextSibling(pNode);
471
sElement = pElement->GetName();
472
sTag = _T("coverpage");
473
if (sElement != sTag) {
474
wxLogMessage(_T("Loading eBook. Error: Expected tag <%s> but found <%s>"),
476
return (lmBookRecord*) NULL; //error
478
sPage = m_pParser->GetText(pNode);
480
//Create the book record object
481
lmBookRecord *pBookr;
482
pBookr = new lmBookRecord(sFileName, sPath, sTitle, sPage);
484
// creates the book entry in the contents table
485
int nContentStart = m_contents.size(); // save the contents index for later
486
lmBookIndexItem *bookitem = new lmBookIndexItem;
488
bookitem->id = wxEmptyString;
489
bookitem->page = sPage;
490
bookitem->title = sTitle;
491
bookitem->titlenum = wxEmptyString;
492
bookitem->image = wxEmptyString;
493
bookitem->pBookRecord = pBookr;
494
m_contents.Add(bookitem);
496
//process other children nodes: <entry>
497
pNode = m_pParser->GetNextSibling(pNode);
501
sElement = pElement->GetName();
502
if (sElement != sTag) {
503
wxLogMessage(_T("Loading eBook. Error: Expected tag <%s> but found <%s>"),
506
return (lmBookRecord*) NULL; //error
508
if (!ProcessTOCEntry(pElement, pBookr, 1)) return false; //error
511
pNode = m_pParser->GetNextSibling(pNode);
515
// store the contents range in the book record
516
pBookr->SetContentsRange(nContentStart, m_contents.size());
518
// Add the new book record to the table
519
m_bookRecords.Add(pBookr);
521
return pBookr; // no error
525
bool lmBookData::ProcessTOCEntry(wxXmlNode* pNode, lmBookRecord *pBookr, int nLevel)
527
// Parse one entry. Recursive for sub-entries
528
// Add entry data to the m_contents array
530
//process children nodes
532
wxString sTitle = wxEmptyString,
533
sPage = wxEmptyString,
534
sName = wxEmptyString,
535
sImage = wxEmptyString,
536
sTitlenum = wxEmptyString,
540
sId = m_pParser->GetAttribute(pNode, _T("id"));
544
//in Win95/98/Me release we need to chnage from utf-8 to local encoding
545
wxString sLang = wxGetApp().GetLanguageCanonicalName();
547
if (sLang == _T("en")) {
548
sCharset = _T("iso-8859-1");
550
else if (sLang == _T("es")) {
551
sCharset = _T("iso-8859-1");
553
else if (sLang == _T("fr")) {
554
sCharset = _T("iso-8859-1");
556
else if (sLang == _T("tr")) {
557
sCharset = _T("iso-8859-9"); //cp-1254
562
wxCSConv convLocal(sCharset);
567
pNode = m_pParser->GetFirstChild(pNode);
568
wxXmlNode* pElement = pNode;
569
bool fTitleImage = false; //to control that title or image exists
573
sElement = pElement->GetName();
574
if (sElement == _T("image")) {
575
sImage = m_pParser->GetText(pElement);
578
else if (sElement == _T("title")) {
579
sTitle = m_pParser->GetText(pElement);
581
#ifdef _MBCS //if Win95/98/Me release
582
//change encoding from utf-8 to local encoding
583
wxCSConv conv(_T("utf-8"));
584
sTitle = wxString(sTitle.wc_str(conv), convLocal); //wxConvLocal);
587
else if (sElement == _T("page")) {
588
sPage = m_pParser->GetText(pElement);
590
else if (sElement == _T("titlenum")) {
591
sTitlenum = m_pParser->GetText(pElement);
596
pNode = m_pParser->GetNextSibling(pNode);
600
wxLogMessage(_T("Loading eBook. Error: Expected tag <title>/<Image> but none of them found."));
601
return false; //error
604
// create the entry in the contents table
605
lmBookIndexItem *bookitem = new lmBookIndexItem;
606
bookitem->level = nLevel;
608
bookitem->page = sPage;
609
bookitem->title = sTitle;
610
bookitem->titlenum = sTitlenum;
611
bookitem->image = sImage;
612
bookitem->pBookRecord = pBookr;
613
m_contents.Add(bookitem);
615
//process sub-entries, if exist
617
wxString sTag = _T("entry");
620
sElement = pElement->GetName();
621
if (sElement != sTag) {
622
wxLogMessage(_T("Loading eBook. Error: Expected tag <%s> but found <%s>"),
624
return false; //error
626
if (!ProcessTOCEntry(pElement, pBookr, nLevel)) return false; //error
629
pNode = m_pParser->GetNextSibling(pNode);
633
return true; // no error
638
wxString lmBookData::FindPageByName(const wxString& x)
641
// - By book filename: i.e. 'SingleExercises.lmb' (returns the cover page)
642
// - By page filename: i.e. 'SingleExercises_ch0.htm'
643
// - By page title: i.e. 'Exercises for aural training'
646
// Returns the url to the page (the full path)
647
// i.e. 'c:\lenmus\books\en\SingleExercises.lmb#zip:SingleExercises_ch0.htm'
653
// 1. try to interpret x as a file name (i.e. 'SingleExercises.lmb')
654
if (x.Right(4) == _T(".lmb")) {
656
int nNumBooks = m_bookRecords.GetCount();
657
for (i = 0; i < nNumBooks; i++)
659
pFile = fsys.OpenFile(m_bookRecords[i]->GetFullPath(x));
661
wxString url = m_bookRecords[i]->GetFullPath(m_bookRecords[i]->GetCoverPage());
668
// 2. Try to interpret x as the filename of a book page (i.e. 'SingleExercises_0.htm')
669
if (x.Right(4) == _T(".htm")) {
670
// Try to find the book page
671
int nNumEntries = m_pagelist.size();
672
for (i = 0; i < nNumEntries; i++)
674
if (m_pagelist[i]->page == x)
675
return m_pagelist[i]->GetFullPath();
679
// 3. Try to interpret it as a title, and so try find in toc (i.e. 'Exercises for
680
// aural training'). This is the less secure method as titles can be repeated
681
// in different books. In these cases this will retutn the first one found
682
int nNumEntries = m_contents.size();
683
for (i = 0; i < nNumEntries; i++)
685
if (m_contents[i]->title == x)
686
return m_contents[i]->GetFullPath();
689
// 4. try to find it in index
690
nNumEntries = m_index.size();
691
for (i = 0; i < nNumEntries; i++)
693
if (m_index[i]->title == x)
694
return m_index[i]->GetFullPath();
697
wxLogMessage(_T("[lmBookData::FindPageByName] Page '%s' not found."), x);
698
return wxEmptyString;
701
wxString lmBookData::FindPageById(int id)
703
//size_t cnt = m_contents.size();
704
//for (size_t i = 0; i < cnt; i++)
706
// if (m_contents[i].id == id)
708
// return m_contents[i].GetFullPath();
712
return wxEmptyString;
716
//----------------------------------------------------------------------------------
717
// lmSearchStatus functions
718
//----------------------------------------------------------------------------------
720
lmSearchStatus::lmSearchStatus(lmBookData* data, const wxString& keyword,
721
bool case_sensitive, bool whole_words_only,
722
const wxString& book)
726
lmBookRecord* bookr = NULL;
727
if (book != wxEmptyString)
729
// we have to search in a specific book. Find it first
730
int i, cnt = data->m_bookRecords.GetCount();
731
for (i = 0; i < cnt; i++)
732
if (data->m_bookRecords[i]->GetTitle() == book)
734
bookr = data->m_bookRecords[i];
735
m_CurIndex = bookr->GetContentsStart();
736
m_MaxIndex = bookr->GetContentsEnd();
739
// check; we won't crash if the book doesn't exist, but it's Bad Anyway.
744
// no book specified; search all books
746
m_MaxIndex = m_Data->m_contents.size();
748
m_Engine.LookFor(keyword, case_sensitive, whole_words_only);
749
m_Active = (m_CurIndex < m_MaxIndex);
752
bool lmSearchStatus::Search()
755
//int i = m_CurIndex; // shortcut
756
//bool found = false;
761
// // sanity check. Illegal use, but we'll try to prevent a crash anyway
762
// wxASSERT(m_Active);
766
//m_Name = wxEmptyString;
768
//thepage = m_Data->m_contents[i].page;
770
//m_Active = (++m_CurIndex < m_MaxIndex);
771
//// check if it is same page with different anchor:
772
//if (!m_LastPage.empty())
774
// const wxChar *p1, *p2;
775
// for (p1 = thepage.c_str(), p2 = m_LastPage.c_str();
776
// *p1 != 0 && *p1 != _T('#') && *p1 == *p2; p1++, p2++) {}
778
// m_LastPage = thepage;
780
// if (*p1 == 0 || *p1 == _T('#'))
783
//else m_LastPage = thepage;
786
//file = fsys.OpenFile(m_Data->m_contents[i].pBookRecord->GetFullPath(thepage));
789
// if (m_Engine.Scan(*file))
791
// m_Name = m_Data->m_contents[i].title;
792
// m_CurItem = &m_Data->m_contents[i];
808
//--------------------------------------------------------------------------------
809
// lmBookSearchEngine
810
//--------------------------------------------------------------------------------
812
void lmBookSearchEngine::LookFor(const wxString& keyword, bool case_sensitive, bool whole_words_only)
814
m_CaseSensitive = case_sensitive;
815
m_WholeWords = whole_words_only;
818
if (!m_CaseSensitive)
819
m_Keyword.LowerCase();
823
static inline bool WHITESPACE(wxChar c)
825
return c == _T(' ') || c == _T('\n') || c == _T('\r') || c == _T('\t');
828
bool lmBookSearchEngine::Scan(const wxFSFile& file)
830
//wxASSERT_MSG(!m_Keyword.empty(), wxT("lmBookSearchEngine::LookFor must be called before scanning!"));
833
//int wrd = m_Keyword.Length();
834
//bool found = false;
835
//wxHtmlFilterHTML filter;
836
//wxString tmp = filter.ReadFile(file);
837
//int lng = tmp.length();
838
//const wxChar *buf = tmp.c_str();
840
//if (!m_CaseSensitive)
843
//const wxChar *kwd = m_Keyword.c_str();
847
// for (i = 0; i < lng - wrd; i++)
849
// if (WHITESPACE(buf[i])) continue;
851
// while ((j < wrd) && (buf[i + j] == kwd[j])) j++;
852
// if (j == wrd && WHITESPACE(buf[i + j])) { found = true; break; }
858
// for (i = 0; i < lng - wrd; i++)
861
// while ((j < wrd) && (buf[i + j] == kwd[j])) j++;
862
// if (j == wrd) { found = true; break; }