1
//---------------------------------------------------------------------------------------
2
// LenMus Phonascus: The teacher of music
3
// Copyright (c) 2002-2012 LenMus project
5
// This program is free software; you can redistribute it and/or modify it under the
6
// terms of the GNU General Public License as published by the Free Software Foundation,
7
// either version 3 of the License, or (at your option) any later version.
9
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
10
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
// You should have received a copy of the GNU General Public License along with this
14
// program. If not, see <http://www.gnu.org/licenses/>.
16
// For any comment, suggestion or feature request, please contact the manager of
17
// the project at cecilios@users.sourceforge.net
19
//---------------------------------------------------------------------------------------
21
#include "lenmus_book_reader.h"
24
#include <wx/wxprec.h>
27
#include <wx/zipstrm.h>
34
//---------------------------------------------------------------------------------------
35
static int wxHtmlHelpIndexCompareFunc(BookIndexItem **a, BookIndexItem **b)
37
BookIndexItem *ia = *a;
38
BookIndexItem *ib = *b;
45
if (ia->parent == ib->parent)
47
return ia->title.CmpNoCase(ib->title);
49
else if (ia->level == ib->level)
51
return wxHtmlHelpIndexCompareFunc(&ia->parent, &ib->parent);
55
BookIndexItem *ia2 = ia;
56
BookIndexItem *ib2 = ib;
58
while (ia2->level > ib2->level)
62
while (ib2->level > ia2->level)
69
int res = wxHtmlHelpIndexCompareFunc(&ia2, &ib2);
72
else if (ia->level > ib->level)
80
//---------------------------------------------------------------------------------------
82
//---------------------------------------------------------------------------------------
84
BookRecord::BookRecord(const wxString& bookfile, const wxString& basepath,
85
const wxString& title, const wxString& start)
87
m_sBookFile = bookfile;
88
m_sBasePath = basepath;
91
// for debugging, give the contents index obvious default values
92
m_ContentsStart = m_ContentsEnd = -1;
95
//---------------------------------------------------------------------------------------
96
BookRecord::~BookRecord()
100
//---------------------------------------------------------------------------------------
101
wxString BookRecord::GetFullPath(const wxString &page) const
103
// returns full filename of page (which is part of the book),
104
// i.e. with book's basePath prepended. If page is already absolute
105
// path, basePath is _not_ prepended.
109
if (wxIsAbsolutePath(page))
112
return m_sBasePath + page;
115
return wxEmptyString;
118
//---------------------------------------------------------------------------------------
120
//---------------------------------------------------------------------------------------
122
wxString BookIndexItem::GetIndentedName() const
125
for (int i = 1; i < level; i++)
132
//---------------------------------------------------------------------------------------
134
// BookReader object stores and manages all book indexes.
135
// Html pages are not processed. When a page display is requested, the page is
136
// directtly loaded by the wxHtmlWindowd, LoadPage() method.
137
//---------------------------------------------------------------------------------------
138
BookReader::BookReader()
140
m_pParser = LENMUS_NEW XmlParser();
143
//---------------------------------------------------------------------------------------
144
BookReader::~BookReader()
148
for(i = m_bookRecords.GetCount(); i > 0; i--) {
149
delete m_bookRecords[i-1];
150
m_bookRecords.RemoveAt(i-1);
152
for (i = m_index.GetCount(); i > 0; i--) {
154
m_index.RemoveAt(i-1);
156
for(i = m_contents.GetCount(); i > 0; i--) {
157
delete m_contents[i-1];
158
m_contents.RemoveAt(i-1);
160
for(i = m_pagelist.GetCount(); i > 0; i--) {
161
delete m_pagelist[i-1];
162
m_pagelist.RemoveAt(i-1);
167
//---------------------------------------------------------------------------------------
168
void BookReader::SetTempDir(const wxString& path)
174
if (wxIsAbsolutePath(path)) m_tempPath = path;
175
else m_tempPath = wxGetCwd() + _T("/") + path;
177
if (m_tempPath[m_tempPath.length() - 1] != _T('/'))
178
m_tempPath << _T('/');
182
//---------------------------------------------------------------------------------------
183
bool BookReader::AddBook(const wxFileName& oFilename)
185
//Reads a book (either a .lmb or .toc file) and loads its content
186
//Returns true if success.
188
if (oFilename.GetExt() == _T("lmb")) {
189
//add html page names to the pagelist table
190
AddBookPagesToList(oFilename);
193
// Process the TOC file (.toc)
194
BookRecord* pBookr = ProcessTOCFile(oFilename);
196
return false; //error
199
// process an optional index file
200
wxFileName* pFN = LENMUS_NEW wxFileName(oFilename);
201
pFN->SetExt(_T("idx"));
202
bool fSuccess = true;
203
if (pFN->FileExists())
204
fSuccess = ProcessIndexFile(*pFN, pBookr);
210
//---------------------------------------------------------------------------------------
211
bool BookReader::AddBookPagesToList(const wxFileName& oFilename)
213
// Returns true if error.
214
// wxLogMessage(_T("[BookReader::AddBookPagesToList] starting"));
217
wxString sBookPath = oFilename.GetFullPath();
218
wxFFileInputStream in(sBookPath);
219
wxZipInputStream zip(in);
221
wxLogMessage(_T("[BookReader::AddBookPagesToList] Loading eBook. Error: can not open file '%s'."),
226
// loop to get all files
227
wxZipEntry* pEntry = zip.GetNextEntry();
231
wxString sPageName = pEntry->GetName();
232
if (sPageName.Find(_T(".lms")) != wxNOT_FOUND) {
233
//add entry to pagelist
234
// wxLogMessage(_T("[BookReader::AddBookPagesToList] Adding page '%s'"), sPageName.c_str());
235
lmPageIndexItem *pItem = LENMUS_NEW lmPageIndexItem();
236
pItem->page = sPageName;
237
pItem->book = sBookPath;
238
m_pagelist.Add(pItem);
240
delete pEntry; //we have ownership of entry object
241
pEntry = zip.GetNextEntry();
244
return false; //no error
248
//---------------------------------------------------------------------------------------
249
bool BookReader::ProcessIndexFile(const wxFileName& oFilename, BookRecord* pBookr)
251
// Returns true if success.
254
// wxLogMessage(_T("[BookReader::ProcessIndexFile] Processing file %s"),
255
// oFilename.GetFullPath().c_str() );
257
wxString sTitle = _T(""),
258
sDefaultPage = _T(""),
259
sContentsFile = _T(""),
262
// load the XML file as tree of nodes
264
if (!xdoc.Load(oFilename.GetFullPath()) ) {
265
wxLogMessage(_T("[BookReader::ProcessIndexFile] Loading eBook. Error parsing index file %s"),
266
oFilename.GetFullPath().c_str() );
267
return false; //error
270
//Verify type of document. Must be <BookIndex>
271
wxXmlNode *pNode = xdoc.GetRoot();
272
wxString sTag = _T("BookIndex");
273
wxString sElement = pNode->GetName();
274
if (sElement != sTag) {
275
wxLogMessage(_T("[BookReader::ProcessIndexFile] Loading eBook. Error: First tag is not <%s> but <%s>"),
276
sTag.c_str(), sElement.c_str());
277
return false; //error
280
//process children nodes: <entry>
281
pNode = m_pParser->GetFirstChild(pNode);
282
wxXmlNode* pElement = pNode;
283
sElement = pElement->GetName();
285
if (sElement != sTag) {
286
wxLogMessage(_T("[BookReader::ProcessIndexFile] Loading eBook. Error: Expected tag <%s> but found <%s>"),
287
sTag.c_str(), sElement.c_str());
288
return false; //error
290
ProcessIndexEntries(pElement, pBookr);
293
if (!m_index.empty()) {
294
m_index.Sort(wxHtmlHelpIndexCompareFunc);
301
//---------------------------------------------------------------------------------------
302
void BookReader::ProcessIndexEntries(wxXmlNode* pNode, BookRecord *pBookr)
304
// Parse the index entries and adds its data to the m_index array
305
// pNode points to <entry> node
307
//get first index entry
308
wxXmlNode* pElement = pNode;
309
wxString sTag = _T("entry");
311
if (sTag == pElement->GetName()) {
312
BookIndexItem *pItem = LENMUS_NEW BookIndexItem();
313
pItem->parent = NULL;
314
pItem->level = 1; //todo
315
pItem->id = m_pParser->GetAttribute(pElement, _T("id"));
316
pItem->page = m_pParser->GetAttribute(pElement, _T("page"));
317
pItem->title = m_pParser->GetText(pElement);
318
pItem->titlenum = _T("");
319
pItem->image = _T("");
320
pItem->pBookRecord = pBookr;
325
pNode = m_pParser->GetNextSibling(pNode);
331
//---------------------------------------------------------------------------------------
332
BookRecord* BookReader::ProcessTOCFile(const wxFileName& oFilename)
334
// Returns ptr to created book record if success, NULL if failure
336
// wxLogMessage(_T("[BookReader::ProcessTOCFile] Processing file %s"),
337
// oFilename.GetFullPath().c_str());
339
wxString sTitle = _T(""),
345
// wxXmlDocument::Load(filename) uses a wxTextStreamFile and it doesn't support
346
// virtual files. So, when using LMB files we have to allocate
347
// a wxZipTextStream and pass it to wxXmlDocument::Load(stream)
350
wxString sFullName, sFileName, sPath, sNameExt;
351
if (oFilename.GetExt() == _T("toc"))
353
sFullName = oFilename.GetFullPath();
354
sFileName = oFilename.GetName();
355
sPath = oFilename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR , wxPATH_NATIVE);
356
fOK = xdoc.Load(sFullName);
358
else if (oFilename.GetExt() == _T("lmb"))
360
//lenmus compressed book (zip file)
361
sFileName = oFilename.GetName();
362
sPath = oFilename.GetFullPath() + _T("#zip:");
363
sNameExt = sFileName + _T(".toc");
364
sFullName = sPath + sNameExt;
366
// convert the local name we are looking for into the zip internal format
367
wxString sInternalName = wxZipEntry::GetInternalName( sNameExt );
370
wxFFileInputStream in( oFilename.GetFullPath() );
371
wxZipInputStream zip(in);
373
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: TOC file '%s' not found."),
374
oFilename.GetFullPath().c_str());
375
return (BookRecord*) NULL; //error
378
// call GetNextEntry() until the required internal name is found
379
wxZipEntry* pEntry = (wxZipEntry*)NULL;
381
if (pEntry) delete pEntry; //delete previous entry
382
pEntry = zip.GetNextEntry(); //now we have ownership of object *pEntry
384
while (pEntry && pEntry->GetInternalName() != sInternalName);
387
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: TOC file '%s' not found."),
389
return (BookRecord*) NULL; //error
391
zip.OpenEntry(*pEntry);
392
fOK = xdoc.Load(zip); //asumes utf-8
397
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error in TOC file '%s'. Extension is neither LMB nor TOC."),
398
oFilename.GetFullPath().c_str());
399
return (BookRecord*) NULL; //error
402
// load the XML file as tree of nodes
405
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error parsing TOC file ") + sFullName);
406
return (BookRecord*) NULL; //error
409
//Verify type of document. Must be <BookTOC>
410
wxXmlNode *pNode = xdoc.GetRoot();
411
wxString sTag = _T("lmBookTOC");
412
wxString sElement = pNode->GetName();
413
if (sElement != sTag) {
414
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: First tag is not <%s> but <%s>"),
415
sTag.c_str(), sElement.c_str());
416
return (BookRecord*) NULL; //error
420
pNode = m_pParser->GetFirstChild(pNode);
421
wxXmlNode* pElement = pNode;
422
sElement = pElement->GetName();
424
if (sElement != sTag) {
425
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: Expected tag <%s> but found <%s>"),
426
sTag.c_str(), sElement.c_str());
427
return (BookRecord*) NULL; //error
429
sTitle = m_pParser->GetText(pNode);
431
// next node: coverpage
432
pNode = m_pParser->GetNextSibling(pNode);
434
sElement = pElement->GetName();
435
sTag = _T("coverpage");
436
if (sElement != sTag) {
437
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: Expected tag <%s> but found <%s>"),
438
sTag.c_str(), sElement.c_str());
439
return (BookRecord*) NULL; //error
441
sPage = m_pParser->GetText(pNode);
443
//Create the book record object
445
pBookr = LENMUS_NEW BookRecord(sFileName, sPath, sTitle, sPage);
447
// creates the book entry in the contents table
448
int nContentStart = m_contents.size(); // save the contents index for later
449
BookIndexItem *bookitem = LENMUS_NEW BookIndexItem;
451
bookitem->id = _T("");
452
bookitem->page = sPage;
453
bookitem->title = sTitle;
454
bookitem->titlenum = _T("");
455
bookitem->image = _T("");
456
bookitem->pBookRecord = pBookr;
457
m_contents.Add(bookitem);
459
//process other children nodes: <entry>
460
pNode = m_pParser->GetNextSibling(pNode);
464
sElement = pElement->GetName();
465
if (sElement != sTag) {
466
wxLogMessage(_T("[BookReader::ProcessTOCFile] Loading eBook. Error: Expected tag <%s> but found <%s>"),
467
sTag.c_str(), sElement.c_str());
469
return (BookRecord*) NULL; //error
471
if (!ProcessTOCEntry(pElement, pBookr, 1)) return false; //error
474
pNode = m_pParser->GetNextSibling(pNode);
478
// store the contents range in the book record
479
pBookr->SetContentsRange(nContentStart, m_contents.size());
481
// Add the LENMUS_NEW book record to the table
482
m_bookRecords.Add(pBookr);
484
return pBookr; // no error
487
//---------------------------------------------------------------------------------------
488
bool BookReader::ProcessTOCEntry(wxXmlNode* pNode, BookRecord *pBookr, int nLevel)
490
// Parse one entry. Recursive for sub-entries
491
// Add entry data to the m_contents array
493
//process children nodes
495
wxString sTitle = _T(""),
503
sId = m_pParser->GetAttribute(pNode, _T("id"));
506
pNode = m_pParser->GetFirstChild(pNode);
507
wxXmlNode* pElement = pNode;
508
bool fTitleImage = false; //to control that title or image exists
512
sElement = pElement->GetName();
513
if (sElement == _T("image")) {
514
sImage = m_pParser->GetText(pElement);
517
else if (sElement == _T("title")) {
518
sTitle = m_pParser->GetText(pElement);
520
#ifdef _MBCS //if Win95/98/Me release
521
//change encoding from utf-8 to local encoding
522
wxCSConv conv(_T("utf-8"));
523
sTitle = wxString(sTitle.wc_str(conv), convLocal); //wxConvLocal);
526
else if (sElement == _T("page")) {
527
sPage = m_pParser->GetText(pElement);
529
else if (sElement == _T("titlenum")) {
530
sTitlenum = m_pParser->GetText(pElement);
535
pNode = m_pParser->GetNextSibling(pNode);
539
wxLogMessage(_T("[BookReader::ProcessTOCEntry] Loading eBook. Error: Expected tag <title>/<Image> but none of them found."));
540
return false; //error
543
// create the entry in the contents table
544
BookIndexItem *bookitem = LENMUS_NEW BookIndexItem;
545
bookitem->level = nLevel;
547
bookitem->page = sPage;
548
bookitem->title = sTitle;
549
bookitem->titlenum = sTitlenum;
550
bookitem->image = sImage;
551
bookitem->pBookRecord = pBookr;
552
m_contents.Add(bookitem);
554
//process sub-entries, if exist
556
wxString sTag = _T("entry");
559
sElement = pElement->GetName();
560
if (sElement != sTag) {
561
wxLogMessage(_T("[BookReader::ProcessTOCEntry] Loading eBook. Error: Expected tag <%s> but found <%s>"),
562
sTag.c_str(), sElement.c_str());
563
return false; //error
565
if (!ProcessTOCEntry(pElement, pBookr, nLevel)) return false; //error
568
pNode = m_pParser->GetNextSibling(pNode);
572
return true; // no error
577
//---------------------------------------------------------------------------------------
578
wxString BookReader::FindPageByName(const wxString& x)
581
// - By book filename: i.e. 'SingleExercises.lmb' (returns the cover page)
582
// - By page filename: i.e. 'SingleExercises_ch0.lms'
583
// - By page title: i.e. 'Exercises for aural training'
586
// Returns the url to the page (the full path)
587
// i.e. 'c:\lenmus\books\en\SingleExercises.lmb#zip:SingleExercises_ch0.lms'
593
// 1. try to interpret x as a file name (i.e. 'SingleExercises.lmb')
594
if (x.Right(4) == _T(".lmb")) {
596
int nNumBooks = m_bookRecords.GetCount();
597
for (i = 0; i < nNumBooks; i++)
599
pFile = fsys.OpenFile(m_bookRecords[i]->GetFullPath(x));
601
wxString url = m_bookRecords[i]->GetFullPath(m_bookRecords[i]->GetCoverPage());
608
// 2. Try to interpret x as the filename of a book page (i.e. 'SingleExercises_0.lms')
609
if (x.Right(4) == _T(".lms")) {
610
// Try to find the book page
611
int nNumEntries = m_pagelist.size();
612
for (i = 0; i < nNumEntries; i++)
614
//wxLogMessage(_T("[BookReader::FindPageByName] page %d, name = %s"),
615
// i, (m_pagelist[i]->page).c_str() );
616
if (m_pagelist[i]->page == x)
617
return m_pagelist[i]->GetFullPath();
621
// 3. Try to interpret it as a title, and so try find in toc (i.e. 'Exercises for
622
// aural training'). This is the less secure method as titles can be repeated
623
// in different books. In these cases this will retutn the first one found
624
int nNumEntries = m_contents.size();
625
for (i = 0; i < nNumEntries; i++)
627
if (m_contents[i]->title == x)
628
return m_contents[i]->GetFullPath();
631
// 4. try to find it in index
632
nNumEntries = m_index.size();
633
for (i = 0; i < nNumEntries; i++)
635
if (m_index[i]->title == x)
636
return m_index[i]->GetFullPath();
639
//wxLogMessage(_T("[BookReader::FindPageByName] Page '%s' not found."), x.c_str());
643
//---------------------------------------------------------------------------------------
644
wxString BookReader::FindPageById(int id)
646
//size_t cnt = m_contents.size();
647
//for (size_t i = 0; i < cnt; i++)
649
// if (m_contents[i].id == id)
651
// return m_contents[i].GetFullPath();
659
////---------------------------------------------------------------------------------------
660
//// lmSearchStatus functions
661
////---------------------------------------------------------------------------------------
663
//lmSearchStatus::lmSearchStatus(BookReader* data, const wxString& keyword,
664
// bool case_sensitive, bool whole_words_only,
665
// const wxString& book)
668
// m_Keyword = keyword;
669
// BookRecord* bookr = NULL;
670
// if (book != _T(""))
672
// // we have to search in a specific book. Find it first
673
// int i, cnt = data->m_bookRecords.GetCount();
674
// for (i = 0; i < cnt; i++)
675
// if (data->m_bookRecords[i]->GetTitle() == book)
677
// bookr = data->m_bookRecords[i];
678
// m_CurIndex = bookr->GetContentsStart();
679
// m_MaxIndex = bookr->GetContentsEnd();
682
// // check; we won't crash if the book doesn't exist, but it's Bad Anyway.
687
// // no book specified; search all books
689
// m_MaxIndex = m_Data->m_contents.size();
691
// m_Engine.LookFor(keyword, case_sensitive, whole_words_only);
692
// m_Active = (m_CurIndex < m_MaxIndex);
695
////---------------------------------------------------------------------------------------
696
//bool lmSearchStatus::Search()
699
// //int i = m_CurIndex; // shortcut
700
// //bool found = false;
701
// //wxString thepage;
705
// // // sanity check. Illegal use, but we'll try to prevent a crash anyway
706
// // wxASSERT(m_Active);
710
// //m_Name = _T("");
711
// //m_CurItem = NULL;
712
// //thepage = m_Data->m_contents[i].page;
714
// //m_Active = (++m_CurIndex < m_MaxIndex);
715
// //// check if it is same page with different anchor:
716
// //if (!m_LastPage.empty())
718
// // const wxChar *p1, *p2;
719
// // for (p1 = thepage.c_str(), p2 = m_LastPage.c_str();
720
// // *p1 != 0 && *p1 != _T('#') && *p1 == *p2; p1++, p2++) {}
722
// // m_LastPage = thepage;
724
// // if (*p1 == 0 || *p1 == _T('#'))
727
// //else m_LastPage = thepage;
729
// //wxFileSystem fsys;
730
// //file = fsys.OpenFile(m_Data->m_contents[i].pBookRecord->GetFullPath(thepage));
733
// // if (m_Engine.Scan(*file))
735
// // m_Name = m_Data->m_contents[i].title;
736
// // m_CurItem = &m_Data->m_contents[i];
752
////---------------------------------------------------------------------------------------
753
//// BookSearchEngine
754
////---------------------------------------------------------------------------------------
756
//void BookSearchEngine::LookFor(const wxString& keyword, bool case_sensitive, bool whole_words_only)
758
// m_CaseSensitive = case_sensitive;
759
// m_WholeWords = whole_words_only;
760
// m_Keyword = keyword;
762
// if (!m_CaseSensitive)
763
// m_Keyword.MakeLower();
767
//static inline bool WHITESPACE(wxChar c)
769
// return c == _T(' ') || c == _T('\n') || c == _T('\r') || c == _T('\t');
772
////---------------------------------------------------------------------------------------
773
//bool BookSearchEngine::Scan(const wxFSFile& file)
775
// //wxASSERT_MSG(!m_Keyword.empty(), wxT("BookSearchEngine::LookFor must be called before scanning!"));
778
// //int wrd = m_Keyword.length();
779
// //bool found = false;
780
// //wxHtmlFilterHTML filter;
781
// //wxString tmp = filter.ReadFile(file);
782
// //int lng = tmp.length();
783
// //const wxChar *buf = tmp.c_str();
785
// //if (!m_CaseSensitive)
786
// // tmp.MakeLower();
788
// //const wxChar *kwd = m_Keyword.c_str();
790
// //if (m_WholeWords)
792
// // for (i = 0; i < lng - wrd; i++)
794
// // if (WHITESPACE(buf[i])) continue;
796
// // while ((j < wrd) && (buf[i + j] == kwd[j])) j++;
797
// // if (j == wrd && WHITESPACE(buf[i + j])) { found = true; break; }
803
// // for (i = 0; i < lng - wrd; i++)
806
// // while ((j < wrd) && (buf[i + j] == kwd[j])) j++;
807
// // if (j == wrd) { found = true; break; }