summaryrefslogtreecommitdiff
path: root/packages/chm
diff options
context:
space:
mode:
authormarco <marco@3ad0048d-3df7-0310-abae-a5850022a9f2>2009-10-10 18:07:31 +0000
committermarco <marco@3ad0048d-3df7-0310-abae-a5850022a9f2>2009-10-10 18:07:31 +0000
commit6cd6e0c6af6a99f17503486db0274f5ef3d18852 (patch)
treea208d53f0e4a2eae91b1cb66d382a49a8fb13489 /packages/chm
parent1d54a359b23e2f7131f820e31f70c65dfa453c1a (diff)
downloadfpc-6cd6e0c6af6a99f17503486db0274f5ef3d18852.tar.gz
* Binary Index support
git-svn-id: http://svn.freepascal.org/svn/fpc/trunk@13835 3ad0048d-3df7-0310-abae-a5850022a9f2
Diffstat (limited to 'packages/chm')
-rw-r--r--packages/chm/src/chmfilewriter.pas17
-rw-r--r--packages/chm/src/chmreader.pas223
-rw-r--r--packages/chm/src/chmwriter.pas477
3 files changed, 708 insertions, 9 deletions
diff --git a/packages/chm/src/chmfilewriter.pas b/packages/chm/src/chmfilewriter.pas
index a5c4de682f..218de71596 100644
--- a/packages/chm/src/chmfilewriter.pas
+++ b/packages/chm/src/chmfilewriter.pas
@@ -102,13 +102,28 @@ var
IndexStream: TFileStream;
TOCStream: TFileStream;
Writer: TChmWriter;
- TOCSitemap: TChmSiteMap;
+ TOCSitemap : TChmSiteMap;
+ IndexSiteMap: TChmSiteMap;
begin
// Assign the TOC and index files
Writer := TChmWriter(Sender);
+ {$ifdef chmindex}
+ Writeln('binindex filename ',IndexFileName);
+ {$endif}
if (IndexFileName <> '') and FileExists(IndexFileName) then begin
IndexStream := TFileStream.Create(IndexFileName, fmOpenRead);
Writer.AppendIndex(IndexStream);
+ if MakeBinaryIndex then
+ begin
+ {$ifdef chmindex}
+ Writeln('into binindex ');
+ {$endif}
+ IndexStream.Position := 0;
+ IndexSitemap := TChmSiteMap.Create(stIndex);
+ indexSitemap.LoadFromStream(IndexStream);
+ Writer.AppendBinaryIndexFromSiteMap(IndexSitemap,False);
+ IndexSitemap.Free;
+ end;
IndexStream.Free;
end;
if (TableOfContentsFileName <> '') and FileExists(TableOfContentsFileName) then begin
diff --git a/packages/chm/src/chmreader.pas b/packages/chm/src/chmreader.pas
index 3f57b0fca0..4b4c994781 100644
--- a/packages/chm/src/chmreader.pas
+++ b/packages/chm/src/chmreader.pas
@@ -876,14 +876,60 @@ begin
end;
end;
+const DefBlockSize = 2048;
+
+function LoadBtreeHeader(m:TMemoryStream;var btreehdr:TBtreeHeader):boolean;
+
+begin
+ if m.size<sizeof(TBtreeHeader) Then
+ Exit(False);
+ result:=true;
+ m.read(btreeHdr,sizeof(TBtreeHeader));
+ {$IFDEF ENDIAN_BIG}
+ btreehdr.flags :=LEToN(btreehdr.flags);
+ btreehdr.blocksize :=LEToN(btreehdr.blocksize);
+ btreehdr.lastlstblock :=LEToN(btreehdr.lastlstblock);
+ btreehdr.indexrootblock:=LEToN(btreehdr.indexrootblock);
+ btreehdr.nrblock :=LEToN(btreehdr.nrblock);
+ btreehdr.treedepth :=LEToN(btreehdr.treedepth);
+ btreehdr.nrkeywords :=LEToN(btreehdr.nrkeywords);
+ btreehdr.codepage :=LEToN(btreehdr.codepage);
+ btreehdr.lcid :=LEToN(btreehdr.lcid);
+ btreehdr.ischm :=LEToN(btreehdr.ischm);
+ {$endif}
+end;
+
+function readwcharstring(var head:pbyte;tail:pbyte;var readv : ansistring):boolean;
+
+var pw : PWord;
+ oldhead : PByte;
+ ws : WideString;
+ n : Integer;
+begin
+ oldhead:=head;
+ pw:=pword(head);
+ while (pw<pword(tail)) and (pw^<>word(0)) do
+ inc(pw);
+ inc(pw); // skip #0#0.
+ head:=pbyte(pw);
+ result:=head<tail;
+
+ n:=head-oldhead;
+ setlength(ws,n div sizeof(widechar));
+ move(oldhead^,ws[1],n);
+ for n:=1 to length(ws) do
+ word(ws[n]):=LEToN(word(ws[n]));
+ readv:=ws; // force conversion for now, and hope it doesn't require cwstring
+end;
+
function TChmReader.GetIndexSitemap(ForceXML:boolean=false): TChmSiteMap;
-var Index : TMemoryStream;
+var Index : TMemoryStream;
+ sitemap : TChmSiteMap;
+ Item : TChmSiteMapItem;
+
+function AbortAndTryTextual:tchmsitemap;
+
begin
- Result := nil;
- // First Try Binary
- Index := nil; // GetObject('/$WWKeywordLinks/BTree');
- if (Index = nil) or ForceXML then
- begin
if Assigned(Index) Then Index.Free;
// Second Try text Index
Index := GetObject(IndexFile);
@@ -893,8 +939,173 @@ begin
Result.LoadFromStream(Index);
Index.Free;
end;
+end;
+
+procedure createentry(Name:ansistring;CharIndex:integer;Topic,Title:ansistring);
+var litem : TChmSiteMapItem;
+ shortname : ansistring;
+ longpart : ansistring;
+begin
+ if charindex=0 then
+ begin
+ item:=sitemap.items.NewItem;
+ item.keyword:=Name;
+ item.local:=topic;
+ item.text:=title;
+ end
+ else
+ begin
+ shortname:=copy(name,1,charindex-2);
+ longpart:=copy(name,charindex,length(name)-charindex+1);
+ if assigned(item) and (shortname=item.text) then
+ begin
+ litem:=item.children.newitem;
+ litem.local:=topic;
+ litem.keyword :=longpart; // recursively split this? No examples.
+ litem.text:=title;
+ end
+ else
+ begin
+ item:=sitemap.items.NewItem;
+ item.keyword:=shortname;
+ item.local:=topic;
+ item.text:=title;
+ litem:=item.children.newitem;
+ litem.keyword:=longpart;
+ litem.local:=topic;
+ litem.text :=Title; // recursively split this? No examples.
+ end;
+ end;
+end;
+
+procedure parselistingblock(p:pbyte);
+var hdr:PBTreeBlockHeader;
+ head,tail : pbyte;
+ isseealso,
+ nrpairs : Integer;
+ i : integer;
+ PE : PBtreeBlockEntry;
+ title : string;
+ CharIndex,
+ ind:integer;
+ seealsostr,
+ topic,
+ Name : AnsiString;
+ item : TChmSiteMapItem;
+begin
+ hdr:=PBTreeBlockHeader(p);
+ hdr^.Length :=LEToN(hdr^.Length);
+ hdr^.NumberOfEntries :=LEToN(hdr^.NumberOfEntries);
+ hdr^.IndexOfPrevBlock:=LEToN(hdr^.IndexOfPrevBlock);
+ hdr^.IndexOfNextBlock:=LEToN(hdr^.IndexOfNextBlock);
+
+ tail:=p+(2048-hdr^.length);
+ head:=p+sizeof(TBtreeBlockHeader);
+
+ {$ifdef binindex}
+ writeln('previndex : ',hdr^.IndexOfPrevBlock);
+ writeln('nextindex : ',hdr^.IndexOfNextBlock);
+ {$endif}
+ while head<tail do
+ begin
+ if not ReadWCharString(Head,Tail,Name) Then
+ Break;
+ {$ifdef binindex}
+ Writeln('name : ',name);
+ {$endif}
+ if (head+sizeof(TBtreeBlockEntry))>=tail then
+ break;
+ PE :=PBtreeBlockEntry(head);
+ NrPairs :=LEToN(PE^.nrpairs);
+ IsSeealso:=LEToN(PE^.isseealso);
+ CharIndex:=LEToN(PE^.CharIndex);
+ {$ifdef binindex}
+ Writeln('seealso: ',IsSeeAlso);
+ Writeln('entrydepth: ',LEToN(PE^.entrydepth));
+ Writeln('charindex : ',charindex );
+ Writeln('Nrpairs : ',NrPairs);
+ writeln('seealso data : ');
+ {$endif}
+
+ inc(head,sizeof(TBtreeBlockEntry));
+ if isseealso>0 then
+ begin
+ if not ReadWCharString(Head,Tail,SeeAlsoStr) Then
+ Break;
+ // have to figure out first what to do with it.
+ end
+ else
+ begin
+ if NrPairs>0 Then
+ for i:=0 to nrpairs-1 do
+ begin
+ if head<tail Then
+ begin
+ ind:=LEToN(plongint(head)^);
+ topic:=lookuptopicbyid(ind,title);
+ {$ifdef binindex}
+ writeln(i:3,' topic: ',topic);
+ writeln(' title: ',title);
+ {$endif}
+ inc(head,4);
+ end;
+ end;
+ end;
+ if nrpairs<>0 Then
+ createentry(Name,CharIndex,Topic,Title);
+ inc(head,4); // always 1
+ {$ifdef binindex}
+ if head<tail then
+ writeln('Zero based index (13 higher than last) :',plongint(head)^);
+ {$endif}
+ inc(head,4); // zero based index (13 higher than last
+ end;
+end;
+
+var TryTextual : boolean;
+ BHdr : TBTreeHeader;
+ block : Array[0..2047] of Byte;
+ i : Integer;
+begin
+ Result := nil; SiteMap:=Nil;
+ // First Try Binary
+ Index := GetObject('/$WWKeywordLinks/BTree');
+ if (Index = nil) or ForceXML then
+ begin
+ Result:=AbortAndTryTextual;
+ Exit;
+ end;
+ if not CheckCommonStreams then
+ begin
+ Result:=AbortAndTryTextual;
Exit;
end;
+ SiteMap:=TChmSitemap.Create(StIndex);
+ Item :=Nil; // cached last created item, in case we need to make
+ // a child.
+ TryTextual:=True;
+ BHdr.LastLstBlock:=0;
+ if LoadBtreeHeader(index,BHdr) and (BHdr.LastLstBlock>0) Then
+ begin
+ if BHdr.BlockSize=defblocksize then
+ begin
+ for i:=0 to BHdr.lastlstblock do
+ begin
+ if (index.size-index.position)>=defblocksize then
+ begin
+ Index.read(block,defblocksize);
+ parselistingblock(@block)
+ end;
+ end;
+ trytextual:=false;
+ result:=sitemap;
+ end;
+ end;
+ if trytextual then
+ begin
+ sitemap.free;
+ Result:=AbortAndTryTextual;
+ end;
end;
function TChmReader.GetTOCSitemap(ForceXML:boolean=false): TChmSiteMap;
diff --git a/packages/chm/src/chmwriter.pas b/packages/chm/src/chmwriter.pas
index db59a38429..d12f03e785 100644
--- a/packages/chm/src/chmwriter.pas
+++ b/packages/chm/src/chmwriter.pas
@@ -123,6 +123,7 @@ Type
function AddString(AString: String): LongWord;
function AddURL(AURL: String; TopicsIndex: DWord): LongWord;
procedure CheckFileMakeSearchable(AStream: TStream; AFileEntry: TFileEntryRec);
+ function AddTopic(ATitle,AnUrl:AnsiString):integer;
function NextTopicIndex: Integer;
// callbacks for lzxcomp
function AtEndOfData: Longbool;
@@ -144,7 +145,9 @@ Type
procedure Execute;
procedure AppendTOC(AStream: TStream);
procedure AppendBinaryTOCFromSiteMap(ASiteMap: TChmSiteMap);
+ procedure AppendBinaryIndexFromSiteMap(ASiteMap: TChmSiteMap;chw:boolean);
procedure AppendBinaryTOCStream(AStream: TStream);
+ procedure AppendBinaryIndexStream(IndexStream,DataStream,MapStream,Propertystream: TStream;chw:boolean);
procedure AppendIndex(AStream: TStream);
procedure AppendSearchDB(AName: String; AStream: TStream);
procedure AddStreamToArchive(AFileName, APath: String; AStream: TStream; Compress: Boolean = True);
@@ -160,8 +163,8 @@ Type
property FullTextSearch: Boolean read FFullTextSearch write FFullTextSearch;
property SearchTitlesOnly: Boolean read FSearchTitlesOnly write FSearchTitlesOnly;
property HasBinaryTOC: Boolean read FHasBinaryTOC write FHasBinaryTOC;
- property HasBinaryIndex: Boolean read FHasBinaryIndex write FHasBinaryIndex;
- property DefaultFont: String read FDefaultFont write FDefaultFont;
+ property HasBinaryIndex: Boolean read FHasBinaryIndex write FHasBinaryIndex;
+ property DefaultFont: String read FDefaultFont write FDefaultFont;
property DefaultPage: String read FDefaultPage write FDefaultPage;
property TempRawStream: TStream read FTempStream write SetTempRawStream;
//property LocaleID: dword read ITSFHeader.LanguageID write ITSFHeader.LanguageID;
@@ -174,6 +177,13 @@ const
LZX_WINDOW_SIZE = 16; // 16 = 2 frames = 1 shl 16
LZX_FRAME_SIZE = $8000;
+ {$ifdef binindex}
+ procedure logentry(s:string);
+ begin
+ Writeln(s);
+ flush(stdout);
+ end;
+ {$endif}
{$I chmobjinstconst.inc}
@@ -569,6 +579,9 @@ begin
// 7 Binary Index
if FHasBinaryIndex then
begin
+ {$ifdef binindex}
+ logentry('binary index!');
+ {$endif}
FSection0.WriteWord(NToLE(Word(7)));
FSection0.WriteWord(NToLE(Word(4)));
FSection0.WriteDWord(DWord(0)); // what is this number to be?
@@ -1152,6 +1165,28 @@ begin
end;
end;
+function TChmWriter.AddTopic(ATitle,AnUrl:AnsiString):integer;
+
+var
+ TopicEntry: TTopicEntry;
+
+begin
+ if ATitle <> '' then
+ TopicEntry.StringsOffset := AddString(ATitle)
+ else
+ TopicEntry.StringsOffset := $FFFFFFFF;
+ result:=NextTopicIndex;
+ TopicEntry.URLTableOffset := AddURL(AnUrl, Result);
+ TopicEntry.InContents := 2;
+ TopicEntry.Unknown := 0;
+ TopicEntry.TocOffset := 0;
+ FTopicsStream.WriteDWord(LEtoN(TopicEntry.TocOffset));
+ FTopicsStream.WriteDWord(LEtoN(TopicEntry.StringsOffset));
+ FTopicsStream.WriteDWord(LEtoN(TopicEntry.URLTableOffset));
+ FTopicsStream.WriteWord(LEtoN(TopicEntry.InContents));
+ FTopicsStream.WriteWord(LEtoN(TopicEntry.Unknown));
+end;
+
function TChmWriter.NextTopicIndex: Integer;
begin
Result := FTopicsStream.Size div 16;
@@ -1430,11 +1465,449 @@ begin
TOCIDXStream.Free;
end;
+Const
+ BinIndexIdent : array[0..1] of char = (CHR($3B),CHR($29));
+ AlwaysX44 : Array[0..15] of char = ('X','4','4',#0,#0,#0,#0,#0,
+ #0,#0,#0,#0,#0,#0,#0,#0);
+ DataEntry : Array[0..12] of Byte = ($00,$00,$00,$00,$05,$00,$00,$00,$80,$00,$00,$00,$00);
+{
+ IndexStream:=TMemoryStream.Create;
+ IndexStream.Write(BinIndexIdent,2);
+ IndexStream.Write(NToLE(word(2)),2);
+ IndexStream.Write(NToLE(word(2048)),2);
+ IndexStream.Write(AlwaysX44,sizeof(AlwaysX44));
+ IndexStrem.Write (dword(0),2);
+}
+
+Const DefBlockSize = 2048;
+
+Type TIndexBlock = Array[0..DefBlockSize-1] of Byte;
+
+procedure writeword(var p:pbyte;w:word); inline;
+
+begin
+ pword(p)^:=NToLE(w);
+ inc(pword(p));
+end;
+
+procedure writedword(var p:pbyte;d:dword); inline;
+
+begin
+ pdword(p)^:=NToLE(d);
+ inc(pdword(p));
+end;
+
+procedure TChmWriter.AppendBinaryIndexFromSiteMap(ASiteMap: TChmSiteMap;chw:boolean);
+
+Var
+ IndexStream : TMemoryStream;
+ //n : Integer;
+ curblock : TIndexBlock; // current listing block being built
+ TestBlock : TIndexBlock; // each entry is first built here. then moved to curblock
+ curind : integer; // next byte to write in testblock.
+ blocknr : Integer; // blocknr of block in testblock;
+ lastblock : Integer; // blocknr of last block.
+ Entries : Integer; // Number of entries in this block so far
+ TotalEntries: Integer; // Total number of entries
+ MapEntries : Integer;
+ MapIndex : Integer;
+ indexblocknr: Integer;
+ blockind : Integer; // next byte to write in blockn[blocknr]
+ blockentries: Integer; // entries so far ins blockn[blocknr]
+ blockn : Array Of TIndexBlock;
+ BlockNPlus1 : Array of TIndexBlock;
+ Mod13value : integer; // A value that is increased by 13 for each entry. (?!?!)
+ EntryToIndex: boolean; // helper var to make sure the first block is always indexed.
+ blocknplusindex : Integer; // blocks in level n+1 (second part)
+ blocknplusentries : Integer; // The other blocks indexed on creation.
+ datastream,mapstream,propertystream : TMemoryStream;
+
+procedure preparecurrentblock;
+
+var p: PBTreeBlockHeader;
+
+begin
+ p:=@curblock[0];
+ p^.Length:=NToLE(Defblocksize-curind);
+ p^.NumberOfEntries:=Entries;
+ p^.IndexOfPrevBlock:=lastblock;
+ p^.IndexOfNextBlock:=Blocknr;
+ IndexStream.Write(curblock[0],Defblocksize);
+ MapStream.Write(NToLE(MapEntries),sizeof(dword));
+ MapStream.Write(NToLE(BlockNr),Sizeof(DWord));
+ MapEntries:=TotalEntries;
+ curind:=sizeof(TBtreeBlockHeader); // index into current block;
+ lastblock:=blocknr;
+ inc(blocknr);
+end;
+
+procedure prepareindexblockn(listingblocknr:integer);
+var p:PBTreeIndexBlockHeader;
+begin
+ p:=@Blockn[IndexBlockNr];
+ p^.Length:=defblocksize-BlockInd;
+ p^.NumberOfEntries:=BlockEntries;
+
+// p^.IndexOfChildBlock // already entered on block creation, since of first entry, not last.
+ inc(Indexblocknr);
+ BlockEntries:=0;
+ BlockInd:=0;
+ if Indexblocknr>=length(blockn) then
+ setlength(blockn,length(blockn)+1); // larger increments also possible. #blocks is kept independantly.
+ p:=@Blockn[IndexBlockNr];
+ p^.IndexOfChildBlock:=ListingBlockNr;
+ blockind:=sizeof(TBTreeIndexBlockHeader);
+end;
+
+procedure finalizeindexblockn(p:pbyte;var ind:integer;Entries:integer);
+var ph:PBTreeIndexBlockHeader;
+begin
+ ph:=PBTreeIndexBlockHeader(p);
+ ph^.Length:=defblocksize-Ind;
+ ph^.NumberOfEntries:=Entries;
+// p^.IndexOfChildBlock // already entered on block creation, since of first entry, not last.
+// inc(Ind);
+end;
+
+procedure CurEntryToIndex(entrysize:integer);
+var p,pentry : pbyte;
+ indexentrysize : integer;
+begin
+ indexentrysize:=entrysize-sizeof(dword); // index entry is 4 bytes shorter, and only the last dword differs
+ if (blockind+indexentrysize)>=Defblocksize then
+ prepareindexblockn(blocknr);
+ p:=@blockn[Indexblocknr][blockind];
+ move(testblock[0],p^,indexentrysize);
+ pentry:=@p[indexentrysize-sizeof(dword)]; // ptr to last dword
+ writedword(pentry,blocknr); // patch up the "index of child field"
+ inc(blockind,indexentrysize);
+end;
+
+procedure CreateEntry(Item:TChmSiteMapItem;Str:WideString;commaatposition:integer);
+
+var p : pbyte;
+ topicid: integer;
+ seealso: Integer;
+ entrysize:Integer;
+ i : Integer;
+begin
+ inc(TotalEntries);
+ p:=@TestBlock[0];
+ for i:=1 to Length(str) do
+ WriteWord(p,Word(str[i])); // write the wstr in little endian
+ WriteWord(p,0); // NT
+// if item.seealso='' then // no seealso for now
+ seealso:=0;
+ // else
+// seealso:=2;
+ WriteWord(p,seealso); // =0 not a see also 2 =seealso
+ WriteWord(p,2); // Entrydepth. We can't know it, so write 2.
+ WriteDword(p,commaatposition); // position of the comma
+ WriteDword(p,0); // unused 0
+ WriteDword(p,1); // for now only local pair.
+ TopicId:=AddTopic(Item.Text,item.Local);
+ WriteDword(p,TopicId);
+ // if seealso then _here_ a wchar NT string with seealso?
+ WriteDword(p,1); // always 1 (unknown);
+ WriteDword(p,mod13value); //a value that increments with 13.
+ mod13value:=mod13value+13;
+ entrysize:=p-pbyte(@testblock[0]);
+ if (curind+entrysize)>=Defblocksize then
+ begin
+ preparecurrentblock;
+ EntrytoIndex:=true;
+ end;
+ if EntryToIndex Then
+ begin
+ CurEntryToIndex(entrysize);
+ EntryToIndex:=False;
+ end;
+ move(testblock[0],curblock[curind],entrysize);
+ inc(curind,entrysize);
+ datastream.write(DataEntry,Sizeof(DataEntry));
+end;
+
+procedure MoveIndexEntry(nr:integer;bytes:integer;childblock:integer);
+var
+ pscr,pdest : pbyte;
+begin
+ {$ifdef binindex}
+ writeln(' moveindexentry ',nr,' bytes:',bytes,' childblock:',childblock);
+ flush(stdout);
+ {$endif}
+
+ if ((blockind+bytes)>=defblocksize) then
+ begin
+ {$ifdef binindex}
+ writeln(' in scalecheck ',blockind);
+ flush(stdout);
+ {$endif}
+
+ FinalizeIndexBlockn(@blocknplus1[blocknplusindex][0],blockind,blocknplusentries);
+ inc(blocknplusindex);
+ if blocknplusindex>=length(blocknplus1) then
+ setlength(blocknplus1,length(blocknplus1)+1);
+ blockInd:=Sizeof(TBTreeIndexBlockHeader);
+ pdword(@blocknplus1[blocknplusindex][0])[4]:=NToLE(ChildBlock); /// init 2nd level index to first 1st level index block
+ end;
+ {$ifdef binindex}
+ writeln(' len:',length(blocknplus1),' blockind:',blockind,' index:',blocknplusindex);
+ flush(stdout);
+ {$endif}
+
+ // copy entry from one indexblock to another
+ pscr:=@blockn[nr][sizeof(TBtreeIndexBlockHeader)];
+ pdest:=@blocknplus1[blocknplusindex][blockind];
+ move(pscr^,pdest^,bytes);
+ pdword(@pdest[bytes-sizeof(dword)])^:=NToLE(childblock); // correcting the childindex
+ inc (blockind,bytes);
+ inc(blocknplusentries); // not needed for writing, but used to check if something has been written. End condition
+end;
+
+function ScanIndexBlock(blk:Pbyte):Integer;
+
+var start : pbyte;
+ n : Integer;
+ i : Integer;
+begin
+ start:=@blk[sizeof(TBtreeIndexBlockHeader)];
+ blk:=start;
+ while pword(blk)^<>0 do // skip wchar
+ inc(pword(blk));
+ inc(pword(blk)); // skip NT
+ inc(pword(blk)); // skip see also
+ inc(pword(blk)); // skip depth
+ inc(pdword(blk)); // skip Character Index.
+ inc(pdword(blk)); // skip always 0
+ n:=LEToN(pdword(blk)^);
+ inc(pdword(blk)); // skip nr of pairs.
+ for i:= 1 to n do
+ inc(pdword(blk)); // skip <n> topicids
+ inc(pdword(blk)); // skip childindex
+ Result:=blk-start;
+end;
+
+procedure CombineWithChildren(ParentItem:TChmSiteMapItem;Str:WideString;commaatposition:integer;first:boolean);
+var i : Integer;
+ Item : TChmSiteMapItem;
+begin
+ if ParentItem.Children.Count = 0 Then
+ Begin
+ // comment/fix next
+ // if commatposition=length(str) then commaatposition:=0;
+ if first then
+ CreateEntry(ParentItem,Str,0)
+ else
+ CreateEntry(ParentItem,Str,commaatposition);
+ End
+ Else
+ for i:=0 to ParentItem.Children.Count-1 do
+ begin
+ item := TChmSiteMapItem(ParentItem.Children.Item[i]);
+ if first Then
+ CombineWithChildren(Item,Str+', '+item.text,commaatposition+2,false)
+ else
+ CombineWithChildren(Item,Str+', '+item.text,commaatposition,false);
+ end;
+end;
+
+Var i : Integer;
+ Key : WideString;
+ Item : TChmSiteMapItem;
+ ListingBlocks : Integer;
+ EntryBytes : Integer;
+ Hdr : TBTreeHeader;
+ TreeDepth : Integer;
+
+{$ifdef binindex}
+procedure printloopvars(i:integer);
+
+begin
+ Writeln('location :' ,i, ' blocknr :', blocknr,' level:',TreeDepth);
+ Writeln('blockn length: ',length(blockn),' indexblocknr: ',indexblocknr,' blockind ',blockind);
+ Writeln('blocknplus1 length: ',length(blocknplus1),' blocknplusindex:',blocknplusindex,' entries:',blocknplusentries);
+ flush(stdout);
+end;
+{$endif}
+begin
+ IndexStream:=TMemoryStream.Create;
+ indexstream.size:=sizeof(TBTreeHeader);
+ IndexStream.position:=Sizeof(TBTreeHeader);
+ datastream:=TMemoryStream.Create;
+ mapstream :=TMemoryStream.Create;
+ mapstream.size:=2;
+ mapstream.position:=2;
+ propertystream :=TMemoryStream.Create;
+ propertystream.write(NToLE(0),sizeof(4));
+ // we iterate over all entries and write listingblocks directly to the stream.
+ // and the first (and maybe last) level is written to blockn.
+ // we can't do higher levels yet because we don't know how many listblocks we get
+ BlockNr :=0; // current block number
+ Lastblock :=-1; // previous block nr or -1 if none.
+ Entries :=0; // entries in this block
+ TotalEntries:=0; // entries so far.
+ Mod13value :=0; // value that increments by 13 entirely.
+ indexblocknr:=0; // nr of first index block.
+ BlockEntries:=0; // entries into current block;
+ MapEntries :=0; // entries before the current listing block, for MAP file
+
+ curind :=sizeof(TBTreeBlockHeader); // index into current listing block;
+ blockind :=sizeof(TBtreeIndexBlockHeader); // index into current index block
+
+ Setlength(blockn,1);
+ pdword(@blockn[0][4])^:=NToLE(0); /// init first listingblock nr to 0 in the first index block
+ EntryToIndex := True;
+ for i:=0 to ASiteMap.Items.Count-1 do
+ begin
+ item := TChmSiteMapItem(ASiteMap.Items.Item[i]);
+ key :=Item.Text;
+ {$ifdef chm_windowsbinindex}
+ // append 2 to all index level 0 entries. This
+ // so we can see if Windows loads the binary or textual index.
+ CombineWithChildren(Item,Key+'2',length(key)+1,true);
+ {$else}
+ CombineWithChildren(Item,Key,length(key),true);
+ {$endif}
+ end;
+ PrepareCurrentBlock; // flush last listing block.
+ Listingblocks:=blocknr; // blocknr is from now on the number of the first block in blockn.
+ // we still need the # of listingblocks for the header though
+
+ {$ifdef binindex}
+ writeln('binindex: listingblocks : '+inttostr(listingblocks),' indexblocks: ',indexblocknr,' entries:',blockentries);
+ {$endif}
+
+ // we have now created and written the listing blocks, and created the first level of index in <blockn>
+ // the following loop uses <blockn> to calculate the next level (in blocknplus1), then write out blockn,
+ // and repeat until we have no entries left.
+
+ // First we finalize the current set of blocks
+
+ if Blockind<>sizeof(TBtreeIndexBlockHeader) Then
+ begin
+ {$ifdef binindex}
+ writeln('finalizing level 1 index');
+ {$endif}
+ FinalizeIndexBlockN(@blockn[indexblocknr][0],blockind,blockentries); // also increasing indexblocknr
+ inc(IndexBlockNr);
+ end;
+ {$ifdef binindex}
+ writeln('binindex: listingblocks : '+inttostr(listingblocks),' indexblocks: ',indexblocknr,' entries:',blockentries);
+ {$endif}
+
+
+ while (Indexblocknr>1) do
+ begin
+ {$ifdef binindex}
+ printloopvars(1);
+ {$endif}
+
+ blockind :=sizeof(TBtreeIndexBlockHeader);
+ pdword(@blockn[0][4])^:=NToLE(Listingblocks); /// init 2nd level index to first 1st level index block
+ blocknplusindex :=0;
+ blocknplusentries :=0;
+ if length(blocknplus1)<1 then
+ Setlength(blocknplus1,1);
+
+ EntryToIndex :=True;
+ {$ifdef binindex}
+ printloopvars(2);
+ {$endif}
+ for i:=0 to Indexblocknr-1 do
+ begin
+ Entrybytes:=ScanIndexBlock(@blockn[i][0]);
+ writeln('after scan ,',i, ' bytes: ',entrybytes,' blocknr:',blocknr,' indexblocknr:',indexblocknr,' to:',blocknr+i);
+ MoveIndexEntry(i,Entrybytes,blocknr+i);
+ indexStream.Write(blockn[i][0],defblocksize);
+ end;
+
+ {$ifdef binindex}
+ printloopvars(3);
+ {$endif}
+
+ If Blockind<>sizeof(TBtreeIndexBlockHeader) Then
+ begin
+ {$ifdef binindex}
+ logentry('finalizing');
+ {$endif}
+ FinalizeIndexBlockn(@blocknplus1[blocknplusindex][0],blockind,blocknplusentries);
+ inc(blocknplusindex);
+ end;
+
+ inc(blocknr,indexblocknr);
+
+ indexblocknr:=blocknplusindex;
+ blockn:=copy(blocknplus1); setlength(blocknplus1,1);
+ {$ifdef binindex}
+ printloopvars(5);
+ {$endif}
+
+ inc(TreeDepth);
+ end;
+ indexStream.Write(blockn[0][0],defblocksize);
+ inc(blocknr);
+ // Fixup header.
+ hdr.ident[0]:=chr($3B); hdr.ident[1]:=chr($29);
+ hdr.flags :=NToLE($2); // bit $2 is always 1, bit $0400 1 if dir? (always on)
+ hdr.blocksize :=NToLE(defblocksize); // size of blocks (2048)
+ hdr.dataformat :=AlwaysX44; // "X44" always the same, see specs.
+ hdr.unknown0 :=NToLE(0); // always 0
+ hdr.lastlstblock :=NToLE(ListingBlocks-1); // index of last listing block in the file;
+ hdr.indexrootblock :=NToLE(blocknr-1); // Index of the root block in the file.
+ hdr.unknown1 :=NToLE(-1); // always -1
+ hdr.nrblock :=NToLE(blocknr); // Number of blocks
+ hdr.treedepth :=NToLE(TreeDepth); // The depth of the tree of blocks (1 if no index blocks, 2 one level of index blocks, ...)
+ hdr.nrkeywords :=NToLE(Totalentries); // number of keywords in the file.
+ hdr.codepage :=NToLE(1252); // Windows code page identifier (usually 1252 - Windows 3.1 US (ANSI))
+ hdr.lcid :=NToLE(0); // ???? LCID from the HHP file.
+ if not chw then
+ hdr.ischm :=NToLE(1) // 0 if this a BTREE and is part of a CHW file, 1 if it is a BTree and is part of a CHI or CHM file
+ else
+ hdr.ischm :=NToLE(0);
+ hdr.unknown2 :=NToLE(10031); // Unknown. Almost always 10031. Also 66631 (accessib.chm, ieeula.chm, iesupp.chm, iexplore.chm, msoe.chm, mstask.chm, ratings.chm, wab.chm).
+ hdr.unknown3 :=NToLE(0); // unknown 0
+ hdr.unknown4 :=NToLE(0); // unknown 0
+ hdr.unknown5 :=NToLE(0); // unknown 0
+
+ IndexStream.Position:=0;
+ IndexStream.write(hdr,sizeof(hdr));
+ {$ifdef binindex}
+ logentry('before append');
+ {$endif}
+
+ AppendBinaryIndexStream(IndexStream,datastream,MapStream,PropertyStream,chw);
+ IndexStream.Free;
+ PropertyStream.Free;
+ MapStream.Free;
+ DataStream.Free;
+end;
+
procedure TChmWriter.AppendBinaryTOCStream(AStream: TStream);
begin
AddStreamToArchive('#TOCIDX', '/', AStream, True);
end;
+procedure TChmWriter.AppendBinaryIndexStream(IndexStream,DataStream,MapStream,Propertystream: TStream;chw:boolean);
+
+procedure stadd(fn:string;stream:TStream);
+
+begin
+ Stream.Position:=0;
+ if CHW then
+ fn:=uppercase(fn);
+ {$ifdef binindex}
+ logentry('before append '+fn);
+ {$endif}
+ AddStreamToArchive(fn,'/$WWKeywordLinks/',stream,True);
+end;
+
+begin
+ stadd('BTree',IndexStream);
+ stadd('Data', DataStream);
+ stadd('Map' , MapStream);
+ stadd('Property', PropertyStream);
+end;
+
procedure TChmWriter.AppendIndex(AStream: TStream);
begin
FHasIndex := True;