This wrapper simply combines the calls to MakeAbsolute() and GetFullPath(), but using it results in shorter and more clear code, so it seems to be worth having.
916 lines
24 KiB
C++
916 lines
24 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/html/chm.cpp
|
|
// Purpose: CHM (Help) support for wxHTML
|
|
// Author: Markus Sinner
|
|
// Copyright: (c) 2003 Herd Software Development
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
|
|
#if wxUSE_LIBMSPACK
|
|
|
|
#include <mspack.h>
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/intl.h"
|
|
#include "wx/log.h"
|
|
#include "wx/module.h"
|
|
#endif
|
|
|
|
#include "wx/filesys.h"
|
|
#include "wx/mstream.h"
|
|
#include "wx/wfstream.h"
|
|
|
|
#include "wx/html/forcelnk.h"
|
|
FORCE_LINK_ME(wxhtml_chm_support)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/// wxChmTools
|
|
/// <p>
|
|
/// this class is used to abstract access to CHM-Archives
|
|
/// with library mspack written by Stuart Caie
|
|
/// http://www.kyz.uklinux.net/libmspack/
|
|
// ----------------------------------------------------------------------------
|
|
class wxChmTools
|
|
{
|
|
public:
|
|
/// constructor
|
|
wxChmTools(const wxFileName &archive);
|
|
/// destructor
|
|
~wxChmTools();
|
|
|
|
/// Generate error-string for error-code
|
|
static const wxString ChmErrorMsg(int error);
|
|
|
|
/// get an array of archive-member-filenames
|
|
const wxArrayString *GetFileNames()
|
|
{
|
|
return m_fileNames;
|
|
}
|
|
|
|
/// get the name of the archive represented by this class
|
|
const wxString GetArchiveName() const
|
|
{
|
|
return m_chmFileName;
|
|
}
|
|
|
|
/// Find a file in the archive
|
|
const wxString Find(const wxString& pattern,
|
|
const wxString& startfrom = wxEmptyString);
|
|
|
|
/// Extract a file in the archive into a file
|
|
size_t Extract(const wxString& pattern, const wxString& filename);
|
|
|
|
/// check archive for a file
|
|
bool Contains(const wxString& pattern);
|
|
|
|
/// get a string for the last error which occurred
|
|
const wxString GetLastErrorMessage();
|
|
|
|
/// Last Error
|
|
int m_lasterror;
|
|
|
|
private:
|
|
// these vars are used by FindFirst/Next:
|
|
wxString m_chmFileName;
|
|
char *m_chmFileNameANSI;
|
|
|
|
/// mspack-pointer to mschmd_header
|
|
struct mschmd_header *m_archive;
|
|
/// mspack-pointer to mschm_decompressor
|
|
struct mschm_decompressor *m_decompressor;
|
|
|
|
/// Array of filenames in archive
|
|
wxArrayString * m_fileNames;
|
|
|
|
/// Internal function to get filepointer
|
|
struct mschmd_file *GetMschmdFile(const wxString& pattern);
|
|
};
|
|
|
|
|
|
/***
|
|
* constructor
|
|
*
|
|
* @param archive The filename of the archive to open
|
|
*/
|
|
wxChmTools::wxChmTools(const wxFileName &archive)
|
|
{
|
|
m_chmFileName = archive.GetFullPath();
|
|
|
|
wxASSERT_MSG( !m_chmFileName.empty(), wxT("empty archive name") );
|
|
|
|
m_archive = NULL;
|
|
m_decompressor = NULL;
|
|
m_fileNames = NULL;
|
|
m_lasterror = 0;
|
|
|
|
struct mschmd_header *chmh;
|
|
struct mschm_decompressor *chmd;
|
|
struct mschmd_file *file;
|
|
|
|
// Create decompressor
|
|
chmd = mspack_create_chm_decompressor(NULL);
|
|
m_decompressor = (struct mschm_decompressor *) chmd;
|
|
|
|
// NB: we must make a copy of the string because chmd->open won't call
|
|
// strdup() [libmspack-20030726], which would cause crashes in
|
|
// Unicode build when mb_str() returns temporary buffer
|
|
m_chmFileNameANSI = strdup((const char*)m_chmFileName.mb_str(wxConvFile));
|
|
|
|
// Open the archive and store it in class:
|
|
if ( (chmh = chmd->open(chmd, (char*)m_chmFileNameANSI)) )
|
|
{
|
|
m_archive = chmh;
|
|
|
|
// Create Filenamearray
|
|
m_fileNames = new wxArrayString;
|
|
|
|
// Store Filenames in array
|
|
for (file = chmh->files; file; file = file->next)
|
|
{
|
|
m_fileNames->Add(wxString::FromAscii(file->filename));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wxLogError(_("Failed to open CHM archive '%s'."),
|
|
archive.GetFullPath());
|
|
m_lasterror = (chmd->last_error(chmd));
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/***
|
|
* Destructor
|
|
*/
|
|
wxChmTools::~wxChmTools()
|
|
{
|
|
struct mschm_decompressor *chmd = m_decompressor;
|
|
struct mschmd_header *chmh = m_archive;
|
|
|
|
delete m_fileNames;
|
|
|
|
// Close Archive
|
|
if (chmh && chmd)
|
|
chmd->close(chmd, chmh);
|
|
|
|
free(m_chmFileNameANSI);
|
|
|
|
// Destroy Decompressor
|
|
if (chmd)
|
|
mspack_destroy_chm_decompressor(chmd);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Checks if the given pattern matches to any
|
|
* filename stored in archive
|
|
*
|
|
* @param pattern The filename pattern, may include '*' and/or '?'
|
|
* @return true, if any file matching pattern has been found,
|
|
* false if not
|
|
*/
|
|
bool wxChmTools::Contains(const wxString& pattern)
|
|
{
|
|
int count;
|
|
wxString pattern_tmp = wxString(pattern).MakeLower();
|
|
|
|
// loop through filearay
|
|
if ( m_fileNames && (count = m_fileNames->GetCount()) > 0 )
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
wxString tmp = m_fileNames->Item(i).MakeLower();
|
|
if ( tmp.Matches(pattern_tmp) || tmp.Mid(1).Matches(pattern_tmp))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Find()
|
|
*
|
|
* Finds the next file described by a pattern in the archive, starting
|
|
* the file given by second parameter
|
|
*
|
|
* @param pattern The file-pattern to search for. May contain '*' and/or '?'
|
|
* @param startfrom The filename which the search should start after
|
|
* @returns The full pathname of the found file
|
|
*/
|
|
const wxString wxChmTools::Find(const wxString& pattern,
|
|
const wxString& startfrom)
|
|
{
|
|
int count;
|
|
wxString tmp;
|
|
wxString pattern_tmp(pattern);
|
|
wxString startfrom_tmp(startfrom);
|
|
pattern_tmp.MakeLower();
|
|
startfrom_tmp.MakeLower();
|
|
|
|
if ( m_fileNames && (count = m_fileNames->GetCount()) > 0 )
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
tmp = m_fileNames->Item(i).MakeLower();
|
|
// if we find the string where the search should began
|
|
if ( tmp.Matches(startfrom_tmp) ||
|
|
tmp.Mid(1).Matches(startfrom_tmp) )
|
|
continue;
|
|
if ( tmp.Matches(pattern_tmp) ||
|
|
tmp.Mid(1).Matches(pattern_tmp) )
|
|
{
|
|
return tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
|
|
/**
|
|
* Extract ()
|
|
*
|
|
* extracts the first hit of pattern to the given position
|
|
*
|
|
* @param pattern A filename pattern (may contain * and ? chars)
|
|
* @param filename The FileName where to temporary extract the file to
|
|
* @return 0 at no file extracted<br>
|
|
* number of bytes extracted else
|
|
*/
|
|
size_t wxChmTools::Extract(const wxString& pattern, const wxString& filename)
|
|
{
|
|
struct mschm_decompressor *d = m_decompressor;
|
|
struct mschmd_header *h = m_archive;
|
|
struct mschmd_file *f;
|
|
|
|
wxString tmp;
|
|
wxString pattern_tmp = (wxString(pattern)).MakeLower();
|
|
|
|
for (f = h->files; f; f = f->next)
|
|
{
|
|
tmp = wxString::FromAscii(f->filename).MakeLower();
|
|
if ( tmp.Matches(pattern_tmp) ||
|
|
tmp.Mid(1).Matches(pattern_tmp) )
|
|
{
|
|
// ignore leading '/'
|
|
if (d->extract(d, f,
|
|
(char*)(const char*)filename.mb_str(wxConvFile)))
|
|
{
|
|
// Error
|
|
m_lasterror = d->last_error(d);
|
|
wxLogError(_("Could not extract %s into %s: %s"),
|
|
wxString::FromAscii(f->filename),
|
|
filename,
|
|
ChmErrorMsg(m_lasterror));
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return (size_t) f->length;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Find a file by pattern
|
|
*
|
|
* @param pattern A filename pattern (may contain * and ? chars)
|
|
* @return A pointer to the file (mschmd_file*)
|
|
*/
|
|
struct mschmd_file *wxChmTools::GetMschmdFile(const wxString& pattern_orig)
|
|
{
|
|
struct mschmd_file *f;
|
|
struct mschmd_header *h = (struct mschmd_header *) m_archive;
|
|
wxString tmp;
|
|
wxString pattern = wxString(pattern_orig).MakeLower();
|
|
|
|
for (f = h->files; f; f = f->next)
|
|
{
|
|
tmp = wxString::FromAscii(f->filename).MakeLower();
|
|
if ( tmp.Matches(pattern) || tmp.Mid(1).Matches(pattern) )
|
|
{
|
|
// ignore leading '/'
|
|
return f;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const wxString wxChmTools::GetLastErrorMessage()
|
|
{
|
|
return ChmErrorMsg(m_lasterror);
|
|
}
|
|
|
|
const wxString wxChmTools::ChmErrorMsg(int error)
|
|
{
|
|
switch (error)
|
|
{
|
|
case MSPACK_ERR_OK:
|
|
return _("no error");
|
|
case MSPACK_ERR_ARGS:
|
|
return _("bad arguments to library function");
|
|
case MSPACK_ERR_OPEN:
|
|
return _("error opening file");
|
|
case MSPACK_ERR_READ:
|
|
return _("read error");
|
|
case MSPACK_ERR_WRITE:
|
|
return _("write error");
|
|
case MSPACK_ERR_SEEK:
|
|
return _("seek error");
|
|
case MSPACK_ERR_NOMEMORY:
|
|
return _("out of memory");
|
|
case MSPACK_ERR_SIGNATURE:
|
|
return _("bad signature");
|
|
case MSPACK_ERR_DATAFORMAT:
|
|
return _("error in data format");
|
|
case MSPACK_ERR_CHECKSUM:
|
|
return _("checksum error");
|
|
case MSPACK_ERR_CRUNCH:
|
|
return _("compression error");
|
|
case MSPACK_ERR_DECRUNCH:
|
|
return _("decompression error");
|
|
}
|
|
return _("unknown error");
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
/// wxChmInputStream
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class wxChmInputStream : public wxInputStream
|
|
{
|
|
public:
|
|
/// Constructor
|
|
wxChmInputStream(const wxString& archive,
|
|
const wxString& file, bool simulate = false);
|
|
/// Destructor
|
|
virtual ~wxChmInputStream();
|
|
|
|
/// Return the size of the accessed file in archive
|
|
virtual size_t GetSize() const wxOVERRIDE { return m_size; }
|
|
/// End of Stream?
|
|
virtual bool Eof() const wxOVERRIDE;
|
|
/// Set simulation-mode of HHP-File (if non is found)
|
|
void SimulateHHP(bool sim) { m_simulateHHP = sim; }
|
|
|
|
protected:
|
|
/// See wxInputStream
|
|
virtual size_t OnSysRead(void *buffer, size_t bufsize) wxOVERRIDE;
|
|
/// See wxInputStream
|
|
virtual wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode) wxOVERRIDE;
|
|
/// See wxInputStream
|
|
virtual wxFileOffset OnSysTell() const wxOVERRIDE { return m_pos; }
|
|
|
|
private:
|
|
size_t m_size;
|
|
wxFileOffset m_pos;
|
|
bool m_simulateHHP;
|
|
|
|
char * m_content;
|
|
wxInputStream * m_contentStream;
|
|
|
|
void CreateHHPStream();
|
|
bool CreateFileStream(const wxString& pattern);
|
|
// this void* is handle of archive . I'm sorry it is void and not proper
|
|
// type but I don't want to make unzip.h header public.
|
|
|
|
// should store pointer to current file
|
|
mspack_file *m_file;
|
|
|
|
// The Chm-Class for extracting the data
|
|
wxChmTools *m_chm;
|
|
|
|
wxString m_fileName;
|
|
};
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
* @param archive The name of the .chm archive. Remember that archive must
|
|
* be local file accessible via fopen, fread functions!
|
|
* @param filename The Name of the file to be extracted from archive
|
|
* @param simulate if true than class should simulate .HHP-File based on #SYSTEM
|
|
* if false than class does nothing if it doesn't find .hhp
|
|
*/
|
|
wxChmInputStream::wxChmInputStream(const wxString& archive,
|
|
const wxString& filename, bool simulate)
|
|
: wxInputStream()
|
|
{
|
|
m_pos = 0;
|
|
m_size = 0;
|
|
m_content = NULL;
|
|
m_contentStream = NULL;
|
|
m_lasterror = wxSTREAM_NO_ERROR;
|
|
m_chm = new wxChmTools (wxFileName(archive));
|
|
m_file = NULL;
|
|
m_fileName = wxString(filename).MakeLower();
|
|
m_simulateHHP = simulate;
|
|
|
|
if ( !m_chm->Contains(m_fileName) )
|
|
{
|
|
// if the file could not be located, but was *.hhp, than we create
|
|
// the content of the hhp-file on the fly and store it for reading
|
|
// by the application
|
|
if ( m_fileName.Find(wxT(".hhp")) != wxNOT_FOUND && m_simulateHHP )
|
|
{
|
|
// now we open an hhp-file
|
|
CreateHHPStream();
|
|
}
|
|
else
|
|
{
|
|
wxLogError(_("Could not locate file '%s'."), filename);
|
|
m_lasterror = wxSTREAM_READ_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{ // file found
|
|
CreateFileStream(m_fileName);
|
|
}
|
|
}
|
|
|
|
|
|
wxChmInputStream::~wxChmInputStream()
|
|
{
|
|
delete m_chm;
|
|
|
|
delete m_contentStream;
|
|
|
|
if (m_content)
|
|
{
|
|
free (m_content);
|
|
m_content=NULL;
|
|
}
|
|
}
|
|
|
|
bool wxChmInputStream::Eof() const
|
|
{
|
|
return (m_content==NULL ||
|
|
m_contentStream==NULL ||
|
|
m_contentStream->Eof() ||
|
|
(size_t)m_pos>m_size);
|
|
}
|
|
|
|
|
|
|
|
size_t wxChmInputStream::OnSysRead(void *buffer, size_t bufsize)
|
|
{
|
|
if ( (size_t)m_pos >= m_size )
|
|
{
|
|
m_lasterror = wxSTREAM_EOF;
|
|
return 0;
|
|
}
|
|
m_lasterror = wxSTREAM_NO_ERROR;
|
|
|
|
// If the rest to read from the stream is less
|
|
// than the buffer size, then only read the rest
|
|
if ( m_pos + bufsize > m_size )
|
|
bufsize = m_size - m_pos;
|
|
|
|
if (m_contentStream->SeekI(m_pos) == wxInvalidOffset)
|
|
{
|
|
m_lasterror = wxSTREAM_EOF;
|
|
return 0;
|
|
}
|
|
|
|
size_t read = m_contentStream->Read(buffer, bufsize).LastRead();
|
|
m_pos += read;
|
|
|
|
if (m_contentStream->SeekI(m_pos) == wxInvalidOffset)
|
|
{
|
|
m_lasterror = wxSTREAM_READ_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
if (read != bufsize)
|
|
m_lasterror = m_contentStream->GetLastError();
|
|
|
|
return read;
|
|
}
|
|
|
|
|
|
|
|
|
|
wxFileOffset wxChmInputStream::OnSysSeek(wxFileOffset seek, wxSeekMode mode)
|
|
{
|
|
wxString mode_str;
|
|
|
|
if ( !m_contentStream || m_contentStream->Eof() )
|
|
{
|
|
m_lasterror = wxSTREAM_EOF;
|
|
return 0;
|
|
}
|
|
m_lasterror = wxSTREAM_NO_ERROR;
|
|
|
|
wxFileOffset nextpos;
|
|
|
|
switch ( mode )
|
|
{
|
|
case wxFromCurrent:
|
|
nextpos = seek + m_pos;
|
|
break;
|
|
case wxFromStart:
|
|
nextpos = seek;
|
|
break;
|
|
case wxFromEnd:
|
|
nextpos = m_size - 1 + seek;
|
|
break;
|
|
default:
|
|
nextpos = m_pos;
|
|
break; /* just to fool compiler, never happens */
|
|
}
|
|
m_pos=nextpos;
|
|
|
|
// Set current position on stream
|
|
m_contentStream->SeekI(m_pos);
|
|
return m_pos;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Help Browser tries to read the contents of the
|
|
* file by interpreting a .hhp file in the Archive.
|
|
* Because .chm doesn't include such a file, we need
|
|
* to rebuild the information based on stored
|
|
* system-files.
|
|
*/
|
|
void
|
|
wxChmInputStream::CreateHHPStream()
|
|
{
|
|
wxFileName file;
|
|
bool hhc = false;
|
|
bool hhk = false;
|
|
wxInputStream *i;
|
|
wxMemoryOutputStream *out;
|
|
const char *tmp;
|
|
|
|
// Try to open the #SYSTEM-File and create the HHP File out of it
|
|
// see http://bonedaddy.net/pabs3/chmspec/0.1.2/Internal.html#SYSTEM
|
|
if ( ! m_chm->Contains(wxT("/#SYSTEM")) )
|
|
{
|
|
#ifdef DEBUG
|
|
wxLogDebug("Archive doesn't contain #SYSTEM file");
|
|
#endif
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
file = wxFileName(wxT("/#SYSTEM"));
|
|
}
|
|
|
|
if ( CreateFileStream(wxT("/#SYSTEM")) )
|
|
{
|
|
// New stream for writing a memory area to simulate the
|
|
// .hhp-file
|
|
out = new wxMemoryOutputStream();
|
|
|
|
tmp = "[OPTIONS]\r\n";
|
|
out->Write((const void *) tmp, strlen(tmp));
|
|
|
|
wxUint16 code;
|
|
wxUint16 len;
|
|
void *buf;
|
|
|
|
// use the actual stream for reading
|
|
i = m_contentStream;
|
|
|
|
/* Now read the contents, and try to get the needed information */
|
|
|
|
// First 4 Bytes are Version information, skip
|
|
i->SeekI(4);
|
|
|
|
while (!i->Eof())
|
|
{
|
|
// Read #SYSTEM-Code and length
|
|
i->Read(&code, 2);
|
|
code = wxUINT16_SWAP_ON_BE( code ) ;
|
|
i->Read(&len, 2);
|
|
len = wxUINT16_SWAP_ON_BE( len ) ;
|
|
// data
|
|
buf = malloc(len);
|
|
i->Read(buf, len);
|
|
|
|
switch (code)
|
|
{
|
|
case 0: // CONTENTS_FILE
|
|
if (len)
|
|
{
|
|
tmp = "Contents file=";
|
|
hhc=true;
|
|
}
|
|
break;
|
|
case 1: // INDEX_FILE
|
|
tmp = "Index file=";
|
|
hhk = true;
|
|
break;
|
|
case 2: // DEFAULT_TOPIC
|
|
tmp = "Default Topic=";
|
|
break;
|
|
case 3: // TITLE
|
|
tmp = "Title=";
|
|
break;
|
|
// case 6: // COMPILED_FILE
|
|
// tmp = "Compiled File=";
|
|
// break;
|
|
case 7: // COMPILED_FILE
|
|
tmp = "Binary Index=YES\r\n";
|
|
out->Write( (const void *) tmp, strlen(tmp));
|
|
tmp = NULL;
|
|
break;
|
|
case 4: // STRUCT SYSTEM INFO
|
|
tmp = NULL ;
|
|
if ( len >= 28 )
|
|
{
|
|
char *structptr = (char*) buf ;
|
|
// LCID at position 0
|
|
wxUint32 dummy = *((wxUint32 *)(structptr+0)) ;
|
|
wxUint32 lcid = wxUINT32_SWAP_ON_BE( dummy ) ;
|
|
char msg[64];
|
|
int len = sprintf(msg, "Language=0x%X\r\n", lcid) ;
|
|
if (len > 0)
|
|
out->Write(msg, len) ;
|
|
}
|
|
break ;
|
|
default:
|
|
tmp=NULL;
|
|
}
|
|
|
|
if (tmp)
|
|
{
|
|
out->Write((const void *) tmp, strlen(tmp));
|
|
out->Write(buf, strlen((char*)buf));
|
|
out->Write("\r\n", 2);
|
|
}
|
|
|
|
free(buf);
|
|
buf=NULL;
|
|
}
|
|
|
|
|
|
// Free the old data which wont be used any more
|
|
delete m_contentStream;
|
|
if (m_content)
|
|
free (m_content);
|
|
|
|
// Now add entries which are missing
|
|
if ( !hhc && m_chm->Contains(wxT("*.hhc")) )
|
|
{
|
|
tmp = "Contents File=*.hhc\r\n";
|
|
out->Write((const void *) tmp, strlen(tmp));
|
|
}
|
|
|
|
if ( !hhk && m_chm->Contains(wxT("*.hhk")) )
|
|
{
|
|
tmp = "Index File=*.hhk\r\n";
|
|
out->Write((const void *) tmp, strlen(tmp));
|
|
}
|
|
|
|
// Now copy the Data from the memory
|
|
out->SeekO(0, wxFromEnd);
|
|
m_size = out->TellO();
|
|
out->SeekO(0, wxFromStart);
|
|
m_content = (char *) malloc (m_size+1);
|
|
out->CopyTo(m_content, m_size);
|
|
m_content[m_size]='\0';
|
|
m_size++;
|
|
m_contentStream = new wxMemoryInputStream(m_content, m_size);
|
|
|
|
delete out;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a Stream pointing to a virtual file in
|
|
* the current archive
|
|
*/
|
|
bool wxChmInputStream::CreateFileStream(const wxString& pattern)
|
|
{
|
|
wxFileInputStream * fin;
|
|
wxString tmpfile = wxFileName::CreateTempFileName(wxT("chmstrm"));
|
|
|
|
if ( tmpfile.empty() )
|
|
{
|
|
wxLogError(_("Could not create temporary file '%s'"), tmpfile);
|
|
return false;
|
|
}
|
|
|
|
// try to extract the file
|
|
if ( m_chm->Extract(pattern, tmpfile) <= 0 )
|
|
{
|
|
wxLogError(_("Extraction of '%s' into '%s' failed."),
|
|
pattern, tmpfile);
|
|
if ( wxFileExists(tmpfile) )
|
|
wxRemoveFile(tmpfile);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Open a filestream to extracted file
|
|
fin = new wxFileInputStream(tmpfile);
|
|
if (!fin->IsOk())
|
|
return false;
|
|
|
|
m_size = fin->GetSize();
|
|
m_content = (char *) malloc(m_size+1);
|
|
fin->Read(m_content, m_size);
|
|
m_content[m_size]='\0';
|
|
|
|
wxRemoveFile(tmpfile);
|
|
|
|
delete fin;
|
|
|
|
m_contentStream = new wxMemoryInputStream (m_content, m_size);
|
|
|
|
return m_contentStream->IsOk();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxChmFSHandler
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class wxChmFSHandler : public wxFileSystemHandler
|
|
{
|
|
public:
|
|
/// Constructor and Destructor
|
|
wxChmFSHandler();
|
|
virtual ~wxChmFSHandler() wxOVERRIDE;
|
|
|
|
/// Is able to open location?
|
|
virtual bool CanOpen(const wxString& location) wxOVERRIDE;
|
|
/// Open a file
|
|
virtual wxFSFile* OpenFile(wxFileSystem& fs, const wxString& location) wxOVERRIDE;
|
|
/// Find first occurrence of spec
|
|
virtual wxString FindFirst(const wxString& spec, int flags = 0) wxOVERRIDE;
|
|
/// Find next occurrence of spec
|
|
virtual wxString FindNext() wxOVERRIDE;
|
|
|
|
private:
|
|
int m_lasterror;
|
|
wxString m_pattern;
|
|
wxString m_found;
|
|
wxChmTools * m_chm;
|
|
};
|
|
|
|
wxChmFSHandler::wxChmFSHandler() : wxFileSystemHandler()
|
|
{
|
|
m_lasterror=0;
|
|
m_chm=NULL;
|
|
}
|
|
|
|
wxChmFSHandler::~wxChmFSHandler()
|
|
{
|
|
if (m_chm)
|
|
delete m_chm;
|
|
}
|
|
|
|
bool wxChmFSHandler::CanOpen(const wxString& location)
|
|
{
|
|
wxString p = GetProtocol(location);
|
|
return (p == wxT("chm")) &&
|
|
(GetProtocol(GetLeftLocation(location)) == wxT("file"));
|
|
}
|
|
|
|
wxFSFile* wxChmFSHandler::OpenFile(wxFileSystem& WXUNUSED(fs),
|
|
const wxString& location)
|
|
{
|
|
wxString right = GetRightLocation(location);
|
|
wxString left = GetLeftLocation(location);
|
|
|
|
wxInputStream *s;
|
|
|
|
int index;
|
|
|
|
if ( GetProtocol(left) != wxT("file") )
|
|
{
|
|
wxLogError(_("CHM handler currently supports only local files!"));
|
|
return NULL;
|
|
}
|
|
|
|
// Work around javascript
|
|
wxString tmp = wxString(right);
|
|
if ( tmp.MakeLower().Contains(wxT("javascipt")) && tmp.Contains(wxT("\'")) )
|
|
{
|
|
right = right.AfterFirst(wxT('\'')).BeforeLast(wxT('\''));
|
|
}
|
|
|
|
// now work on the right location
|
|
if (right.Contains(wxT("..")))
|
|
{
|
|
right = wxFileName(right).GetAbsolutePath(wxT("/"));
|
|
}
|
|
|
|
// a workaround for absolute links to root
|
|
if ( (index=right.Index(wxT("//"))) != wxNOT_FOUND )
|
|
{
|
|
right=wxString(right.Mid(index+1));
|
|
wxLogWarning(_("Link contained '//', converted to absolute link."));
|
|
}
|
|
|
|
wxFileName leftFilename = wxFileSystem::URLToFileName(left);
|
|
if (!leftFilename.FileExists())
|
|
return NULL;
|
|
|
|
// Open a stream to read the content of the chm-file
|
|
s = new wxChmInputStream(leftFilename.GetFullPath(), right, true);
|
|
|
|
if ( s )
|
|
{
|
|
return new wxFSFile(s,
|
|
left + wxT("#chm:") + right,
|
|
wxEmptyString,
|
|
GetAnchor(location),
|
|
wxDateTime(leftFilename.GetModificationTime()));
|
|
}
|
|
|
|
delete s;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Doku see wxFileSystemHandler
|
|
*/
|
|
wxString wxChmFSHandler::FindFirst(const wxString& spec, int WXUNUSED(flags))
|
|
{
|
|
wxString right = GetRightLocation(spec);
|
|
wxString left = GetLeftLocation(spec);
|
|
wxString nativename = wxFileSystem::URLToFileName(left).GetFullPath();
|
|
|
|
if ( GetProtocol(left) != wxT("file") )
|
|
{
|
|
wxLogError(_("CHM handler currently supports only local files!"));
|
|
return wxEmptyString;
|
|
}
|
|
|
|
m_chm = new wxChmTools(wxFileName(nativename));
|
|
m_pattern = right.AfterLast(wxT('/'));
|
|
|
|
wxString m_found = m_chm->Find(m_pattern);
|
|
|
|
// now fake around hhp-files which are not existing in projects...
|
|
if (m_found.empty() &&
|
|
m_pattern.Contains(wxT(".hhp")) &&
|
|
!m_pattern.Contains(wxT(".hhp.cached")))
|
|
{
|
|
m_found.Printf(wxT("%s#chm:%s.hhp"),
|
|
left, m_pattern.BeforeLast(wxT('.')));
|
|
}
|
|
|
|
return m_found;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxString wxChmFSHandler::FindNext()
|
|
{
|
|
if (m_pattern.empty())
|
|
return wxEmptyString;
|
|
else
|
|
return m_chm->Find(m_pattern, m_found);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// wxModule to register CHM handler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class wxChmSupportModule : public wxModule
|
|
{
|
|
wxDECLARE_DYNAMIC_CLASS(wxChmSupportModule);
|
|
|
|
public:
|
|
virtual bool OnInit() wxOVERRIDE
|
|
{
|
|
wxFileSystem::AddHandler(new wxChmFSHandler);
|
|
return true;
|
|
}
|
|
virtual void OnExit() wxOVERRIDE {}
|
|
}
|
|
;
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxChmSupportModule, wxModule);
|
|
|
|
#endif // wxUSE_LIBMSPACK
|