3 using System.Collections;
4 using System.Collections.Specialized;
6 namespace HtmlHelp.ChmDecoding
9 /// The class <c>CHMBtree</c> implements methods/properties to decode the binary help index.
10 /// This class automatically creates an index arraylist for the current CHMFile instance.
11 /// It does not store the index internally !
13 /// <remarks>The binary index can be found in the storage file $WWKeywordLinks/BTree</remarks>
14 internal sealed class CHMBtree : IDisposable
17 /// Constant specifying the size of the string blocks
19 private const int BLOCK_SIZE = 2048;
21 /// Internal flag specifying if the object is going to be disposed
23 private bool disposed = false;
25 /// Internal member storing the binary file data
27 private byte[] _binaryFileData = null;
29 /// Internal member storing flags
31 private int _flags = 0;
33 /// Internal member storing the data format
35 private byte[] _dataFormat = new byte[16];
37 /// Internal member storing the index of the last listing block
39 private int _indexOfLastListingBlock = 0;
41 /// Internal member storing the index of the root block
43 private int _indexOfRootBlock = 0;
45 /// Internal member storing the number of blocks
47 private int _numberOfBlocks = 0;
49 /// Internal member storing the tree depth.
50 /// (1 if no index blocks, 2 one level of index blocks, ...)
52 private int _treeDepth = 0;
54 /// Internal member storing the number of keywords in the file
56 private int _numberOfKeywords = 0;
58 /// Internal member storing the codepage
60 private int _codePage = 0;
62 /// true if the index is from a CHI or CHM file, else CHW
64 private bool _isCHI_CHM = true;
66 /// Internal member storing the associated chmfile object
68 private CHMFile _associatedFile = null;
70 /// Internal flag specifying if we have to read listing or index blocks
72 private bool _readListingBlocks = true;
74 /// Internal member storing an indexlist of the current file.
76 private ArrayList _indexList = new ArrayList();
79 /// Constructor of the class
81 /// <param name="binaryFileData">binary file data of the $WWKeywordLinks/BTree file</param>
82 /// <param name="associatedFile">associated chm file</param>
83 public CHMBtree(byte[] binaryFileData, CHMFile associatedFile)
85 if( associatedFile == null)
87 throw new ArgumentException("CHMBtree.ctor() - Associated CHMFile must not be null !", "associatedFile");
90 _binaryFileData = binaryFileData;
91 _associatedFile = associatedFile;
94 // clear internal binary data after extraction
95 _binaryFileData = null;
99 /// Decodes the binary file data and fills the internal properties
101 /// <returns>true if succeeded</returns>
102 private bool DecodeData()
106 MemoryStream memStream = new MemoryStream(_binaryFileData);
107 BinaryReader binReader = new BinaryReader(memStream);
113 binReader.ReadChars(2); // 2chars signature (not important)
115 _flags = (int)binReader.ReadInt16(); // WORD flags
117 binReader.ReadInt16(); // size of blocks (always 2048)
119 _dataFormat = binReader.ReadBytes(16);
121 binReader.ReadInt32(); // unknown DWORD
123 _indexOfLastListingBlock = binReader.ReadInt32();
124 _indexOfRootBlock = binReader.ReadInt32();
126 binReader.ReadInt32(); // unknown DWORD
128 _numberOfBlocks = binReader.ReadInt32();
129 _treeDepth = binReader.ReadInt16();
130 _numberOfKeywords = binReader.ReadInt32();
131 _codePage = binReader.ReadInt32();
133 binReader.ReadInt32(); // lcid DWORD
135 nTemp = binReader.ReadInt32();
136 _isCHI_CHM = (nTemp==1);
138 binReader.ReadInt32(); // unknown DWORD
139 binReader.ReadInt32(); // unknown DWORD
140 binReader.ReadInt32(); // unknown DWORD
141 binReader.ReadInt32(); // unknown DWORD
143 // end of header decode
145 while( (memStream.Position < memStream.Length) && (bRet) )
147 nCurOffset = (int)memStream.Position;
148 byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE);
149 bRet &= DecodeBlock(dataBlock, ref nCurOffset, _treeDepth-1);
156 /// Decodes a block of url-string data
158 /// <param name="dataBlock">block of data</param>
159 /// <param name="nOffset">current file offset</param>
160 /// <param name="indexBlocks">number of index blocks</param>
161 /// <returns>true if succeeded</returns>
162 private bool DecodeBlock( byte[] dataBlock, ref int nOffset, int indexBlocks )
165 int nblockOffset = nOffset;
167 MemoryStream memStream = new MemoryStream(dataBlock);
168 BinaryReader binReader = new BinaryReader(memStream);
170 int freeSpace = binReader.ReadInt16(); // length of freespace
171 int nrOfEntries = binReader.ReadInt16(); // number of entries
173 bool bListingEndReached = false;
175 //while( (memStream.Position < (memStream.Length-freeSpace)) && (bRet) )
177 int nIndexOfPrevBlock = -1;
178 int nIndexOfNextBlock = -1;
179 int nIndexOfChildBlock = 0;
181 if(_readListingBlocks)
183 nIndexOfPrevBlock = binReader.ReadInt32(); // -1 if this is the header
184 nIndexOfNextBlock = binReader.ReadInt32(); // -1 if this is the last block
188 nIndexOfChildBlock = binReader.ReadInt32();
191 for(int nE = 0; nE < nrOfEntries; nE++)
193 if(_readListingBlocks)
195 bListingEndReached = (nIndexOfNextBlock==-1);
197 string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
199 bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0);
201 int indent = binReader.ReadInt16(); // indent of entry
202 int nCharIndex = binReader.ReadInt32();
204 binReader.ReadInt32();
206 int numberOfPairs = binReader.ReadInt32();
208 int[] nTopics = new int[numberOfPairs];
209 string[] seeAlso = new string[numberOfPairs];
211 for(int i=0; i < numberOfPairs; i++)
215 seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
219 nTopics[i] = binReader.ReadInt32();
223 binReader.ReadInt32(); // unknown
225 int nIndexOfThisEntry = binReader.ReadInt32();
227 IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics);
228 _indexList.Add(newItem);
232 string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
234 bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0);
236 int indent = binReader.ReadInt16(); // indent of entry
237 int nCharIndex = binReader.ReadInt32();
239 binReader.ReadInt32();
241 int numberOfPairs = binReader.ReadInt32();
243 int[] nTopics = new int[numberOfPairs];
244 string[] seeAlso = new string[numberOfPairs];
246 for(int i=0; i < numberOfPairs; i++)
250 seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding);
254 nTopics[i] = binReader.ReadInt32();
258 int nIndexChild = binReader.ReadInt32();
259 int nIndexOfThisEntry=-1;
261 IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics);
262 _indexList.Add(newItem);
268 binReader.ReadBytes(freeSpace);
271 if( bListingEndReached )
272 _readListingBlocks = false;
278 /// Gets the internal generated index list
280 internal ArrayList IndexList
282 get { return _indexList; }
286 /// Implement IDisposable.
288 public void Dispose()
291 // This object will be cleaned up by the Dispose method.
292 // Therefore, you should call GC.SupressFinalize to
293 // take this object off the finalization queue
294 // and prevent finalization code for this object
295 // from executing a second time.
296 GC.SuppressFinalize(this);
300 /// Dispose(bool disposing) executes in two distinct scenarios.
301 /// If disposing equals true, the method has been called directly
302 /// or indirectly by a user's code. Managed and unmanaged resources
304 /// If disposing equals false, the method has been called by the
305 /// runtime from inside the finalizer and you should not reference
306 /// other objects. Only unmanaged resources can be disposed.
308 /// <param name="disposing">disposing flag</param>
309 private void Dispose(bool disposing)
311 // Check to see if Dispose has already been called.
314 // If disposing equals true, dispose all managed
315 // and unmanaged resources.
318 // Dispose managed resources.
319 _binaryFileData = null;