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