]>
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 occured 
  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 off_t 
OnSysSeek(off_t seek
, wxSeekMode mode
); 
 381     /// See wxInputStream 
 382     virtual off_t 
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 off_t 
wxChmInputStream::OnSysSeek(off_t 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
; 
 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/ch05s03.html#SYSTEM 
 561     if ( ! m_chm
->Contains(_("/#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 
 605                 case 0: // CONTENTS_FILE 
 606                     tmp 
= "Contents file="; 
 609                 case 1: // INDEX_FILE 
 613                 case 2: // DEFAULT_TOPIC 
 614                     tmp 
= "Default Topic="; 
 620                 //       case 6: // COMPILED_FILE 
 621                 //         tmp = "Compiled File="; 
 623                 case 7: // COMPILED_FILE 
 624                     tmp 
= "Binary Index=YES\r\n"; 
 625                     out
->Write( (const void *) tmp
, strlen(tmp
)); 
 635                 out
->Write((const void *) tmp
, strlen(tmp
)); 
 636                 out
->Write(buf
, strlen((char*)buf
)); 
 637                 out
->Write("\r\n", 2); 
 645         // Free the old data which wont be used any more 
 646         delete m_contentStream
; 
 650         // Now add entries which are missing 
 651         if ( !hhc 
&& m_chm
->Contains(_T("*.hhc")) ) 
 653             tmp 
= "Contents File=*.hhc\r\n"; 
 654             out
->Write((const void *) tmp
, strlen(tmp
)); 
 657         if ( !hhk 
&& m_chm
->Contains(_T("*.hhk")) ) 
 659             tmp 
= "Index File=*.hhk\r\n"; 
 660             out
->Write((const void *) tmp
, strlen(tmp
)); 
 663         // Now copy the Data from the memory 
 664         out
->SeekO(0, wxFromEnd
); 
 665         m_size 
= out
->TellO(); 
 666         out
->SeekO(0, wxFromStart
); 
 667         m_content 
= (char *) malloc (m_size
+1); 
 668         out
->CopyTo(m_content
, m_size
); 
 669         m_content
[m_size
]='\0'; 
 671         m_contentStream 
= new wxMemoryInputStream(m_content
, m_size
); 
 679  * Creates a Stream pointing to a virtual file in 
 680  * the current archive 
 682 bool wxChmInputStream::CreateFileStream(const wxString
& pattern
) 
 684     wxFileInputStream 
* fin
; 
 685     wxString tmpfile 
= wxFileName::CreateTempFileName(_T("chmstrm"), NULL
); 
 687     if ( tmpfile
.empty() ) 
 689         wxLogError(_("Could not create temporary file '%s'"), tmpfile
.c_str()); 
 693     // try to extract the file 
 694     if ( m_chm
->Extract(pattern
, tmpfile
) <= 0 ) 
 696         wxLogError(_("Extraction of '%s' into '%s' failed."), 
 697                    pattern
.c_str(), tmpfile
.c_str()); 
 698         if ( wxFileExists(tmpfile
) ) 
 699             wxRemoveFile(tmpfile
); 
 704         // Open a filestream to extracted file 
 705         fin 
= new wxFileInputStream(tmpfile
); 
 706         m_size 
= fin
->GetSize(); 
 707         m_content 
= (char *) malloc(m_size
+1); 
 708         fin
->Read(m_content
, m_size
); 
 709         m_content
[m_size
]='\0'; 
 711         wxRemoveFile(tmpfile
); 
 715         m_contentStream 
= new wxMemoryInputStream (m_content
, m_size
); 
 717         return m_contentStream
->IsOk(); 
 723 // ---------------------------------------------------------------------------- 
 725 // ---------------------------------------------------------------------------- 
 727 class wxChmFSHandler 
: public wxFileSystemHandler
 
 730     /// Constructor and Destructor 
 734     /// Is able to open location? 
 735     virtual bool CanOpen(const wxString
& location
); 
 737     virtual wxFSFile
* OpenFile(wxFileSystem
& fs
, const wxString
& location
); 
 738     /// Find first occurence of spec 
 739     virtual wxString 
FindFirst(const wxString
& spec
, int flags 
= 0); 
 740     /// Find next occurence of spec 
 741     virtual wxString 
FindNext(); 
 750 wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler() 
 753     m_pattern
=wxEmptyString
; 
 754     m_found
=wxEmptyString
; 
 758 wxChmFSHandler::~wxChmFSHandler() 
 764 bool wxChmFSHandler::CanOpen(const wxString
& location
) 
 766     wxString p 
= GetProtocol(location
); 
 767     return (p 
== _T("chm")) && 
 768            (GetProtocol(GetLeftLocation(location
)) == _T("file")); 
 771 wxFSFile
* wxChmFSHandler::OpenFile(wxFileSystem
& WXUNUSED(fs
), 
 772                                    const wxString
& location
) 
 774     wxString right 
= GetRightLocation(location
); 
 775     wxString left 
= GetLeftLocation(location
); 
 781     if ( GetProtocol(left
) != _T("file") ) 
 783         wxLogError(_("CHM handler currently supports only local files!")); 
 787     // Work around javascript 
 788     wxString tmp 
= wxString(right
); 
 789     if ( tmp
.MakeLower().Contains(_T("javascipt")) && tmp
.Contains(_T("\'")) ) 
 791         right 
= right
.AfterFirst(_T('\'')).BeforeLast(_T('\'')); 
 794     // now work on the right location 
 795     if (right
.Contains(_T(".."))) 
 797         wxFileName 
abs(right
); 
 798         abs
.MakeAbsolute(_T("/")); 
 799         right 
= abs
.GetFullPath(); 
 802     // a workaround for absolute links to root 
 803     if ( (index
=right
.Index(_T("//"))) != wxNOT_FOUND 
) 
 805         right
=wxString(right
.Mid(index
+1)); 
 806         wxLogWarning(_("Link contained '//', converted to absolute link.")); 
 809     wxFileName leftFilename 
= wxFileSystem::URLToFileName(left
); 
 811     // Open a stream to read the content of the chm-file 
 812     s 
= new wxChmInputStream(leftFilename
.GetFullPath(), right
, true); 
 814     wxString mime 
= GetMimeTypeFromExt(location
); 
 818         return new wxFSFile(s
, 
 819                             left 
+ _T("#chm:") + right
, 
 822                             wxDateTime(wxFileModificationTime(left
))); 
 832  * Doku see wxFileSystemHandler 
 834 wxString 
wxChmFSHandler::FindFirst(const wxString
& spec
, int flags
) 
 836     wxString right 
= GetRightLocation(spec
); 
 837     wxString left 
= GetLeftLocation(spec
); 
 838     wxString nativename 
= wxFileSystem::URLToFileName(left
).GetFullPath(); 
 840     if ( GetProtocol(left
) != _T("file") ) 
 842         wxLogError(_("CHM handler currently supports only local files!")); 
 843         return wxEmptyString
; 
 846     m_chm 
= new wxChmTools(wxFileName(nativename
)); 
 847     m_pattern 
= right
.AfterLast(_T('/')); 
 849     wxString m_found 
= m_chm
->Find(m_pattern
); 
 851     // now fake around hhp-files which are not existing in projects... 
 852     if (m_found
.empty() && 
 853         m_pattern
.Contains(_T(".hhp")) && 
 854         !m_pattern
.Contains(_T(".hhp.cached"))) 
 856         m_found
.Printf(_T("%s#chm:%s.hhp"), 
 857                        left
.c_str(), m_pattern
.BeforeLast(_T('.')).c_str()); 
 866 wxString 
wxChmFSHandler::FindNext() 
 868     if (m_pattern
.empty()) 
 869         return wxEmptyString
; 
 871         return m_chm
->Find(m_pattern
, m_found
); 
 874 // --------------------------------------------------------------------------- 
 875 // wxModule to register CHM handler 
 876 // --------------------------------------------------------------------------- 
 878 class wxChmSupportModule 
: public wxModule
 
 880     DECLARE_DYNAMIC_CLASS(wxChmSupportModule
) 
 883     virtual bool OnInit() 
 885         wxFileSystem::AddHandler(new wxChmFSHandler
); 
 888     virtual void OnExit() {} 
 892 IMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule
, wxModule
) 
 894 #endif // wxUSE_LIBMSPACK