using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Globalization;
namespace HtmlHelp.ChmDecoding
{
///
/// The class CHMSystem reads the #SYSTEM file of the chm and stores its settings
///
internal sealed class CHMSystem : IDisposable
{
///
/// Internal flag specifying if the object is going to be disposed
///
private bool disposed = false;
///
/// Internal member storing the binary file data
///
private byte[] _binaryFileData = null;
///
/// Internal member storing the file version
///
private int _fileVersion = 0;
///
/// Internal member storing the contents file path
///
private string _contentsFile = "";
///
/// Internal member storing the index file path
///
private string _indexFile = "";
///
/// Internal member storing the default help topic
///
private string _defaultTopic = "";
///
/// Internal member storing the help-window title
///
private string _title = "";
///
/// Internal flag if dbcs is on
///
private bool _dbcs = false;
///
/// Internal flag if fulltext search is enabled
///
private bool _fullTextSearch = false;
///
/// Internal flag if KLinks are in the file
///
private bool _hasKLinks = false;
///
/// Internal flag if ALinks are in the file
///
private bool _hasALinks = false;
///
/// Internal member storing the name of the default window
///
private string _defaultWindow = "";
///
/// Internal member storing the filename of the compiled file
///
private string _compileFile = "";
///
/// Internal flag storing the offset value of the binary index
///
private uint _binaryIndexURLTableID = 0;
///
/// Inernal member storing the compiler version this file was compiled
///
private string _compilerVersion = "";
///
/// Internal flag storing the offset value of the binary TOC
///
private uint _binaryTOCURLTableID = 0;
///
/// Internal member storing the associated chmfile object
///
private CHMFile _associatedFile = null;
///
/// Internal member storing the default fontface, size, charset
///
private string _defaultFont = "";
///
/// Internal member storing the culture info of the file
///
private CultureInfo _culture;
///
/// Constructor of the class
///
/// binary file data of the #SYSTEM file
/// associated chm file
public CHMSystem(byte[] binaryFileData, CHMFile associatedFile)
{
_binaryFileData = binaryFileData;
_associatedFile = associatedFile;
DecodeData();
if(_culture == null)
{
// Set the text encoder of the chm file to the read charset/codepage
_associatedFile.TextEncoding = Encoding.GetEncoding( this.CodePage );
}
}
///
/// Decodes the binary file data and fills the internal properties
///
/// true if succeeded
private bool DecodeData()
{
bool bRet = true;
MemoryStream memStream = new MemoryStream(_binaryFileData);
BinaryReader binReader = new BinaryReader(memStream);
// First entry = DWORD for version number
_fileVersion = (int) binReader.ReadInt32();
while( (memStream.Position < memStream.Length) && (bRet) )
{
bRet &= DecodeEntry(ref binReader);
}
return bRet;
}
///
/// Decodes an #system file entry
///
/// binary reader reference
/// true if succeeded
private bool DecodeEntry(ref BinaryReader binReader)
{
bool bRet = true;
int code = (int) binReader.ReadInt16(); // entry code, WORD
int length = (int) binReader.ReadInt16(); // length of entry
switch(code)
{
case 0:
{
_contentsFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 1:
{
_indexFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 2:
{
_defaultTopic = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 3:
{
_title = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 4:
{
int nTemp = 0;
nTemp = binReader.ReadInt32(); // read DWORD LCID
_culture = new CultureInfo(nTemp);
if(_culture != null)
_associatedFile.TextEncoding = Encoding.GetEncoding(_culture.TextInfo.ANSICodePage);
nTemp = binReader.ReadInt32(); // read DWORD DBCS
_dbcs = (nTemp == 1);
nTemp = binReader.ReadInt32(); // read DWORD Fulltext search
_fullTextSearch = (nTemp == 1);
nTemp = binReader.ReadInt32(); // read DWORD has klinks
_hasKLinks = (nTemp != 0);
nTemp = binReader.ReadInt32(); // read DWORD has alinks
_hasALinks = (nTemp != 0);
// read the rest of code 4 (not important for us)
byte[] temp = new byte[length-(5*4)];
temp = binReader.ReadBytes(length-(5*4));
};break;
case 5:
{
_defaultWindow = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 6:
{
_compileFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 7:
{
if(_fileVersion > 2)
{
_binaryIndexURLTableID = (uint) binReader.ReadInt32();
}
else
{
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
}
};break;
case 8:
{
// abbreviation (not interresting for us)
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 9:
{
_compilerVersion = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
case 10:
{
// timestamp of the file (not interresting for us)
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 11:
{
if(_fileVersion > 2)
{
_binaryTOCURLTableID = (uint) binReader.ReadInt32();
}
else
{
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
}
};break;
case 12:
{
// number of information bytes
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 13:
{
// copy of file #idxhdr
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 14:
{
// custom tabs for HH viewer
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 15:
{
// a checksum
byte[] read = binReader.ReadBytes(length);
int i=read.Length;
};break;
case 16:
{
// Default Font=string,number,number
// The string is the name of the font, the first number is the
// point size & the last number is the character set used by the font.
// For acceptable values see *_CHARSET defines in wingdi.h from the
// Windows SDK or the same file in MinGW or Wine.
// Most of the time you will only want to use 0, which is the value for ANSI,
// which is the subset of ASCII used by Windows.
_defaultFont = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding);
};break;
default:
{
byte[] temp = new byte[length];
temp = binReader.ReadBytes(length);
//bRet = false;
int i=temp.Length;
};break;
}
return bRet;
}
///
/// Reads all HHC files and checks which one has the global object tag.
/// This hhc file will be returned as master hhc
///
/// list of topics containing the extension hhc
/// true if the arraylist contains topic items
/// the filename of the found master toc
private string GetMasterHHC(ArrayList hhcTopics, bool TopicItemArrayList)
{
string sRet = "";
if( (hhcTopics!=null) && (hhcTopics.Count > 0) )
{
if( TopicItemArrayList )
{
if(hhcTopics.Count == 1)
{
sRet = ((TopicEntry)hhcTopics[0]).Locale;
}
else
{
foreach(TopicEntry curEntry in hhcTopics)
{
CHMStream.CHMStream iw=null;
MemoryStream fileObject=null;
if( _associatedFile.CurrentStorageWrapper == null)
{
iw=new CHMStream.CHMStream();
iw.OpenCHM(_associatedFile.ChmFilePath);
}
else
{
iw = _associatedFile.CurrentStorageWrapper;
}
fileObject = iw.OpenStream(curEntry.Locale);
if( fileObject != null)
{
string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length);
fileObject.Close();
if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) )
{
sRet = curEntry.Locale;
break;
}
}
}
}
}
else
{
if(hhcTopics.Count == 1)
{
sRet = ((string)hhcTopics[0]);
}
else
{
foreach(string curEntry in hhcTopics)
{
CHMStream.CHMStream iw=null;
MemoryStream fileObject=null;
if( _associatedFile.CurrentStorageWrapper == null)
{
iw=new CHMStream.CHMStream();
iw.OpenCHM(_associatedFile.ChmFilePath);
}
else
{
iw = _associatedFile.CurrentStorageWrapper;
}
fileObject = iw.OpenStream(curEntry);
if( fileObject != null)
{
string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length);
fileObject.Close();
if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) )
{
sRet = curEntry;
break;
}
}
}
}
}
}
return sRet;
}
///
/// Gets the file version of the chm file.
/// 2 for Compatibility=1.0, 3 for Compatibility=1.1
///
public int FileVersion
{
get { return _fileVersion; }
}
///
/// Gets the contents file name
///
public string ContentsFile
{
get
{
if( BinaryTOC ) // if the file contains a binary TOC
{
// make sure the CHMFile instance exists and has loaded the file #URLTBL
if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) )
{
// Get an url-table entry by its unique id
UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryTOCURLTableID );
if(entry != null)
{
// entry found, return the url ( = filename )
return entry.URL;
}
}
}
else
{
if(_contentsFile.Length <= 0)
{
string sCheck = "Table of Contents.hhc"; // default HHP contents filename
if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) )
{
TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck );
if( te == null)
{
sCheck = "toc.hhc"; // default HHP contents filename
te = _associatedFile.TopicsFile.GetByLocale( sCheck );
if( te == null)
{
sCheck = CompileFile + ".hhc";
te = _associatedFile.TopicsFile.GetByLocale( sCheck );
if( te == null)
{
ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhc");
if( arrExt == null )
{
arrExt = _associatedFile.EnumFilesByExtension("hhc");
if( arrExt == null )
{
Debug.WriteLine("CHMSystem.ContentsFile - Failed, contents file not found !");
}
else
{
if(arrExt.Count > 1)
{
sCheck = GetMasterHHC(arrExt, false);
_contentsFile = sCheck;
}
else
{
_contentsFile = ((string)arrExt[0]);
sCheck = _contentsFile;
}
}
}
else
{
if(arrExt.Count > 1)
{
sCheck = GetMasterHHC(arrExt, true);
_contentsFile = sCheck;
}
else
{
_contentsFile = ((TopicEntry)arrExt[0]).Locale;
sCheck = _contentsFile;
}
}
}
else
{
_contentsFile = sCheck;
}
}
else
{
_contentsFile = sCheck;
}
}
else
{
_contentsFile = sCheck;
}
}
return sCheck;
}
}
return _contentsFile;
}
}
///
/// Gets the index file name
///
public string IndexFile
{
get
{
if( BinaryIndex ) // if the file contains a binary index
{
// make sure the CHMFile instance exists and has loaded the file #URLTBL
if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) )
{
// Get an url-table entry by its unique id
UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryIndexURLTableID );
if(entry != null)
{
// entry found, return the url ( = filename )
return entry.URL;
}
}
}
else
{
if(_indexFile.Length <= 0)
{
string sCheck = "Index.hhk"; // default HHP index filename
if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) )
{
TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck );
if( te == null)
{
sCheck = CompileFile + ".hhk";
te = _associatedFile.TopicsFile.GetByLocale( sCheck );
if( te == null)
{
ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhk");
if( arrExt == null )
{
Debug.WriteLine("CHMSystem.IndexFile - Failed, index file not found !");
}
else
{
_indexFile = ((TopicEntry)arrExt[0]).Locale;
sCheck = _indexFile;
}
}
else
{
_indexFile = sCheck;
}
}
else
{
_indexFile = sCheck;
}
}
return sCheck;
}
}
return _indexFile;
}
}
///
/// Sets the default topic of this file
///
/// new local value of the topic
internal void SetDefaultTopic(string local)
{
_defaultTopic = local;
}
///
/// Gets the default help topic
///
public string DefaultTopic
{
get { return _defaultTopic; }
}
///
/// Gets the title of the help window
///
public string Title
{
get { return _title; }
}
///
/// Gets the flag if DBCS is in use
///
public bool DBCS
{
get { return _dbcs; }
}
///
/// Gets the flag if full-text-search is available
///
public bool FullTextSearch
{
get { return _fullTextSearch; }
}
///
/// Gets the flag if the file has ALinks
///
public bool HasALinks
{
get { return _hasALinks; }
}
///
/// Gets the flag if the file has KLinks
///
public bool HasKLinks
{
get { return _hasKLinks; }
}
///
/// Gets the default window name
///
public string DefaultWindow
{
get { return _defaultWindow; }
}
///
/// Gets the file name of the compile file
///
public string CompileFile
{
get { return _compileFile; }
}
///
/// Gets the id of the binary index in the url table
///
public uint BinaryIndexURLTableID
{
get { return _binaryIndexURLTableID; }
}
///
/// Gets the flag if the chm has a binary index file
///
public bool BinaryIndex
{
get { return (_binaryIndexURLTableID>0); }
}
///
/// Gets the flag if the chm has a binary index file
///
public string CompilerVersion
{
get { return _compilerVersion; }
}
///
/// Gets the id of the binary toc in the url table
///
public uint BinaryTOCURLTableID
{
get { return _binaryTOCURLTableID; }
}
///
/// Gets the flag if the chm has a binary toc file
///
public bool BinaryTOC
{
get { return (_binaryTOCURLTableID>0); }
}
///
/// Gets the font face of the read font property.
/// Empty string for default font.
///
public string FontFace
{
get
{
if( _defaultFont.Length > 0)
{
string [] fontSplit = _defaultFont.Split( new char[]{','});
if(fontSplit.Length > 0)
return fontSplit[0].Trim();
}
return "";
}
}
///
/// Gets the font size of the read font property.
/// 0 for default font size
///
public double FontSize
{
get
{
if( _defaultFont.Length > 0)
{
string [] fontSplit = _defaultFont.Split( new char[]{','});
if(fontSplit.Length > 1)
return double.Parse(fontSplit[1].Trim());
}
return 0.0;
}
}
///
/// Gets the character set of the read font property
/// 1 for default
///
public int CharacterSet
{
get
{
if( _defaultFont.Length > 0)
{
string [] fontSplit = _defaultFont.Split( new char[]{','});
if(fontSplit.Length > 2)
return Int32.Parse(fontSplit[2].Trim());
}
return 0;
}
}
///
/// Gets the codepage depending on the read font property
///
public int CodePage
{
get
{
// if we've read a LCID from the system file
// ignore the font-property settings and return
// the codepage generated from the culture info
if(_culture != null)
{
return _culture.TextInfo.ANSICodePage;
}
int nRet = 1252; // default codepage windows-1252
int nCSet = CharacterSet;
switch(nCSet)
{
case 0x00: nRet = 1252;break; // ANSI_CHARSET
case 0xCC: nRet = 1251;break; // RUSSIAN_CHARSET
case 0xEE: nRet = 1250;break; // EE_CHARSET
case 0xA1: nRet = 1253;break; // GREEK_CHARSET
case 0xA2: nRet = 1254;break; // TURKISH_CHARSET
case 0xBA: nRet = 1257;break; // BALTIC_CHARSET
case 0xB1: nRet = 1255;break; // HEBREW_CHARSET
case 0xB2: nRet = 1256;break; // ARABIC_CHARSET
case 0x80: nRet = 932;break; // SHIFTJIS_CHARSET
case 0x81: nRet = 949;break; // HANGEUL_CHARSET
case 0x86: nRet = 936;break; // GB2313_CHARSET
case 0x88: nRet = 950;break; // CHINESEBIG5_CHARSET
}
return nRet;
}
}
///
/// Gets the assiciated culture info
///
public CultureInfo Culture
{
get { return _culture; }
}
///
/// Implement IDisposable.
///
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
///
/// Dispose(bool disposing) executes in two distinct scenarios.
/// If disposing equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.
/// If disposing equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects. Only unmanaged resources can be disposed.
///
/// disposing flag
private void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if(!this.disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if(disposing)
{
// Dispose managed resources.
_binaryFileData = null;
}
}
disposed = true;
}
}
}