]>
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