using System;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
namespace HtmlHelp.ChmDecoding
{
///
/// The class CHMBtree implements methods/properties to decode the binary help index.
/// This class automatically creates an index arraylist for the current CHMFile instance.
/// It does not store the index internally !
///
/// The binary index can be found in the storage file $WWKeywordLinks/BTree
internal sealed class CHMBtree : IDisposable
{
///
/// Constant specifying the size of the string blocks
///
private const int BLOCK_SIZE = 2048;
///
/// 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 flags
///
private int _flags = 0;
///
/// Internal member storing the data format
///
private byte[] _dataFormat = new byte[16];
///
/// Internal member storing the index of the last listing block
///
private int _indexOfLastListingBlock = 0;
///
/// Internal member storing the index of the root block
///
private int _indexOfRootBlock = 0;
///
/// Internal member storing the number of blocks
///
private int _numberOfBlocks = 0;
///
/// Internal member storing the tree depth.
/// (1 if no index blocks, 2 one level of index blocks, ...)
///
private int _treeDepth = 0;
///
/// Internal member storing the number of keywords in the file
///
private int _numberOfKeywords = 0;
///
/// Internal member storing the codepage
///
private int _codePage = 0;
///
/// true if the index is from a CHI or CHM file, else CHW
///
private bool _isCHI_CHM = true;
///
/// Internal member storing the associated chmfile object
///
private CHMFile _associatedFile = null;
///
/// Internal flag specifying if we have to read listing or index blocks
///
private bool _readListingBlocks = true;
///
/// Internal member storing an indexlist of the current file.
///
private ArrayList _indexList = new ArrayList();
///
/// Constructor of the class
///
/// binary file data of the $WWKeywordLinks/BTree file
/// associated chm file
public CHMBtree(byte[] binaryFileData, CHMFile associatedFile)
{
if( associatedFile == null)
{
throw new ArgumentException("CHMBtree.ctor() - Associated CHMFile must not be null !", "associatedFile");
}
_binaryFileData = binaryFileData;
_associatedFile = associatedFile;
DecodeData();
// clear internal binary data after extraction
_binaryFileData = null;
}
///
/// 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);
int nCurOffset = 0;
int nTemp = 0;
// decode header
binReader.ReadChars(2); // 2chars signature (not important)
_flags = (int)binReader.ReadInt16(); // WORD flags
binReader.ReadInt16(); // size of blocks (always 2048)
_dataFormat = binReader.ReadBytes(16);
binReader.ReadInt32(); // unknown DWORD
_indexOfLastListingBlock = binReader.ReadInt32();
_indexOfRootBlock = binReader.ReadInt32();
binReader.ReadInt32(); // unknown DWORD
_numberOfBlocks = binReader.ReadInt32();
_treeDepth = binReader.ReadInt16();
_numberOfKeywords = binReader.ReadInt32();
_codePage = binReader.ReadInt32();
binReader.ReadInt32(); // lcid DWORD
nTemp = binReader.ReadInt32();
_isCHI_CHM = (nTemp==1);
binReader.ReadInt32(); // unknown DWORD
binReader.ReadInt32(); // unknown DWORD
binReader.ReadInt32(); // unknown DWORD
binReader.ReadInt32(); // unknown DWORD
// end of header decode
while( (memStream.Position < memStream.Length) && (bRet) )
{
nCurOffset = (int)memStream.Position;
byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE);
bRet &= DecodeBlock(dataBlock, ref nCurOffset, _treeDepth-1);
}
return bRet;
}
///
/// Decodes a block of url-string data
///
/// block of data
/// current file offset
/// number of index blocks
/// true if succeeded
private bool DecodeBlock( byte[] dataBlock, ref int nOffset, int indexBlocks )
{
bool bRet = true;
int nblockOffset = nOffset;
MemoryStream memStream = new MemoryStream(dataBlock);
BinaryReader binReader = new BinaryReader(memStream);
int freeSpace = binReader.ReadInt16(); // length of freespace
int nrOfEntries = binReader.ReadInt16(); // number of entries
bool bListingEndReached = false;
//while( (memStream.Position < (memStream.Length-freeSpace)) && (bRet) )
//{
int nIndexOfPrevBlock = -1;
int nIndexOfNextBlock = -1;
int nIndexOfChildBlock = 0;
if(_readListingBlocks)
{
nIndexOfPrevBlock = binReader.ReadInt32(); // -1 if this is the header
nIndexOfNextBlock = binReader.ReadInt32(); // -1 if this is the last block
}
else
{
nIndexOfChildBlock = binReader.ReadInt32();
}
for(int nE = 0; nE < nrOfEntries; nE++)
{
if(_readListingBlocks)
{
bListingEndReached = (nIndexOfNextBlock==-1);
string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0);
int indent = binReader.ReadInt16(); // indent of entry
int nCharIndex = binReader.ReadInt32();
binReader.ReadInt32();
int numberOfPairs = binReader.ReadInt32();
int[] nTopics = new int[numberOfPairs];
string[] seeAlso = new string[numberOfPairs];
for(int i=0; i < numberOfPairs; i++)
{
if(isSeeAlsoKeyword)
{
seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
}
else
{
nTopics[i] = binReader.ReadInt32();
}
}
binReader.ReadInt32(); // unknown
int nIndexOfThisEntry = binReader.ReadInt32();
IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics);
_indexList.Add(newItem);
}
else
{
string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0);
int indent = binReader.ReadInt16(); // indent of entry
int nCharIndex = binReader.ReadInt32();
binReader.ReadInt32();
int numberOfPairs = binReader.ReadInt32();
int[] nTopics = new int[numberOfPairs];
string[] seeAlso = new string[numberOfPairs];
for(int i=0; i < numberOfPairs; i++)
{
if(isSeeAlsoKeyword)
{
seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
}
else
{
nTopics[i] = binReader.ReadInt32();
}
}
int nIndexChild = binReader.ReadInt32();
int nIndexOfThisEntry=-1;
IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics);
_indexList.Add(newItem);
}
}
//}
binReader.ReadBytes(freeSpace);
if( bListingEndReached )
_readListingBlocks = false;
return bRet;
}
///
/// Gets the internal generated index list
///
internal ArrayList IndexList
{
get { return _indexList; }
}
///
/// 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;
}
}
}