]>
git.saurik.com Git - wxWidgets.git/blob - src/html/chm.cpp
1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: CHM (Help) support for wxHTML
4 // Author: Markus Sinner
5 // Copyright: (c) 2003 Herd Software Development
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 #include "wx/wxprec.h"
25 #include "wx/module.h"
26 #include "wx/filesys.h"
27 #include "wx/mstream.h"
28 #include "wx/wfstream.h"
30 #include "wx/html/forcelnk.h"
31 FORCE_LINK_ME(wxhtml_chm_support
)
33 // ----------------------------------------------------------------------------
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 // ----------------------------------------------------------------------------
44 wxChmTools(const wxFileName
&archive
);
48 /// Generate error-string for error-code
49 static const wxString
ChmErrorMsg(int error
);
51 /// get an array of archive-member-filenames
52 const wxArrayString
*GetFileNames()
57 /// get the name of the archive representated by this class
58 const wxString
GetArchiveName()
63 /// Find a file in the archive
64 const wxString
Find(const wxString
& pattern
,
65 const wxString
& startfrom
= wxEmptyString
);
67 /// Extract a file in the archive into a file
68 size_t Extract(const wxString
& pattern
, const wxString
& filename
);
70 /// check archive for a file
71 bool Contains(const wxString
& pattern
);
73 /// get a string for the last error which occurred
74 const wxString
GetLastErrorMessage();
80 // these vars are used by FindFirst/Next:
81 wxString m_chmFileName
;
82 char *m_chmFileNameANSI
;
84 /// mspack-pointer to mschmd_header
85 struct mschmd_header
*m_archive
;
86 /// mspack-pointer to mschm_decompressor
87 struct mschm_decompressor
*m_decompressor
;
89 /// Array of filenames in archive
90 wxArrayString
* m_fileNames
;
92 /// Internal function to get filepointer
93 struct mschmd_file
*GetMschmdFile(const wxString
& pattern
);
100 * @param archive The filename of the archive to open
102 wxChmTools::wxChmTools(const wxFileName
&archive
)
104 m_chmFileName
= archive
.GetFullPath();
106 wxASSERT_MSG( !m_chmFileName
.empty(), _T("empty archive name") );
109 m_decompressor
= NULL
;
113 struct mschmd_header
*chmh
;
114 struct mschm_decompressor
*chmd
;
115 struct mschmd_file
*file
;
117 // Create decompressor
118 chmd
= mspack_create_chm_decompressor(NULL
);
119 m_decompressor
= (struct mschm_decompressor
*) chmd
;
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
));
126 // Open the archive and store it in class:
127 if ( (chmh
= chmd
->open(chmd
, (char*)m_chmFileNameANSI
)) )
131 // Create Filenamearray
132 m_fileNames
= new wxArrayString
;
134 // Store Filenames in array
135 for (file
= chmh
->files
; file
; file
= file
->next
)
137 m_fileNames
->Add(wxString::FromAscii(file
->filename
));
142 wxLogError(_("Failed to open CHM archive '%s'."),
143 archive
.GetFullPath().c_str());
144 m_lasterror
= (chmd
->last_error(chmd
));
153 wxChmTools::~wxChmTools()
155 struct mschm_decompressor
*chmd
= m_decompressor
;
156 struct mschmd_header
*chmh
= m_archive
;
162 chmd
->close(chmd
, chmh
);
164 free(m_chmFileNameANSI
);
166 // Destroy Decompressor
168 mspack_destroy_chm_decompressor(chmd
);
174 * Checks if the given pattern matches to any
175 * filename stored in archive
177 * @param pattern The filename pattern, may include '*' and/or '?'
178 * @return true, if any file matching pattern has been found,
181 bool wxChmTools::Contains(const wxString
& pattern
)
184 wxString pattern_tmp
= wxString(pattern
).MakeLower();
186 // loop through filearay
187 if ( m_fileNames
&& (count
= m_fileNames
->GetCount()) > 0 )
189 for (int i
= 0; i
< count
; i
++)
191 wxString tmp
= m_fileNames
->Item(i
).MakeLower();
192 if ( tmp
.Matches(pattern_tmp
) || tmp
.Mid(1).Matches(pattern_tmp
))
205 * Finds the next file descibed by a pattern in the archive, starting
206 * the file given by second parameter
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
212 const wxString
wxChmTools::Find(const wxString
& pattern
,
213 const wxString
& startfrom
)
217 wxString
pattern_tmp(pattern
);
218 wxString
startfrom_tmp(startfrom
);
219 pattern_tmp
.MakeLower();
220 startfrom_tmp
.MakeLower();
222 if ( m_fileNames
&& (count
= m_fileNames
->GetCount()) > 0 )
224 for (int i
= 0; i
< count
; i
++)
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
) )
231 if ( tmp
.Matches(pattern_tmp
) ||
232 tmp
.Mid(1).Matches(pattern_tmp
) )
239 return wxEmptyString
;
246 * extracts the first hit of pattern to the given position
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
253 size_t wxChmTools::Extract(const wxString
& pattern
, const wxString
& filename
)
255 struct mschm_decompressor
*d
= m_decompressor
;
256 struct mschmd_header
*h
= m_archive
;
257 struct mschmd_file
*f
;
260 wxString pattern_tmp
= (wxString(pattern
)).MakeLower();
262 for (f
= h
->files
; f
; f
= f
->next
)
264 tmp
= wxString::FromAscii(f
->filename
).MakeLower();
265 if ( tmp
.Matches(pattern_tmp
) ||
266 tmp
.Mid(1).Matches(pattern_tmp
) )
268 // ignore leading '/'
270 (char*)(const char*)filename
.mb_str(wxConvFile
)))
273 m_lasterror
= d
->last_error(d
);
274 wxLogError(_("Could not extract %s into %s: %s"),
275 wxString::FromAscii(f
->filename
).c_str(),
277 ChmErrorMsg(m_lasterror
).c_str());
282 return (size_t) f
->length
;
293 * Find a file by pattern
295 * @param pattern A filename pattern (may contain * and ? chars)
296 * @return A pointer to the file (mschmd_file*)
298 struct mschmd_file
*wxChmTools::GetMschmdFile(const wxString
& pattern_orig
)
300 struct mschmd_file
*f
;
301 struct mschmd_header
*h
= (struct mschmd_header
*) m_archive
;
303 wxString pattern
= wxString(pattern_orig
).MakeLower();
305 for (f
= h
->files
; f
; f
= f
->next
)
307 tmp
= wxString::FromAscii(f
->filename
).MakeLower();
308 if ( tmp
.Matches(pattern
) || tmp
.Mid(1).Matches(pattern
) )
310 // ignore leading '/'
318 const wxString
wxChmTools::GetLastErrorMessage()
320 return ChmErrorMsg(m_lasterror
);
323 const wxString
wxChmTools::ChmErrorMsg(int error
)
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");
352 return _("unknown error");
356 // ---------------------------------------------------------------------------
358 // ---------------------------------------------------------------------------
360 class wxChmInputStream
: public wxInputStream
364 wxChmInputStream(const wxString
& archive
,
365 const wxString
& file
, bool simulate
= false);
369 /// Return the size of the accessed file in archive
370 virtual size_t GetSize() const { return m_size
; }
372 virtual bool Eof() const;
373 /// Set simulation-mode of HHP-File (if non is found)
374 void SimulateHHP(bool sim
) { m_simulateHHP
= sim
; }
377 /// See wxInputStream
378 virtual size_t OnSysRead(void *buffer
, size_t bufsize
);
379 /// See wxInputStream
380 virtual wxFileOffset
OnSysSeek(wxFileOffset seek
, wxSeekMode mode
);
381 /// See wxInputStream
382 virtual wxFileOffset
OnSysTell() const { return m_pos
; }
390 wxInputStream
* m_contentStream
;
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.
398 // locates the file and returns a mspack_file *
399 mspack_file
*LocateFile(wxString filename
);
401 // should store pointer to current file
404 // The Chm-Class for extracting the data
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
417 * if false than class does nothing if it doesnt find .hhp
419 wxChmInputStream::wxChmInputStream(const wxString
& archive
,
420 const wxString
& filename
, bool simulate
)
426 m_contentStream
= NULL
;
427 m_lasterror
= wxSTREAM_NO_ERROR
;
428 m_chm
= new wxChmTools (wxFileName(archive
));
430 m_fileName
= wxString(filename
).MakeLower();
431 m_simulateHHP
= simulate
;
433 if ( !m_chm
->Contains(m_fileName
) )
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
438 if ( m_fileName
.Find(_T(".hhp")) != wxNOT_FOUND
&& m_simulateHHP
)
440 // now we open an hhp-file
445 wxLogError(_("Could not locate file '%s'."), filename
.c_str());
446 m_lasterror
= wxSTREAM_READ_ERROR
;
452 CreateFileStream(m_fileName
);
457 wxChmInputStream::~wxChmInputStream()
461 delete m_contentStream
;
470 bool wxChmInputStream::Eof() const
472 return (m_content
==NULL
||
473 m_contentStream
==NULL
||
474 m_contentStream
->Eof() ||
480 size_t wxChmInputStream::OnSysRead(void *buffer
, size_t bufsize
)
482 if ( m_pos
>= m_size
)
484 m_lasterror
= wxSTREAM_EOF
;
487 m_lasterror
= wxSTREAM_NO_ERROR
;
489 // If the rest to read from the stream is less
490 // than the buffer size, than only read the rest
491 if ( m_pos
+ bufsize
> m_size
)
492 bufsize
= m_size
- m_pos
;
494 m_contentStream
->SeekI(m_pos
);
495 m_contentStream
->Read(buffer
, bufsize
);
497 m_contentStream
->SeekI(m_pos
);
504 wxFileOffset
wxChmInputStream::OnSysSeek(wxFileOffset seek
, wxSeekMode mode
)
506 wxString mode_str
= wxEmptyString
;
508 if ( !m_contentStream
|| m_contentStream
->Eof() )
510 m_lasterror
= wxSTREAM_EOF
;
513 m_lasterror
= wxSTREAM_NO_ERROR
;
515 wxFileOffset nextpos
;
520 nextpos
= seek
+ m_pos
;
526 nextpos
= m_size
- 1 + seek
;
530 break; /* just to fool compiler, never happens */
534 // Set current position on stream
535 m_contentStream
->SeekI(m_pos
);
542 * Help Browser tries to read the contents of the
543 * file by interpreting a .hhp file in the Archiv.
544 * For .chm doesnt include such a file, we need
545 * to rebuild the information based on stored
549 wxChmInputStream::CreateHHPStream()
556 wxMemoryOutputStream
*out
;
559 // Try to open the #SYSTEM-File and create the HHP File out of it
560 // see http://bonedaddy.net/pabs3/chmspec/0.1.2/Internal.html#SYSTEM
561 if ( ! m_chm
->Contains(_T("/#SYSTEM")) )
564 wxLogDebug(_("Archive doesnt contain #SYSTEM file"));
570 file
= wxFileName(_T("/#SYSTEM"));
573 if ( CreateFileStream(_T("/#SYSTEM")) )
575 // New stream for writing a memory area to simulate the
577 out
= new wxMemoryOutputStream();
579 tmp
= "[OPTIONS]\r\n";
580 out
->Write((const void *) tmp
, strlen(tmp
));
586 // use the actual stream for reading
589 /* Now read the contents, and try to get the needed information */
591 // First 4 Bytes are Version information, skip
596 // Read #SYSTEM-Code and length
598 code
= wxUINT16_SWAP_ON_BE( code
) ;
600 len
= wxUINT16_SWAP_ON_BE( len
) ;
607 case 0: // CONTENTS_FILE
608 tmp
= "Contents file=";
611 case 1: // INDEX_FILE
615 case 2: // DEFAULT_TOPIC
616 tmp
= "Default Topic=";
622 // case 6: // COMPILED_FILE
623 // tmp = "Compiled File=";
625 case 7: // COMPILED_FILE
626 tmp
= "Binary Index=YES\r\n";
627 out
->Write( (const void *) tmp
, strlen(tmp
));
630 case 4: // STRUCT SYSTEM INFO
634 char *structptr
= (char*) buf
;
635 // LCID at position 0
636 wxUint32 dummy
= *((wxUint32
*)(structptr
+0)) ;
637 wxUint32 lcid
= wxUINT32_SWAP_ON_BE( dummy
) ;
639 msg
.Printf(_T("Language=0x%X\r\n"),lcid
) ;
640 out
->Write(msg
.c_str() , msg
.Length() ) ;
649 out
->Write((const void *) tmp
, strlen(tmp
));
650 out
->Write(buf
, strlen((char*)buf
));
651 out
->Write("\r\n", 2);
659 // Free the old data which wont be used any more
660 delete m_contentStream
;
664 // Now add entries which are missing
665 if ( !hhc
&& m_chm
->Contains(_T("*.hhc")) )
667 tmp
= "Contents File=*.hhc\r\n";
668 out
->Write((const void *) tmp
, strlen(tmp
));
671 if ( !hhk
&& m_chm
->Contains(_T("*.hhk")) )
673 tmp
= "Index File=*.hhk\r\n";
674 out
->Write((const void *) tmp
, strlen(tmp
));
677 // Now copy the Data from the memory
678 out
->SeekO(0, wxFromEnd
);
679 m_size
= out
->TellO();
680 out
->SeekO(0, wxFromStart
);
681 m_content
= (char *) malloc (m_size
+1);
682 out
->CopyTo(m_content
, m_size
);
683 m_content
[m_size
]='\0';
685 m_contentStream
= new wxMemoryInputStream(m_content
, m_size
);
693 * Creates a Stream pointing to a virtual file in
694 * the current archive
696 bool wxChmInputStream::CreateFileStream(const wxString
& pattern
)
698 wxFileInputStream
* fin
;
699 wxString tmpfile
= wxFileName::CreateTempFileName(_T("chmstrm"), NULL
);
701 if ( tmpfile
.empty() )
703 wxLogError(_("Could not create temporary file '%s'"), tmpfile
.c_str());
707 // try to extract the file
708 if ( m_chm
->Extract(pattern
, tmpfile
) <= 0 )
710 wxLogError(_("Extraction of '%s' into '%s' failed."),
711 pattern
.c_str(), tmpfile
.c_str());
712 if ( wxFileExists(tmpfile
) )
713 wxRemoveFile(tmpfile
);
718 // Open a filestream to extracted file
719 fin
= new wxFileInputStream(tmpfile
);
720 m_size
= fin
->GetSize();
721 m_content
= (char *) malloc(m_size
+1);
722 fin
->Read(m_content
, m_size
);
723 m_content
[m_size
]='\0';
725 wxRemoveFile(tmpfile
);
729 m_contentStream
= new wxMemoryInputStream (m_content
, m_size
);
731 return m_contentStream
->IsOk();
737 // ----------------------------------------------------------------------------
739 // ----------------------------------------------------------------------------
741 class wxChmFSHandler
: public wxFileSystemHandler
744 /// Constructor and Destructor
748 /// Is able to open location?
749 virtual bool CanOpen(const wxString
& location
);
751 virtual wxFSFile
* OpenFile(wxFileSystem
& fs
, const wxString
& location
);
752 /// Find first occurrence of spec
753 virtual wxString
FindFirst(const wxString
& spec
, int flags
= 0);
754 /// Find next occurrence of spec
755 virtual wxString
FindNext();
764 wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler()
767 m_pattern
=wxEmptyString
;
768 m_found
=wxEmptyString
;
772 wxChmFSHandler::~wxChmFSHandler()
778 bool wxChmFSHandler::CanOpen(const wxString
& location
)
780 wxString p
= GetProtocol(location
);
781 return (p
== _T("chm")) &&
782 (GetProtocol(GetLeftLocation(location
)) == _T("file"));
785 wxFSFile
* wxChmFSHandler::OpenFile(wxFileSystem
& WXUNUSED(fs
),
786 const wxString
& location
)
788 wxString right
= GetRightLocation(location
);
789 wxString left
= GetLeftLocation(location
);
795 if ( GetProtocol(left
) != _T("file") )
797 wxLogError(_("CHM handler currently supports only local files!"));
801 // Work around javascript
802 wxString tmp
= wxString(right
);
803 if ( tmp
.MakeLower().Contains(_T("javascipt")) && tmp
.Contains(_T("\'")) )
805 right
= right
.AfterFirst(_T('\'')).BeforeLast(_T('\''));
808 // now work on the right location
809 if (right
.Contains(_T("..")))
811 wxFileName
abs(right
);
812 abs
.MakeAbsolute(_T("/"));
813 right
= abs
.GetFullPath();
816 // a workaround for absolute links to root
817 if ( (index
=right
.Index(_T("//"))) != wxNOT_FOUND
)
819 right
=wxString(right
.Mid(index
+1));
820 wxLogWarning(_("Link contained '//', converted to absolute link."));
823 wxFileName leftFilename
= wxFileSystem::URLToFileName(left
);
825 // Open a stream to read the content of the chm-file
826 s
= new wxChmInputStream(leftFilename
.GetFullPath(), right
, true);
828 wxString mime
= GetMimeTypeFromExt(location
);
832 return new wxFSFile(s
,
833 left
+ _T("#chm:") + right
,
836 wxDateTime(wxFileModificationTime(left
)));
846 * Doku see wxFileSystemHandler
848 wxString
wxChmFSHandler::FindFirst(const wxString
& spec
, int flags
)
850 wxString right
= GetRightLocation(spec
);
851 wxString left
= GetLeftLocation(spec
);
852 wxString nativename
= wxFileSystem::URLToFileName(left
).GetFullPath();
854 if ( GetProtocol(left
) != _T("file") )
856 wxLogError(_("CHM handler currently supports only local files!"));
857 return wxEmptyString
;
860 m_chm
= new wxChmTools(wxFileName(nativename
));
861 m_pattern
= right
.AfterLast(_T('/'));
863 wxString m_found
= m_chm
->Find(m_pattern
);
865 // now fake around hhp-files which are not existing in projects...
866 if (m_found
.empty() &&
867 m_pattern
.Contains(_T(".hhp")) &&
868 !m_pattern
.Contains(_T(".hhp.cached")))
870 m_found
.Printf(_T("%s#chm:%s.hhp"),
871 left
.c_str(), m_pattern
.BeforeLast(_T('.')).c_str());
880 wxString
wxChmFSHandler::FindNext()
882 if (m_pattern
.empty())
883 return wxEmptyString
;
885 return m_chm
->Find(m_pattern
, m_found
);
888 // ---------------------------------------------------------------------------
889 // wxModule to register CHM handler
890 // ---------------------------------------------------------------------------
892 class wxChmSupportModule
: public wxModule
894 DECLARE_DYNAMIC_CLASS(wxChmSupportModule
)
897 virtual bool OnInit()
899 wxFileSystem::AddHandler(new wxChmFSHandler
);
902 virtual void OnExit() {}
906 IMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule
, wxModule
)
908 #endif // wxUSE_LIBMSPACK