]>
git.saurik.com Git - wxWidgets.git/blob - 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
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 #include "wx/wxprec.h"
23 #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(), wxT("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);
367 virtual ~wxChmInputStream();
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 doesn't 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(wxT(".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, then only read the rest
491 if ( m_pos
+ bufsize
> m_size
)
492 bufsize
= m_size
- m_pos
;
494 if (m_contentStream
->SeekI(m_pos
) == wxInvalidOffset
)
496 m_lasterror
= wxSTREAM_EOF
;
500 size_t read
= m_contentStream
->Read(buffer
, bufsize
).LastRead();
503 if (m_contentStream
->SeekI(m_pos
) == wxInvalidOffset
)
505 m_lasterror
= wxSTREAM_READ_ERROR
;
510 m_lasterror
= m_contentStream
->GetLastError();
518 wxFileOffset
wxChmInputStream::OnSysSeek(wxFileOffset seek
, wxSeekMode mode
)
520 wxString mode_str
= wxEmptyString
;
522 if ( !m_contentStream
|| m_contentStream
->Eof() )
524 m_lasterror
= wxSTREAM_EOF
;
527 m_lasterror
= wxSTREAM_NO_ERROR
;
529 wxFileOffset nextpos
;
534 nextpos
= seek
+ m_pos
;
540 nextpos
= m_size
- 1 + seek
;
544 break; /* just to fool compiler, never happens */
548 // Set current position on stream
549 m_contentStream
->SeekI(m_pos
);
556 * Help Browser tries to read the contents of the
557 * file by interpreting a .hhp file in the Archiv.
558 * For .chm doesn't include such a file, we need
559 * to rebuild the information based on stored
563 wxChmInputStream::CreateHHPStream()
570 wxMemoryOutputStream
*out
;
573 // Try to open the #SYSTEM-File and create the HHP File out of it
574 // see http://bonedaddy.net/pabs3/chmspec/0.1.2/Internal.html#SYSTEM
575 if ( ! m_chm
->Contains(wxT("/#SYSTEM")) )
578 wxLogDebug("Archive doesn't contain #SYSTEM file");
584 file
= wxFileName(wxT("/#SYSTEM"));
587 if ( CreateFileStream(wxT("/#SYSTEM")) )
589 // New stream for writing a memory area to simulate the
591 out
= new wxMemoryOutputStream();
593 tmp
= "[OPTIONS]\r\n";
594 out
->Write((const void *) tmp
, strlen(tmp
));
600 // use the actual stream for reading
603 /* Now read the contents, and try to get the needed information */
605 // First 4 Bytes are Version information, skip
610 // Read #SYSTEM-Code and length
612 code
= wxUINT16_SWAP_ON_BE( code
) ;
614 len
= wxUINT16_SWAP_ON_BE( len
) ;
621 case 0: // CONTENTS_FILE
622 tmp
= "Contents file=";
625 case 1: // INDEX_FILE
629 case 2: // DEFAULT_TOPIC
630 tmp
= "Default Topic=";
636 // case 6: // COMPILED_FILE
637 // tmp = "Compiled File=";
639 case 7: // COMPILED_FILE
640 tmp
= "Binary Index=YES\r\n";
641 out
->Write( (const void *) tmp
, strlen(tmp
));
644 case 4: // STRUCT SYSTEM INFO
648 char *structptr
= (char*) buf
;
649 // LCID at position 0
650 wxUint32 dummy
= *((wxUint32
*)(structptr
+0)) ;
651 wxUint32 lcid
= wxUINT32_SWAP_ON_BE( dummy
) ;
653 msg
.Printf(wxT("Language=0x%X\r\n"),lcid
) ;
654 out
->Write(msg
.c_str() , msg
.length() ) ;
663 out
->Write((const void *) tmp
, strlen(tmp
));
664 out
->Write(buf
, strlen((char*)buf
));
665 out
->Write("\r\n", 2);
673 // Free the old data which wont be used any more
674 delete m_contentStream
;
678 // Now add entries which are missing
679 if ( !hhc
&& m_chm
->Contains(wxT("*.hhc")) )
681 tmp
= "Contents File=*.hhc\r\n";
682 out
->Write((const void *) tmp
, strlen(tmp
));
685 if ( !hhk
&& m_chm
->Contains(wxT("*.hhk")) )
687 tmp
= "Index File=*.hhk\r\n";
688 out
->Write((const void *) tmp
, strlen(tmp
));
691 // Now copy the Data from the memory
692 out
->SeekO(0, wxFromEnd
);
693 m_size
= out
->TellO();
694 out
->SeekO(0, wxFromStart
);
695 m_content
= (char *) malloc (m_size
+1);
696 out
->CopyTo(m_content
, m_size
);
697 m_content
[m_size
]='\0';
699 m_contentStream
= new wxMemoryInputStream(m_content
, m_size
);
707 * Creates a Stream pointing to a virtual file in
708 * the current archive
710 bool wxChmInputStream::CreateFileStream(const wxString
& pattern
)
712 wxFileInputStream
* fin
;
713 wxString tmpfile
= wxFileName::CreateTempFileName(wxT("chmstrm"));
715 if ( tmpfile
.empty() )
717 wxLogError(_("Could not create temporary file '%s'"), tmpfile
.c_str());
721 // try to extract the file
722 if ( m_chm
->Extract(pattern
, tmpfile
) <= 0 )
724 wxLogError(_("Extraction of '%s' into '%s' failed."),
725 pattern
.c_str(), tmpfile
.c_str());
726 if ( wxFileExists(tmpfile
) )
727 wxRemoveFile(tmpfile
);
732 // Open a filestream to extracted file
733 fin
= new wxFileInputStream(tmpfile
);
737 m_size
= fin
->GetSize();
738 m_content
= (char *) malloc(m_size
+1);
739 fin
->Read(m_content
, m_size
);
740 m_content
[m_size
]='\0';
742 wxRemoveFile(tmpfile
);
746 m_contentStream
= new wxMemoryInputStream (m_content
, m_size
);
748 return m_contentStream
->IsOk();
754 // ----------------------------------------------------------------------------
756 // ----------------------------------------------------------------------------
758 class wxChmFSHandler
: public wxFileSystemHandler
761 /// Constructor and Destructor
763 virtual ~wxChmFSHandler();
765 /// Is able to open location?
766 virtual bool CanOpen(const wxString
& location
);
768 virtual wxFSFile
* OpenFile(wxFileSystem
& fs
, const wxString
& location
);
769 /// Find first occurrence of spec
770 virtual wxString
FindFirst(const wxString
& spec
, int flags
= 0);
771 /// Find next occurrence of spec
772 virtual wxString
FindNext();
781 wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler()
784 m_pattern
=wxEmptyString
;
785 m_found
=wxEmptyString
;
789 wxChmFSHandler::~wxChmFSHandler()
795 bool wxChmFSHandler::CanOpen(const wxString
& location
)
797 wxString p
= GetProtocol(location
);
798 return (p
== wxT("chm")) &&
799 (GetProtocol(GetLeftLocation(location
)) == wxT("file"));
802 wxFSFile
* wxChmFSHandler::OpenFile(wxFileSystem
& WXUNUSED(fs
),
803 const wxString
& location
)
805 wxString right
= GetRightLocation(location
);
806 wxString left
= GetLeftLocation(location
);
812 if ( GetProtocol(left
) != wxT("file") )
814 wxLogError(_("CHM handler currently supports only local files!"));
818 // Work around javascript
819 wxString tmp
= wxString(right
);
820 if ( tmp
.MakeLower().Contains(wxT("javascipt")) && tmp
.Contains(wxT("\'")) )
822 right
= right
.AfterFirst(wxT('\'')).BeforeLast(wxT('\''));
825 // now work on the right location
826 if (right
.Contains(wxT("..")))
828 wxFileName
abs(right
);
829 abs
.MakeAbsolute(wxT("/"));
830 right
= abs
.GetFullPath();
833 // a workaround for absolute links to root
834 if ( (index
=right
.Index(wxT("//"))) != wxNOT_FOUND
)
836 right
=wxString(right
.Mid(index
+1));
837 wxLogWarning(_("Link contained '//', converted to absolute link."));
840 wxFileName leftFilename
= wxFileSystem::URLToFileName(left
);
842 // Open a stream to read the content of the chm-file
843 s
= new wxChmInputStream(leftFilename
.GetFullPath(), right
, true);
847 return new wxFSFile(s
,
848 left
+ wxT("#chm:") + right
,
851 wxDateTime(wxFileModificationTime(left
)));
861 * Doku see wxFileSystemHandler
863 wxString
wxChmFSHandler::FindFirst(const wxString
& spec
, int flags
)
865 wxString right
= GetRightLocation(spec
);
866 wxString left
= GetLeftLocation(spec
);
867 wxString nativename
= wxFileSystem::URLToFileName(left
).GetFullPath();
869 if ( GetProtocol(left
) != wxT("file") )
871 wxLogError(_("CHM handler currently supports only local files!"));
872 return wxEmptyString
;
875 m_chm
= new wxChmTools(wxFileName(nativename
));
876 m_pattern
= right
.AfterLast(wxT('/'));
878 wxString m_found
= m_chm
->Find(m_pattern
);
880 // now fake around hhp-files which are not existing in projects...
881 if (m_found
.empty() &&
882 m_pattern
.Contains(wxT(".hhp")) &&
883 !m_pattern
.Contains(wxT(".hhp.cached")))
885 m_found
.Printf(wxT("%s#chm:%s.hhp"),
886 left
.c_str(), m_pattern
.BeforeLast(wxT('.')).c_str());
895 wxString
wxChmFSHandler::FindNext()
897 if (m_pattern
.empty())
898 return wxEmptyString
;
900 return m_chm
->Find(m_pattern
, m_found
);
903 // ---------------------------------------------------------------------------
904 // wxModule to register CHM handler
905 // ---------------------------------------------------------------------------
907 class wxChmSupportModule
: public wxModule
909 DECLARE_DYNAMIC_CLASS(wxChmSupportModule
)
912 virtual bool OnInit()
914 wxFileSystem::AddHandler(new wxChmFSHandler
);
917 virtual void OnExit() {}
921 IMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule
, wxModule
)
923 #endif // wxUSE_LIBMSPACK