]> git.saurik.com Git - wxWidgets.git/blame - src/html/chm.cpp
missing commit
[wxWidgets.git] / src / html / chm.cpp
CommitLineData
3527f29c 1/////////////////////////////////////////////////////////////////////////////
02761f6c 2// Name: src/html/chm.cpp
3527f29c
VS
3// Purpose: CHM (Help) support for wxHTML
4// Author: Markus Sinner
5// Copyright: (c) 2003 Herd Software Development
6// CVS-ID: $Id$
65571936 7// Licence: wxWindows licence
3527f29c
VS
8/////////////////////////////////////////////////////////////////////////////
9
10#include "wx/wxprec.h"
11
12#ifdef __BORLANDC__
02761f6c 13 #pragma hdrstop
3527f29c
VS
14#endif
15
16#if wxUSE_LIBMSPACK
17
18#include <mspack.h>
19
b4f4d3dd 20#ifndef WX_PRECOMP
02761f6c
WS
21 #include "wx/intl.h"
22 #include "wx/log.h"
23 #include "wx/module.h"
3527f29c
VS
24#endif
25
3527f29c
VS
26#include "wx/filesys.h"
27#include "wx/mstream.h"
28#include "wx/wfstream.h"
29
30#include "wx/html/forcelnk.h"
31FORCE_LINK_ME(wxhtml_chm_support)
32
33// ----------------------------------------------------------------------------
34/// wxChmTools
35/// <p>
36/// this class is used to abstract access to CHM-Archives
37/// with library mspack written by Stuart Caie
38/// http://www.kyz.uklinux.net/libmspack/
39// ----------------------------------------------------------------------------
40class wxChmTools
41{
42public:
43 /// constructor
44 wxChmTools(const wxFileName &archive);
45 /// destructor
46 ~wxChmTools();
47
48 /// Generate error-string for error-code
49 static const wxString ChmErrorMsg(int error);
50
51 /// get an array of archive-member-filenames
52 const wxArrayString *GetFileNames()
53 {
54 return m_fileNames;
55 };
56
57 /// get the name of the archive representated by this class
58 const wxString GetArchiveName()
59 {
60 return m_chmFileName;
61 };
62
63 /// Find a file in the archive
64 const wxString Find(const wxString& pattern,
65 const wxString& startfrom = wxEmptyString);
66
67 /// Extract a file in the archive into a file
68 size_t Extract(const wxString& pattern, const wxString& filename);
69
70 /// check archive for a file
71 bool Contains(const wxString& pattern);
72
3103e8a9 73 /// get a string for the last error which occurred
3527f29c
VS
74 const wxString GetLastErrorMessage();
75
76 /// Last Error
77 int m_lasterror;
78
79private:
80 // these vars are used by FindFirst/Next:
81 wxString m_chmFileName;
82 char *m_chmFileNameANSI;
83
84 /// mspack-pointer to mschmd_header
85 struct mschmd_header *m_archive;
86 /// mspack-pointer to mschm_decompressor
87 struct mschm_decompressor *m_decompressor;
88
89 /// Array of filenames in archive
90 wxArrayString * m_fileNames;
91
92 /// Internal function to get filepointer
93 struct mschmd_file *GetMschmdFile(const wxString& pattern);
94};
95
96
97/***
98 * constructor
99 *
100 * @param archive The filename of the archive to open
101 */
102wxChmTools::wxChmTools(const wxFileName &archive)
103{
104 m_chmFileName = archive.GetFullPath();
105
9a83f860 106 wxASSERT_MSG( !m_chmFileName.empty(), wxT("empty archive name") );
3527f29c
VS
107
108 m_archive = NULL;
109 m_decompressor = NULL;
110 m_fileNames = NULL;
111 m_lasterror = 0;
112
113 struct mschmd_header *chmh;
114 struct mschm_decompressor *chmd;
115 struct mschmd_file *file;
116
117 // Create decompressor
118 chmd = mspack_create_chm_decompressor(NULL);
119 m_decompressor = (struct mschm_decompressor *) chmd;
120
121 // NB: we must make a copy of the string because chmd->open won't call
122 // strdup() [libmspack-20030726], which would cause crashes in
123 // Unicode build when mb_str() returns temporary buffer
124 m_chmFileNameANSI = strdup((const char*)m_chmFileName.mb_str(wxConvFile));
d1da8872 125
3527f29c
VS
126 // Open the archive and store it in class:
127 if ( (chmh = chmd->open(chmd, (char*)m_chmFileNameANSI)) )
128 {
129 m_archive = chmh;
130
131 // Create Filenamearray
132 m_fileNames = new wxArrayString;
133
134 // Store Filenames in array
135 for (file = chmh->files; file; file = file->next)
136 {
137 m_fileNames->Add(wxString::FromAscii(file->filename));
138 }
139 }
140 else
141 {
142 wxLogError(_("Failed to open CHM archive '%s'."),
143 archive.GetFullPath().c_str());
144 m_lasterror = (chmd->last_error(chmd));
145 return;
146 }
147}
148
149
150/***
151 * Destructor
152 */
153wxChmTools::~wxChmTools()
154{
155 struct mschm_decompressor *chmd = m_decompressor;
156 struct mschmd_header *chmh = m_archive;
157
158 delete m_fileNames;
159
160 // Close Archive
161 if (chmh && chmd)
162 chmd->close(chmd, chmh);
163
164 free(m_chmFileNameANSI);
165
166 // Destroy Decompressor
167 if (chmd)
168 mspack_destroy_chm_decompressor(chmd);
169}
170
171
172
173/**
174 * Checks if the given pattern matches to any
175 * filename stored in archive
176 *
177 * @param pattern The filename pattern, may include '*' and/or '?'
178 * @return true, if any file matching pattern has been found,
179 * false if not
180 */
181bool wxChmTools::Contains(const wxString& pattern)
182{
183 int count;
184 wxString pattern_tmp = wxString(pattern).MakeLower();
185
186 // loop through filearay
187 if ( m_fileNames && (count = m_fileNames->GetCount()) > 0 )
188 {
189 for (int i = 0; i < count; i++)
190 {
191 wxString tmp = m_fileNames->Item(i).MakeLower();
192 if ( tmp.Matches(pattern_tmp) || tmp.Mid(1).Matches(pattern_tmp))
193 return true;
194 }
195 }
196
197 return false;
198}
199
200
201
202/**
203 * Find()
204 *
205 * Finds the next file descibed by a pattern in the archive, starting
206 * the file given by second parameter
d1da8872 207 *
3527f29c
VS
208 * @param pattern The file-pattern to search for. May contain '*' and/or '?'
209 * @param startfrom The filename which the search should start after
210 * @returns The full pathname of the found file
211 */
212const wxString wxChmTools::Find(const wxString& pattern,
213 const wxString& startfrom)
214{
215 int count;
216 wxString tmp;
217 wxString pattern_tmp(pattern);
218 wxString startfrom_tmp(startfrom);
219 pattern_tmp.MakeLower();
220 startfrom_tmp.MakeLower();
221
222 if ( m_fileNames && (count = m_fileNames->GetCount()) > 0 )
223 {
224 for (int i = 0; i < count; i++)
225 {
226 tmp = m_fileNames->Item(i).MakeLower();
227 // if we find the string where the search should began
228 if ( tmp.Matches(startfrom_tmp) ||
229 tmp.Mid(1).Matches(startfrom_tmp) )
230 continue;
231 if ( tmp.Matches(pattern_tmp) ||
232 tmp.Mid(1).Matches(pattern_tmp) )
233 {
234 return tmp;
235 }
236 }
237 }
238
239 return wxEmptyString;
240}
241
242
243/**
244 * Extract ()
245 *
246 * extracts the first hit of pattern to the given position
247 *
248 * @param pattern A filename pattern (may contain * and ? chars)
249 * @param filename The FileName where to temporary extract the file to
250 * @return 0 at no file extracted<br>
251 * number of bytes extracted else
252 */
253size_t wxChmTools::Extract(const wxString& pattern, const wxString& filename)
254{
255 struct mschm_decompressor *d = m_decompressor;
256 struct mschmd_header *h = m_archive;
257 struct mschmd_file *f;
258
259 wxString tmp;
260 wxString pattern_tmp = (wxString(pattern)).MakeLower();
261
262 for (f = h->files; f; f = f->next)
263 {
264 tmp = wxString::FromAscii(f->filename).MakeLower();
265 if ( tmp.Matches(pattern_tmp) ||
266 tmp.Mid(1).Matches(pattern_tmp) )
267 {
268 // ignore leading '/'
269 if (d->extract(d, f,
270 (char*)(const char*)filename.mb_str(wxConvFile)))
271 {
272 // Error
273 m_lasterror = d->last_error(d);
274 wxLogError(_("Could not extract %s into %s: %s"),
275 wxString::FromAscii(f->filename).c_str(),
276 filename.c_str(),
277 ChmErrorMsg(m_lasterror).c_str());
278 return 0;
279 }
280 else
281 {
282 return (size_t) f->length;
283 }
284 }
285 }
286
287 return 0;
288}
289
290
291
292/**
293 * Find a file by pattern
294 *
295 * @param pattern A filename pattern (may contain * and ? chars)
296 * @return A pointer to the file (mschmd_file*)
297 */
298struct mschmd_file *wxChmTools::GetMschmdFile(const wxString& pattern_orig)
299{
300 struct mschmd_file *f;
301 struct mschmd_header *h = (struct mschmd_header *) m_archive;
302 wxString tmp;
303 wxString pattern = wxString(pattern_orig).MakeLower();
304
305 for (f = h->files; f; f = f->next)
306 {
307 tmp = wxString::FromAscii(f->filename).MakeLower();
308 if ( tmp.Matches(pattern) || tmp.Mid(1).Matches(pattern) )
309 {
310 // ignore leading '/'
311 return f;
312 }
313 }
314
315 return NULL;
316}
317
318const wxString wxChmTools::GetLastErrorMessage()
319{
320 return ChmErrorMsg(m_lasterror);
321}
322
323const wxString wxChmTools::ChmErrorMsg(int error)
324{
325 switch (error)
326 {
327 case MSPACK_ERR_OK:
328 return _("no error");
329 case MSPACK_ERR_ARGS:
330 return _("bad arguments to library function");
331 case MSPACK_ERR_OPEN:
332 return _("error opening file");
333 case MSPACK_ERR_READ:
334 return _("read error");
335 case MSPACK_ERR_WRITE:
336 return _("write error");
337 case MSPACK_ERR_SEEK:
338 return _("seek error");
339 case MSPACK_ERR_NOMEMORY:
340 return _("out of memory");
341 case MSPACK_ERR_SIGNATURE:
342 return _("bad signature");
343 case MSPACK_ERR_DATAFORMAT:
344 return _("error in data format");
345 case MSPACK_ERR_CHECKSUM:
346 return _("checksum error");
347 case MSPACK_ERR_CRUNCH:
348 return _("compression error");
349 case MSPACK_ERR_DECRUNCH:
350 return _("decompression error");
351 }
352 return _("unknown error");
353}
354
355
356// ---------------------------------------------------------------------------
357/// wxChmInputStream
358// ---------------------------------------------------------------------------
359
360class wxChmInputStream : public wxInputStream
361{
362public:
363 /// Constructor
364 wxChmInputStream(const wxString& archive,
365 const wxString& file, bool simulate = false);
366 /// Destructor
d3c7fc99 367 virtual ~wxChmInputStream();
3527f29c
VS
368
369 /// Return the size of the accessed file in archive
370 virtual size_t GetSize() const { return m_size; }
371 /// End of Stream?
372 virtual bool Eof() const;
373 /// Set simulation-mode of HHP-File (if non is found)
374 void SimulateHHP(bool sim) { m_simulateHHP = sim; }
375
376protected:
377 /// See wxInputStream
378 virtual size_t OnSysRead(void *buffer, size_t bufsize);
379 /// See wxInputStream
30984dea 380 virtual wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode);
3527f29c 381 /// See wxInputStream
30984dea 382 virtual wxFileOffset OnSysTell() const { return m_pos; }
3527f29c
VS
383
384private:
385 size_t m_size;
30984dea 386 wxFileOffset m_pos;
3527f29c
VS
387 bool m_simulateHHP;
388
389 char * m_content;
390 wxInputStream * m_contentStream;
391
392 void CreateHHPStream();
393 bool CreateFileStream(const wxString& pattern);
394 // this void* is handle of archive . I'm sorry it is void and not proper
395 // type but I don't want to make unzip.h header public.
396
397
398 // locates the file and returns a mspack_file *
399 mspack_file *LocateFile(wxString filename);
400
401 // should store pointer to current file
402 mspack_file *m_file;
403
404 // The Chm-Class for extracting the data
405 wxChmTools *m_chm;
406
407 wxString m_fileName;
408};
409
410
411/**
412 * Constructor
413 * @param archive The name of the .chm archive. Remember that archive must
414 * be local file accesible via fopen, fread functions!
415 * @param filename The Name of the file to be extracted from archive
416 * @param simulate if true than class should simulate .HHP-File based on #SYSTEM
90523bdb 417 * if false than class does nothing if it doesn't find .hhp
3527f29c
VS
418 */
419wxChmInputStream::wxChmInputStream(const wxString& archive,
420 const wxString& filename, bool simulate)
421 : wxInputStream()
422{
423 m_pos = 0;
424 m_size = 0;
425 m_content = NULL;
426 m_contentStream = NULL;
427 m_lasterror = wxSTREAM_NO_ERROR;
428 m_chm = new wxChmTools (wxFileName(archive));
429 m_file = NULL;
430 m_fileName = wxString(filename).MakeLower();
431 m_simulateHHP = simulate;
432
433 if ( !m_chm->Contains(m_fileName) )
434 {
435 // if the file could not be located, but was *.hhp, than we create
436 // the content of the hhp-file on the fly and store it for reading
437 // by the application
9a83f860 438 if ( m_fileName.Find(wxT(".hhp")) != wxNOT_FOUND && m_simulateHHP )
3527f29c
VS
439 {
440 // now we open an hhp-file
441 CreateHHPStream();
442 }
443 else
444 {
445 wxLogError(_("Could not locate file '%s'."), filename.c_str());
446 m_lasterror = wxSTREAM_READ_ERROR;
447 return;
448 }
449 }
450 else
451 { // file found
452 CreateFileStream(m_fileName);
453 }
454}
455
456
457wxChmInputStream::~wxChmInputStream()
458{
459 delete m_chm;
460
461 delete m_contentStream;
462
463 if (m_content)
464 {
465 free (m_content);
466 m_content=NULL;
467 }
468}
469
470bool wxChmInputStream::Eof() const
471{
472 return (m_content==NULL ||
473 m_contentStream==NULL ||
474 m_contentStream->Eof() ||
475 m_pos>m_size);
476}
477
478
479
480size_t wxChmInputStream::OnSysRead(void *buffer, size_t bufsize)
481{
482 if ( m_pos >= m_size )
483 {
484 m_lasterror = wxSTREAM_EOF;
485 return 0;
486 }
487 m_lasterror = wxSTREAM_NO_ERROR;
488
489 // If the rest to read from the stream is less
b81e4506 490 // than the buffer size, then only read the rest
3527f29c
VS
491 if ( m_pos + bufsize > m_size )
492 bufsize = m_size - m_pos;
493
b81e4506
FM
494 if (m_contentStream->SeekI(m_pos) == wxInvalidOffset)
495 {
496 m_lasterror = wxSTREAM_EOF;
497 return 0;
498 }
499
500 size_t read = m_contentStream->Read(buffer, bufsize).LastRead();
501 m_pos += read;
502
503 if (m_contentStream->SeekI(m_pos) == wxInvalidOffset)
504 {
505 m_lasterror = wxSTREAM_READ_ERROR;
506 return 0;
507 }
508
509 if (read != bufsize)
510 m_lasterror = m_contentStream->GetLastError();
511
512 return read;
3527f29c
VS
513}
514
515
516
517
30984dea 518wxFileOffset wxChmInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
3527f29c
VS
519{
520 wxString mode_str = wxEmptyString;
521
522 if ( !m_contentStream || m_contentStream->Eof() )
523 {
524 m_lasterror = wxSTREAM_EOF;
525 return 0;
526 }
527 m_lasterror = wxSTREAM_NO_ERROR;
528
30984dea 529 wxFileOffset nextpos;
3527f29c
VS
530
531 switch ( mode )
532 {
533 case wxFromCurrent:
534 nextpos = seek + m_pos;
535 break;
536 case wxFromStart:
537 nextpos = seek;
538 break;
539 case wxFromEnd:
540 nextpos = m_size - 1 + seek;
541 break;
542 default:
543 nextpos = m_pos;
544 break; /* just to fool compiler, never happens */
545 }
546 m_pos=nextpos;
547
548 // Set current position on stream
549 m_contentStream->SeekI(m_pos);
550 return m_pos;
551}
552
553
554
555/**
556 * Help Browser tries to read the contents of the
557 * file by interpreting a .hhp file in the Archiv.
90523bdb 558 * For .chm doesn't include such a file, we need
3527f29c
VS
559 * to rebuild the information based on stored
560 * system-files.
561 */
562void
563wxChmInputStream::CreateHHPStream()
564{
565 wxFileName file;
3527f29c
VS
566 bool hhc = false;
567 bool hhk = false;
568 wxInputStream *i;
569 wxMemoryOutputStream *out;
570 const char *tmp;
571
572 // Try to open the #SYSTEM-File and create the HHP File out of it
05d89f2c 573 // see http://bonedaddy.net/pabs3/chmspec/0.1.2/Internal.html#SYSTEM
9a83f860 574 if ( ! m_chm->Contains(wxT("/#SYSTEM")) )
3527f29c
VS
575 {
576#ifdef DEBUG
90523bdb 577 wxLogDebug("Archive doesn't contain #SYSTEM file");
3527f29c
VS
578#endif
579 return;
580 }
581 else
582 {
9a83f860 583 file = wxFileName(wxT("/#SYSTEM"));
3527f29c
VS
584 }
585
9a83f860 586 if ( CreateFileStream(wxT("/#SYSTEM")) )
3527f29c
VS
587 {
588 // New stream for writing a memory area to simulate the
589 // .hhp-file
590 out = new wxMemoryOutputStream();
591
592 tmp = "[OPTIONS]\r\n";
593 out->Write((const void *) tmp, strlen(tmp));
594
595 wxUint16 code;
596 wxUint16 len;
597 void *buf;
598
599 // use the actual stream for reading
600 i = m_contentStream;
601
602 /* Now read the contents, and try to get the needed information */
603
604 // First 4 Bytes are Version information, skip
605 i->SeekI(4);
606
607 while (!i->Eof())
608 {
609 // Read #SYSTEM-Code and length
610 i->Read(&code, 2);
fbd35620 611 code = wxUINT16_SWAP_ON_BE( code ) ;
3527f29c 612 i->Read(&len, 2);
fbd35620 613 len = wxUINT16_SWAP_ON_BE( len ) ;
3527f29c
VS
614 // data
615 buf = malloc(len);
616 i->Read(buf, len);
617
618 switch (code)
619 {
620 case 0: // CONTENTS_FILE
348a9a6f
MW
621 if (len)
622 {
623 tmp = "Contents file=";
624 hhc=true;
625 }
3527f29c
VS
626 break;
627 case 1: // INDEX_FILE
628 tmp = "Index file=";
629 hhk = true;
630 break;
631 case 2: // DEFAULT_TOPIC
632 tmp = "Default Topic=";
3527f29c
VS
633 break;
634 case 3: // TITLE
635 tmp = "Title=";
636 break;
637 // case 6: // COMPILED_FILE
638 // tmp = "Compiled File=";
639 // break;
640 case 7: // COMPILED_FILE
641 tmp = "Binary Index=YES\r\n";
642 out->Write( (const void *) tmp, strlen(tmp));
643 tmp = NULL;
644 break;
fbd35620
SC
645 case 4: // STRUCT SYSTEM INFO
646 tmp = NULL ;
647 if ( len >= 28 )
648 {
649 char *structptr = (char*) buf ;
650 // LCID at position 0
651 wxUint32 dummy = *((wxUint32 *)(structptr+0)) ;
652 wxUint32 lcid = wxUINT32_SWAP_ON_BE( dummy ) ;
348a9a6f
MW
653 char msg[64];
654 int len = sprintf(msg, "Language=0x%X\r\n", lcid) ;
655 if (len > 0)
656 out->Write(msg, len) ;
fbd35620
SC
657 }
658 break ;
3527f29c
VS
659 default:
660 tmp=NULL;
661 }
662
663 if (tmp)
664 {
665 out->Write((const void *) tmp, strlen(tmp));
666 out->Write(buf, strlen((char*)buf));
667 out->Write("\r\n", 2);
668 }
669
670 free(buf);
671 buf=NULL;
672 }
673
674
675 // Free the old data which wont be used any more
676 delete m_contentStream;
677 if (m_content)
678 free (m_content);
679
680 // Now add entries which are missing
9a83f860 681 if ( !hhc && m_chm->Contains(wxT("*.hhc")) )
3527f29c
VS
682 {
683 tmp = "Contents File=*.hhc\r\n";
684 out->Write((const void *) tmp, strlen(tmp));
685 }
686
9a83f860 687 if ( !hhk && m_chm->Contains(wxT("*.hhk")) )
3527f29c
VS
688 {
689 tmp = "Index File=*.hhk\r\n";
690 out->Write((const void *) tmp, strlen(tmp));
691 }
02761f6c 692
3527f29c
VS
693 // Now copy the Data from the memory
694 out->SeekO(0, wxFromEnd);
695 m_size = out->TellO();
696 out->SeekO(0, wxFromStart);
697 m_content = (char *) malloc (m_size+1);
698 out->CopyTo(m_content, m_size);
699 m_content[m_size]='\0';
700 m_size++;
701 m_contentStream = new wxMemoryInputStream(m_content, m_size);
702
703 delete out;
704 }
705}
706
707
708/**
709 * Creates a Stream pointing to a virtual file in
710 * the current archive
711 */
712bool wxChmInputStream::CreateFileStream(const wxString& pattern)
713{
714 wxFileInputStream * fin;
9a83f860 715 wxString tmpfile = wxFileName::CreateTempFileName(wxT("chmstrm"));
3527f29c
VS
716
717 if ( tmpfile.empty() )
718 {
719 wxLogError(_("Could not create temporary file '%s'"), tmpfile.c_str());
720 return false;
721 }
722
723 // try to extract the file
724 if ( m_chm->Extract(pattern, tmpfile) <= 0 )
725 {
726 wxLogError(_("Extraction of '%s' into '%s' failed."),
727 pattern.c_str(), tmpfile.c_str());
728 if ( wxFileExists(tmpfile) )
729 wxRemoveFile(tmpfile);
730 return false;
731 }
732 else
733 {
734 // Open a filestream to extracted file
735 fin = new wxFileInputStream(tmpfile);
9690b006
FM
736 if (!fin->IsOk())
737 return false;
738
3527f29c
VS
739 m_size = fin->GetSize();
740 m_content = (char *) malloc(m_size+1);
741 fin->Read(m_content, m_size);
742 m_content[m_size]='\0';
743
744 wxRemoveFile(tmpfile);
745
746 delete fin;
747
748 m_contentStream = new wxMemoryInputStream (m_content, m_size);
749
750 return m_contentStream->IsOk();
751 }
752}
753
754
755
756// ----------------------------------------------------------------------------
757// wxChmFSHandler
758// ----------------------------------------------------------------------------
759
760class wxChmFSHandler : public wxFileSystemHandler
761{
762public:
763 /// Constructor and Destructor
764 wxChmFSHandler();
d3c7fc99 765 virtual ~wxChmFSHandler();
3527f29c
VS
766
767 /// Is able to open location?
768 virtual bool CanOpen(const wxString& location);
769 /// Open a file
770 virtual wxFSFile* OpenFile(wxFileSystem& fs, const wxString& location);
3103e8a9 771 /// Find first occurrence of spec
3527f29c 772 virtual wxString FindFirst(const wxString& spec, int flags = 0);
3103e8a9 773 /// Find next occurrence of spec
3527f29c
VS
774 virtual wxString FindNext();
775
776private:
777 int m_lasterror;
778 wxString m_pattern;
779 wxString m_found;
780 wxChmTools * m_chm;
781};
782
783wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler()
784{
785 m_lasterror=0;
786 m_pattern=wxEmptyString;
787 m_found=wxEmptyString;
788 m_chm=NULL;
789}
790
791wxChmFSHandler::~wxChmFSHandler()
792{
793 if (m_chm)
794 delete m_chm;
795}
796
797bool wxChmFSHandler::CanOpen(const wxString& location)
798{
799 wxString p = GetProtocol(location);
9a83f860
VZ
800 return (p == wxT("chm")) &&
801 (GetProtocol(GetLeftLocation(location)) == wxT("file"));
3527f29c
VS
802}
803
804wxFSFile* wxChmFSHandler::OpenFile(wxFileSystem& WXUNUSED(fs),
805 const wxString& location)
806{
807 wxString right = GetRightLocation(location);
808 wxString left = GetLeftLocation(location);
809
810 wxInputStream *s;
811
812 int index;
813
9a83f860 814 if ( GetProtocol(left) != wxT("file") )
3527f29c
VS
815 {
816 wxLogError(_("CHM handler currently supports only local files!"));
817 return NULL;
818 }
819
820 // Work around javascript
821 wxString tmp = wxString(right);
9a83f860 822 if ( tmp.MakeLower().Contains(wxT("javascipt")) && tmp.Contains(wxT("\'")) )
3527f29c 823 {
9a83f860 824 right = right.AfterFirst(wxT('\'')).BeforeLast(wxT('\''));
3527f29c
VS
825 }
826
827 // now work on the right location
9a83f860 828 if (right.Contains(wxT("..")))
3527f29c
VS
829 {
830 wxFileName abs(right);
9a83f860 831 abs.MakeAbsolute(wxT("/"));
3527f29c
VS
832 right = abs.GetFullPath();
833 }
834
835 // a workaround for absolute links to root
9a83f860 836 if ( (index=right.Index(wxT("//"))) != wxNOT_FOUND )
3527f29c
VS
837 {
838 right=wxString(right.Mid(index+1));
839 wxLogWarning(_("Link contained '//', converted to absolute link."));
840 }
841
842 wxFileName leftFilename = wxFileSystem::URLToFileName(left);
15552854
MW
843 if (!leftFilename.FileExists())
844 return NULL;
3527f29c
VS
845
846 // Open a stream to read the content of the chm-file
847 s = new wxChmInputStream(leftFilename.GetFullPath(), right, true);
848
3527f29c
VS
849 if ( s )
850 {
851 return new wxFSFile(s,
9a83f860 852 left + wxT("#chm:") + right,
69cce151 853 wxEmptyString,
3527f29c 854 GetAnchor(location),
348a9a6f 855 wxDateTime(leftFilename.GetModificationTime()));
3527f29c
VS
856 }
857
858 delete s;
859 return NULL;
860}
861
862
863
864/**
865 * Doku see wxFileSystemHandler
866 */
867wxString wxChmFSHandler::FindFirst(const wxString& spec, int flags)
868{
869 wxString right = GetRightLocation(spec);
870 wxString left = GetLeftLocation(spec);
871 wxString nativename = wxFileSystem::URLToFileName(left).GetFullPath();
872
9a83f860 873 if ( GetProtocol(left) != wxT("file") )
3527f29c
VS
874 {
875 wxLogError(_("CHM handler currently supports only local files!"));
876 return wxEmptyString;
877 }
878
879 m_chm = new wxChmTools(wxFileName(nativename));
9a83f860 880 m_pattern = right.AfterLast(wxT('/'));
3527f29c
VS
881
882 wxString m_found = m_chm->Find(m_pattern);
883
884 // now fake around hhp-files which are not existing in projects...
885 if (m_found.empty() &&
9a83f860
VZ
886 m_pattern.Contains(wxT(".hhp")) &&
887 !m_pattern.Contains(wxT(".hhp.cached")))
3527f29c 888 {
9a83f860
VZ
889 m_found.Printf(wxT("%s#chm:%s.hhp"),
890 left.c_str(), m_pattern.BeforeLast(wxT('.')).c_str());
3527f29c
VS
891 }
892
893 return m_found;
894
895}
896
897
898
899wxString wxChmFSHandler::FindNext()
900{
901 if (m_pattern.empty())
902 return wxEmptyString;
903 else
904 return m_chm->Find(m_pattern, m_found);
905}
906
907// ---------------------------------------------------------------------------
908// wxModule to register CHM handler
909// ---------------------------------------------------------------------------
910
911class wxChmSupportModule : public wxModule
912{
913 DECLARE_DYNAMIC_CLASS(wxChmSupportModule)
914
915public:
916 virtual bool OnInit()
917 {
918 wxFileSystem::AddHandler(new wxChmFSHandler);
919 return true;
920 }
921 virtual void OnExit() {}
922}
923;
924
925IMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule, wxModule)
926
927#endif // wxUSE_LIBMSPACK