]>
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
6 // Licence: wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
22 #include "wx/module.h"
25 #include "wx/filesys.h"
26 #include "wx/mstream.h"
27 #include "wx/wfstream.h"
29 #include "wx/html/forcelnk.h"
30 FORCE_LINK_ME(wxhtml_chm_support
)
32 // ----------------------------------------------------------------------------
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 // ----------------------------------------------------------------------------
43 wxChmTools(const wxFileName
&archive
);
47 /// Generate error-string for error-code
48 static const wxString
ChmErrorMsg(int error
);
50 /// get an array of archive-member-filenames
51 const wxArrayString
*GetFileNames()
56 /// get the name of the archive representated by this class
57 const wxString
GetArchiveName()
62 /// Find a file in the archive
63 const wxString
Find(const wxString
& pattern
,
64 const wxString
& startfrom
= wxEmptyString
);
66 /// Extract a file in the archive into a file
67 size_t Extract(const wxString
& pattern
, const wxString
& filename
);
69 /// check archive for a file
70 bool Contains(const wxString
& pattern
);
72 /// get a string for the last error which occurred
73 const wxString
GetLastErrorMessage();
79 // these vars are used by FindFirst/Next:
80 wxString m_chmFileName
;
81 char *m_chmFileNameANSI
;
83 /// mspack-pointer to mschmd_header
84 struct mschmd_header
*m_archive
;
85 /// mspack-pointer to mschm_decompressor
86 struct mschm_decompressor
*m_decompressor
;
88 /// Array of filenames in archive
89 wxArrayString
* m_fileNames
;
91 /// Internal function to get filepointer
92 struct mschmd_file
*GetMschmdFile(const wxString
& pattern
);
99 * @param archive The filename of the archive to open
101 wxChmTools::wxChmTools(const wxFileName
&archive
)
103 m_chmFileName
= archive
.GetFullPath();
105 wxASSERT_MSG( !m_chmFileName
.empty(), wxT("empty archive name") );
108 m_decompressor
= NULL
;
112 struct mschmd_header
*chmh
;
113 struct mschm_decompressor
*chmd
;
114 struct mschmd_file
*file
;
116 // Create decompressor
117 chmd
= mspack_create_chm_decompressor(NULL
);
118 m_decompressor
= (struct mschm_decompressor
*) chmd
;
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
));
125 // Open the archive and store it in class:
126 if ( (chmh
= chmd
->open(chmd
, (char*)m_chmFileNameANSI
)) )
130 // Create Filenamearray
131 m_fileNames
= new wxArrayString
;
133 // Store Filenames in array
134 for (file
= chmh
->files
; file
; file
= file
->next
)
136 m_fileNames
->Add(wxString::FromAscii(file
->filename
));
141 wxLogError(_("Failed to open CHM archive '%s'."),
142 archive
.GetFullPath().c_str());
143 m_lasterror
= (chmd
->last_error(chmd
));
152 wxChmTools::~wxChmTools()
154 struct mschm_decompressor
*chmd
= m_decompressor
;
155 struct mschmd_header
*chmh
= m_archive
;
161 chmd
->close(chmd
, chmh
);
163 free(m_chmFileNameANSI
);
165 // Destroy Decompressor
167 mspack_destroy_chm_decompressor(chmd
);
173 * Checks if the given pattern matches to any
174 * filename stored in archive
176 * @param pattern The filename pattern, may include '*' and/or '?'
177 * @return true, if any file matching pattern has been found,
180 bool wxChmTools::Contains(const wxString
& pattern
)
183 wxString pattern_tmp
= wxString(pattern
).MakeLower();
185 // loop through filearay
186 if ( m_fileNames
&& (count
= m_fileNames
->GetCount()) > 0 )
188 for (int i
= 0; i
< count
; i
++)
190 wxString tmp
= m_fileNames
->Item(i
).MakeLower();
191 if ( tmp
.Matches(pattern_tmp
) || tmp
.Mid(1).Matches(pattern_tmp
))
204 * Finds the next file descibed by a pattern in the archive, starting
205 * the file given by second parameter
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
211 const wxString
wxChmTools::Find(const wxString
& pattern
,
212 const wxString
& startfrom
)
216 wxString
pattern_tmp(pattern
);
217 wxString
startfrom_tmp(startfrom
);
218 pattern_tmp
.MakeLower();
219 startfrom_tmp
.MakeLower();
221 if ( m_fileNames
&& (count
= m_fileNames
->GetCount()) > 0 )
223 for (int i
= 0; i
< count
; i
++)
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
) )
230 if ( tmp
.Matches(pattern_tmp
) ||
231 tmp
.Mid(1).Matches(pattern_tmp
) )
238 return wxEmptyString
;
245 * extracts the first hit of pattern to the given position
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
252 size_t wxChmTools::Extract(const wxString
& pattern
, const wxString
& filename
)
254 struct mschm_decompressor
*d
= m_decompressor
;
255 struct mschmd_header
*h
= m_archive
;
256 struct mschmd_file
*f
;
259 wxString pattern_tmp
= (wxString(pattern
)).MakeLower();
261 for (f
= h
->files
; f
; f
= f
->next
)
263 tmp
= wxString::FromAscii(f
->filename
).MakeLower();
264 if ( tmp
.Matches(pattern_tmp
) ||
265 tmp
.Mid(1).Matches(pattern_tmp
) )
267 // ignore leading '/'
269 (char*)(const char*)filename
.mb_str(wxConvFile
)))
272 m_lasterror
= d
->last_error(d
);
273 wxLogError(_("Could not extract %s into %s: %s"),
274 wxString::FromAscii(f
->filename
).c_str(),
276 ChmErrorMsg(m_lasterror
).c_str());
281 return (size_t) f
->length
;
292 * Find a file by pattern
294 * @param pattern A filename pattern (may contain * and ? chars)
295 * @return A pointer to the file (mschmd_file*)
297 struct mschmd_file
*wxChmTools::GetMschmdFile(const wxString
& pattern_orig
)
299 struct mschmd_file
*f
;
300 struct mschmd_header
*h
= (struct mschmd_header
*) m_archive
;
302 wxString pattern
= wxString(pattern_orig
).MakeLower();
304 for (f
= h
->files
; f
; f
= f
->next
)
306 tmp
= wxString::FromAscii(f
->filename
).MakeLower();
307 if ( tmp
.Matches(pattern
) || tmp
.Mid(1).Matches(pattern
) )
309 // ignore leading '/'
317 const wxString
wxChmTools::GetLastErrorMessage()
319 return ChmErrorMsg(m_lasterror
);
322 const wxString
wxChmTools::ChmErrorMsg(int error
)
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");
351 return _("unknown error");
355 // ---------------------------------------------------------------------------
357 // ---------------------------------------------------------------------------
359 class wxChmInputStream
: public wxInputStream
363 wxChmInputStream(const wxString
& archive
,
364 const wxString
& file
, bool simulate
= false);
366 virtual ~wxChmInputStream();
368 /// Return the size of the accessed file in archive
369 virtual size_t GetSize() const { return m_size
; }
371 virtual bool Eof() const;
372 /// Set simulation-mode of HHP-File (if non is found)
373 void SimulateHHP(bool sim
) { m_simulateHHP
= sim
; }
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
; }
389 wxInputStream
* m_contentStream
;
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.
397 // locates the file and returns a mspack_file *
398 mspack_file
*LocateFile(wxString filename
);
400 // should store pointer to current file
403 // The Chm-Class for extracting the data
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
418 wxChmInputStream::wxChmInputStream(const wxString
& archive
,
419 const wxString
& filename
, bool simulate
)
425 m_contentStream
= NULL
;
426 m_lasterror
= wxSTREAM_NO_ERROR
;
427 m_chm
= new wxChmTools (wxFileName(archive
));
429 m_fileName
= wxString(filename
).MakeLower();
430 m_simulateHHP
= simulate
;
432 if ( !m_chm
->Contains(m_fileName
) )
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
)
439 // now we open an hhp-file
444 wxLogError(_("Could not locate file '%s'."), filename
.c_str());
445 m_lasterror
= wxSTREAM_READ_ERROR
;
451 CreateFileStream(m_fileName
);
456 wxChmInputStream::~wxChmInputStream()
460 delete m_contentStream
;
469 bool wxChmInputStream::Eof() const
471 return (m_content
==NULL
||
472 m_contentStream
==NULL
||
473 m_contentStream
->Eof() ||
479 size_t wxChmInputStream::OnSysRead(void *buffer
, size_t bufsize
)
481 if ( m_pos
>= m_size
)
483 m_lasterror
= wxSTREAM_EOF
;
486 m_lasterror
= wxSTREAM_NO_ERROR
;
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
;
493 if (m_contentStream
->SeekI(m_pos
) == wxInvalidOffset
)
495 m_lasterror
= wxSTREAM_EOF
;
499 size_t read
= m_contentStream
->Read(buffer
, bufsize
).LastRead();
502 if (m_contentStream
->SeekI(m_pos
) == wxInvalidOffset
)
504 m_lasterror
= wxSTREAM_READ_ERROR
;
509 m_lasterror
= m_contentStream
->GetLastError();
517 wxFileOffset
wxChmInputStream::OnSysSeek(wxFileOffset seek
, wxSeekMode mode
)
519 wxString mode_str
= wxEmptyString
;
521 if ( !m_contentStream
|| m_contentStream
->Eof() )
523 m_lasterror
= wxSTREAM_EOF
;
526 m_lasterror
= wxSTREAM_NO_ERROR
;
528 wxFileOffset nextpos
;
533 nextpos
= seek
+ m_pos
;
539 nextpos
= m_size
- 1 + seek
;
543 break; /* just to fool compiler, never happens */
547 // Set current position on stream
548 m_contentStream
->SeekI(m_pos
);
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
562 wxChmInputStream::CreateHHPStream()
568 wxMemoryOutputStream
*out
;
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")) )
576 wxLogDebug("Archive doesn't contain #SYSTEM file");
582 file
= wxFileName(wxT("/#SYSTEM"));
585 if ( CreateFileStream(wxT("/#SYSTEM")) )
587 // New stream for writing a memory area to simulate the
589 out
= new wxMemoryOutputStream();
591 tmp
= "[OPTIONS]\r\n";
592 out
->Write((const void *) tmp
, strlen(tmp
));
598 // use the actual stream for reading
601 /* Now read the contents, and try to get the needed information */
603 // First 4 Bytes are Version information, skip
608 // Read #SYSTEM-Code and length
610 code
= wxUINT16_SWAP_ON_BE( code
) ;
612 len
= wxUINT16_SWAP_ON_BE( len
) ;
619 case 0: // CONTENTS_FILE
622 tmp
= "Contents file=";
626 case 1: // INDEX_FILE
630 case 2: // DEFAULT_TOPIC
631 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 int len
= sprintf(msg
, "Language=0x%X\r\n", lcid
) ;
655 out
->Write(msg
, len
) ;
664 out
->Write((const void *) tmp
, strlen(tmp
));
665 out
->Write(buf
, strlen((char*)buf
));
666 out
->Write("\r\n", 2);
674 // Free the old data which wont be used any more
675 delete m_contentStream
;
679 // Now add entries which are missing
680 if ( !hhc
&& m_chm
->Contains(wxT("*.hhc")) )
682 tmp
= "Contents File=*.hhc\r\n";
683 out
->Write((const void *) tmp
, strlen(tmp
));
686 if ( !hhk
&& m_chm
->Contains(wxT("*.hhk")) )
688 tmp
= "Index File=*.hhk\r\n";
689 out
->Write((const void *) tmp
, strlen(tmp
));
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';
700 m_contentStream
= new wxMemoryInputStream(m_content
, m_size
);
708 * Creates a Stream pointing to a virtual file in
709 * the current archive
711 bool wxChmInputStream::CreateFileStream(const wxString
& pattern
)
713 wxFileInputStream
* fin
;
714 wxString tmpfile
= wxFileName::CreateTempFileName(wxT("chmstrm"));
716 if ( tmpfile
.empty() )
718 wxLogError(_("Could not create temporary file '%s'"), tmpfile
.c_str());
722 // try to extract the file
723 if ( m_chm
->Extract(pattern
, tmpfile
) <= 0 )
725 wxLogError(_("Extraction of '%s' into '%s' failed."),
726 pattern
.c_str(), tmpfile
.c_str());
727 if ( wxFileExists(tmpfile
) )
728 wxRemoveFile(tmpfile
);
733 // Open a filestream to extracted file
734 fin
= new wxFileInputStream(tmpfile
);
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';
743 wxRemoveFile(tmpfile
);
747 m_contentStream
= new wxMemoryInputStream (m_content
, m_size
);
749 return m_contentStream
->IsOk();
755 // ----------------------------------------------------------------------------
757 // ----------------------------------------------------------------------------
759 class wxChmFSHandler
: public wxFileSystemHandler
762 /// Constructor and Destructor
764 virtual ~wxChmFSHandler();
766 /// Is able to open location?
767 virtual bool CanOpen(const wxString
& location
);
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();
782 wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler()
785 m_pattern
=wxEmptyString
;
786 m_found
=wxEmptyString
;
790 wxChmFSHandler::~wxChmFSHandler()
796 bool wxChmFSHandler::CanOpen(const wxString
& location
)
798 wxString p
= GetProtocol(location
);
799 return (p
== wxT("chm")) &&
800 (GetProtocol(GetLeftLocation(location
)) == wxT("file"));
803 wxFSFile
* wxChmFSHandler::OpenFile(wxFileSystem
& WXUNUSED(fs
),
804 const wxString
& location
)
806 wxString right
= GetRightLocation(location
);
807 wxString left
= GetLeftLocation(location
);
813 if ( GetProtocol(left
) != wxT("file") )
815 wxLogError(_("CHM handler currently supports only local files!"));
819 // Work around javascript
820 wxString tmp
= wxString(right
);
821 if ( tmp
.MakeLower().Contains(wxT("javascipt")) && tmp
.Contains(wxT("\'")) )
823 right
= right
.AfterFirst(wxT('\'')).BeforeLast(wxT('\''));
826 // now work on the right location
827 if (right
.Contains(wxT("..")))
829 wxFileName
abs(right
);
830 abs
.MakeAbsolute(wxT("/"));
831 right
= abs
.GetFullPath();
834 // a workaround for absolute links to root
835 if ( (index
=right
.Index(wxT("//"))) != wxNOT_FOUND
)
837 right
=wxString(right
.Mid(index
+1));
838 wxLogWarning(_("Link contained '//', converted to absolute link."));
841 wxFileName leftFilename
= wxFileSystem::URLToFileName(left
);
842 if (!leftFilename
.FileExists())
845 // Open a stream to read the content of the chm-file
846 s
= new wxChmInputStream(leftFilename
.GetFullPath(), right
, true);
850 return new wxFSFile(s
,
851 left
+ wxT("#chm:") + right
,
854 wxDateTime(leftFilename
.GetModificationTime()));
864 * Doku see wxFileSystemHandler
866 wxString
wxChmFSHandler::FindFirst(const wxString
& spec
, int flags
)
868 wxString right
= GetRightLocation(spec
);
869 wxString left
= GetLeftLocation(spec
);
870 wxString nativename
= wxFileSystem::URLToFileName(left
).GetFullPath();
872 if ( GetProtocol(left
) != wxT("file") )
874 wxLogError(_("CHM handler currently supports only local files!"));
875 return wxEmptyString
;
878 m_chm
= new wxChmTools(wxFileName(nativename
));
879 m_pattern
= right
.AfterLast(wxT('/'));
881 wxString m_found
= m_chm
->Find(m_pattern
);
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")))
888 m_found
.Printf(wxT("%s#chm:%s.hhp"),
889 left
.c_str(), m_pattern
.BeforeLast(wxT('.')).c_str());
898 wxString
wxChmFSHandler::FindNext()
900 if (m_pattern
.empty())
901 return wxEmptyString
;
903 return m_chm
->Find(m_pattern
, m_found
);
906 // ---------------------------------------------------------------------------
907 // wxModule to register CHM handler
908 // ---------------------------------------------------------------------------
910 class wxChmSupportModule
: public wxModule
912 DECLARE_DYNAMIC_CLASS(wxChmSupportModule
)
915 virtual bool OnInit()
917 wxFileSystem::AddHandler(new wxChmFSHandler
);
920 virtual void OnExit() {}
924 IMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule
, wxModule
)
926 #endif // wxUSE_LIBMSPACK