{%mainunit dw_html}
{$IFDEF chmInterface}
type
{ TCHMHTMLWriter }
TCHMHTMLWriter = class(THTMLWriter)
private
FOutChm: TStream;
FChm: TChmWriter;
FTempUncompressed: TStream;
FTempUncompressedName: String;
FChmTitle: String;
FTOCName,
FIndexName,
FDefaultPage: String;
FMakeSearchable,
FNoBinToc,
FNoBinIndex,
FAutoTOC,
FAutoIndex: Boolean;
FOtherFiles: String;
procedure ProcessOptions;
function ResolveLinkIDAbs(const Name: String; Level : Integer = 0): DOMString;
function RetrieveOtherFiles(const DataName: String; out PathInChm: String; out FileName: String; var Stream: TStream): Boolean;
procedure LastFileAdded(Sender: TObject);
procedure GenerateTOC;
procedure GenerateIndex;
public
procedure WriteHTMLPages; override;
function InterPretOption(const Cmd,Arg : String): boolean; override;
class procedure Usage(List: TStrings); override;
Class Function FileNameExtension : String; override;
Class procedure SplitImport(var AFilename, ALinkPrefix: String); override;
end;
{$ELSE} // implementation
{ TCHMHTMLWriter }
function TCHMHTMLWriter.ResolveLinkIDAbs(const Name: String; Level : Integer = 0): DOMString;
begin
Result:=UTF8Decode(FixHTMLpath(Engine.ResolveLink(Module,Name, True)));
// for global index: don't make it relative to the current document.
end;
procedure TCHMHTMLWriter.ProcessOptions;
var
TempStream: TMemoryStream;
begin
if FDefaultPage = '' then
FDefaultPage := 'index.html'
else
begin
DoLog('Note: --index-page not assigned. Using default "index.html"');
end;
if FCSSFile <> '' then
begin
if not FileExists(FCSSFile) Then
Raise Exception.CreateFmt('Can''t find CSS file "%S"',[FCSSFILE]);
TempStream := TMemoryStream.Create;
TempStream.LoadFromFile(FCSSFile);
TempStream.Position := 0;
FChm.AddStreamToArchive('fpdoc.css', '/', TempStream, True);
TempStream.Free;
end;
FChm.DefaultPage := FDefaultPage;
if FOtherFiles <> '' then
begin
FChm.FilesToCompress.LoadFromFile(FOtherFiles);
end;
FChm.FullTextSearch := FMakeSearchable;
end;
function TCHMHTMLWriter.RetrieveOtherFiles(const DataName: String; out
PathInChm: String; out FileName: String; var Stream: TStream): Boolean;
begin
Result:=True;
if Stream <> nil then
Stream.Free;
Stream := TMemoryStream.Create;
TMemoryStream(Stream).LoadFromFile(DataName);
FileName := ExtractFileName(DataName);
if ExtractFileDir(DataName) <> '' then
PathInChm := ExtractRelativepath(GetCurrentDir, ExtractFileDir(DataName))
else
PathInChm := '/';
FixHTMLpath(PathInChm);
Stream.Position := 0;
end;
procedure TCHMHTMLWriter.LastFileAdded(Sender: TObject);
var
TmpStream: TMemoryStream;
begin
TmpStream := TMemoryStream.Create;
if FAutoTOC then
GenerateTOC
else
if FTOCName <> '' then
begin
TmpStream.LoadFromFile(FTOCName);
TmpStream.Position := 0;
FChm.AppendTOC(TmpStream);
TmpStream.Size := 0;
end;
if FAutoIndex then
GenerateIndex
else
if FIndexName <> '' then
begin
TmpStream.LoadFromFile(FIndexName);
TmpStream.Position := 0;
FChm.AppendIndex(TmpStream);
end;
TmpStream.Free;
DoLog('Finishing compressing...');
end;
function TOCSort(Item1, Item2: TChmSiteMapItem): Integer;
begin
Result := CompareText(LowerCase(Item1.Text), LowerCase(Item2.Text));
end;
function GetAlphaItem(AItems: TChmSiteMapItems; AName: String): TChmSiteMapItem;
var
x: Integer;
begin
Result := nil;
for x := 0 to AItems.Count-1 do
begin
if AItems.Item[x].Text = AName then
Exit(AItems.Item[x]);
end;
Result := AItems.NewItem;
Result.Text := AName;
end;
procedure TCHMHTMLWriter.GenerateTOC;
var
TOC: TChmSiteMap;
Element: TPasElement;
j: Integer;
i: Integer;
AModule: TPasModule;
Stream: TMemoryStream;
TmpItem: TChmSiteMapItem;
ObjByUnitItem,
AlphaObjItem,
ObjUnitItem,
RoutinesByUnitItem,
RoutinesUnitItem,
AlphaRoutinesItem: TChmSiteMapItem;
begin
DoLog('Generating Table of contents...');
if Assigned(Package) then
begin
Toc := TChmSiteMap.Create(stTOC);
Stream := TMemoryStream.Create;
ObjByUnitItem := TOC.Items.NewItem;
ObjByUnitItem.Text := 'Classes and Objects, by Unit';
AlphaObjItem := TOC.Items.NewItem;
AlphaObjItem.Text := 'Alphabetical Classes and Objects List';
RoutinesByUnitItem := TOC.Items.NewItem;
RoutinesByUnitItem.Text := 'Routines, by Unit';
AlphaRoutinesItem := TOC.Items.NewItem;
AlphaRoutinesItem.Text := 'Alphabetical Routines List';
// objects and classes
for i := 0 to Package.Modules.Count - 1 do
begin
AModule := TPasModule(Package.Modules[i]);
If not assigned(AModule.InterfaceSection) Then
Continue;
ObjUnitItem := ObjByUnitItem.Children.NewItem;
ObjUnitItem.Text := AModule.Name;
RoutinesUnitItem := RoutinesByUnitItem.Children.NewItem;
RoutinesUnitItem.Text := AModule.Name;
for j := 0 to AModule.InterfaceSection.Classes.Count-1 do
begin
Element := TPasClassType(AModule.InterfaceSection.Classes[j]);
// by unit
TmpItem := ObjUnitItem.Children.NewItem;
TmpItem.Text := Element.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(Element, 0)));
//alpha
TmpItem := GetAlphaItem(AlphaObjItem.Children, UpperCase(Copy(Element.Name, 1, 2))).Children.NewItem;
TmpItem.Text := Element.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(Element, 0)));
end;
// non object procedures and functions
for j := 0 to AModule.InterfaceSection.Functions.Count-1 do
begin
Element := TPasFunctionType(AModule.InterfaceSection.Functions[j]);
// by unit
TmpItem := RoutinesUnitItem.Children.NewItem;
TmpItem.Text := Element.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(Element, 0)));
// alpha
TmpItem := GetAlphaItem(AlphaRoutinesItem.Children, UpperCase(Element.Name[1])).Children.NewItem;
TmpItem.Text := Element.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(Element, 0)));
end;
end;
end;
// cleanup
for i := ObjByUnitItem.Children.Count-1 downto 0 do
begin
if ObjByUnitItem.Children.Item[i].Children.Count = 0 then
ObjByUnitItem.Children.Delete(i);
end;
for i := RoutinesByUnitItem.Children.Count-1 downto 0 do
begin
if RoutinesByUnitItem.Children.Item[i].Children.Count = 0 then
RoutinesByUnitItem.Children.Delete(i);
end;
for i := TOC.Items.Count-1 downto 0 do
begin
if TOC.Items.Item[i].Children.Count = 0 then
TOC.Items.Delete(i);
end;
// Sort
for i := 0 to TOC.Items.Count-1 do
begin
TOC.Items.Item[i].Children.Sort(TListSortCompare(@TOCSort));
for j := 0 to TOC.Items.Item[i].Children.Count-1 do
begin
TOC.Items.Item[i].Children.Item[j].Children.Sort(TListSortCompare(@TOCSort));
end;
end;
if not fnobintoc then
fchm.AppendBinaryTOCFromSiteMap(Toc);
TOC.SaveToStream(Stream);
TOC.Free;
fchm.AppendTOC(Stream);
Stream.Free;
end;
type
TClassMemberType = (cmtProcedure, cmtFunction, cmtConstructor, cmtDestructor,
cmtInterface, cmtProperty, cmtVariable, cmtUnknown);
function ElementType(Element: TPasElement): TClassMemberType;
var
ETypeName: String;
begin
Result := cmtUnknown;
ETypeName := Element.ElementTypeName;
//overloaded we don't care
if ETypeName[1] = 'o' then ETypeName := Copy(ETypeName, 11, Length(ETypeName));
if ETypeName[1] = 'f' then Exit(cmtFunction);
if ETypeName[1] = 'c' then Exit(cmtConstructor);
if ETypeName[1] = 'v' then Exit(cmtVariable);
if ETypeName[1] = 'i' then Exit(cmtInterface);
// the p's
if ETypeName[4] = 'c' then Exit(cmtProcedure);
if ETypeName[4] = 'p' then Exit(cmtProperty);
end;
procedure TCHMHTMLWriter.GenerateIndex;
var
Index: TChmSiteMap;
i, j, k: Integer;
TmpItem: TChmSiteMapItem;
ParentItem: TChmSiteMapItem;
AModule: TPasModule;
TmpElement: TPasElement;
ParentElement: TPasElement;
MemberItem: TChmSiteMapItem;
Stream: TMemoryStream;
RedirectUrl,Urls: String;
begin
DoLog('Generating Index...');
if Assigned(Package) then
begin
Index := TChmSiteMap.Create(stIndex);
Stream := TMemoryStream.Create;
for i := 0 to Package.Modules.Count - 1 do
begin
AModule := TPasModule(Package.Modules[i]);
if not assigned(AModule.InterfaceSection) then
continue;
ParentItem := Index.Items.NewItem;
ParentItem.Text := AModule.Name;
ParentItem.addLocal(FixHTMLpath(Allocator.GetFilename(AModule, 0)));
// classes
for j := 0 to AModule.InterfaceSection.Classes.Count-1 do
begin
ParentElement := TPasClassType(AModule.InterfaceSection.Classes[j]);
ParentItem := Index.Items.NewItem;
ParentItem.Text := ParentELement.Name;
ParentItem.addLocal(FixHTMLpath(Allocator.GetFilename(ParentElement, 0)));
for k := 0 to TPasClassType(ParentElement).Members.Count-1 do
begin
TmpElement := TPasElement(TPasClassType(ParentElement).Members.Items[k]);
if Engine.HidePrivate and(TmpElement.Visibility = visPrivate) then
continue;
if Engine.HideProtected and(TmpElement.Visibility = visProtected) then
continue;
Urls:=FixHTMLpath(Allocator.GetFilename(TmpElement, 0));
RedirectUrl:='';
if TmpElement is TPasEnumValue then
RedirectUrl := UTF8Encode(ResolveLinkIDAbs(tmpElement.Parent.PathName))
else
RedirectUrl := UTF8Encode(ResolveLinkIDAbs(tmpElement.PathName));
if(trim(RedirectUrl)<>'') and (RedirectUrl<>urls) then
begin
writeln('Hint: Index Resolved:',urls,' to ',RedirectUrl);
urls:=RedirectUrl;
end;
TmpItem := ParentItem.Children.NewItem;
case ElementType(TmpElement) of
cmtProcedure : TmpItem.Text := TmpElement.Name + ' procedure';
cmtFunction : TmpItem.Text := TmpElement.Name + ' function';
cmtConstructor : TmpItem.Text := TmpElement.Name + ' constructor';
cmtDestructor : TmpItem.Text := TmpElement.Name + ' destructor';
cmtProperty : TmpItem.Text := TmpElement.Name + ' property';
cmtVariable : TmpItem.Text := TmpElement.Name + ' variable';
cmtInterface : TmpItem.Text := TmpElement.Name + ' interface';
cmtUnknown : TmpItem.Text := TmpElement.Name;
end;
TmpItem.addLocal(Urls);
{
ParentElement = Class
TmpElement = Member
}
MemberItem := nil;
MemberItem := GetAlphaItem(Index.Items, TmpElement.Name);
// ahh! if MemberItem.Local is empty MemberType is not shown!
MemberItem.addLocal(Urls);
TmpItem := MemberItem.Children.NewItem;
TmpItem.Text := ParentElement.Name;
TmpItem.AddLocal(Urls);
end;
end;
// routines
for j := 0 to AModule.InterfaceSection.Functions.Count-1 do
begin
ParentElement := TPasProcedureType(AModule.InterfaceSection.Functions[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name + ' ' + ParentElement.ElementTypeName;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(ParentElement, 0)));
end;
// consts
for j := 0 to AModule.InterfaceSection.Consts.Count-1 do
begin
ParentElement := TPasElement(AModule.InterfaceSection.Consts[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(ParentElement, 0)));
end;
// types
for j := 0 to AModule.InterfaceSection.Types.Count-1 do
begin
ParentElement := TPasType(AModule.InterfaceSection.Types[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name;
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(ParentElement, 0)));
// enums
if ParentELement is TPasEnumType then
begin
ParentItem := TmpItem;
for k := 0 to TPasEnumType(ParentElement).Values.Count-1 do
begin
TmpElement := TPasType(TPasEnumType(ParentElement).Values.Items[k]);
// subitem
TmpItem := ParentItem.Children.NewItem;
TmpItem.Text := TmpElement.Name;
TmpItem.addLocal(ParentItem.Local);
// root level
TmpItem := Index.Items.NewItem;
TmpItem.Text := TmpElement.Name;
TmpItem.addLocal(ParentItem.Local);
end;
end;
end;
// variables
for j := 0 to AModule.InterfaceSection.Variables.Count-1 do
begin
ParentElement := TPasElement(AModule.InterfaceSection.Variables[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name + ' var';
TmpItem.addLocal(FixHTMLpath(Allocator.GetFilename(ParentElement, 0)));
end;
// declarations
{
for j := 0 to AModule.InterfaceSection.Declarations.Count-1 do
begin
ParentElement := TPasElement(AModule.InterfaceSection.Declarations[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name;
TmpItem.Local := FixHTMLpath(Allocator.GetFilename(ParentElement, 0));
end;
// resource strings
for j := 0 to AModule.InterfaceSection.ResStrings.Count-1 do
begin
ParentElement := TPasElement(AModule.InterfaceSection.ResStrings[j]);
TmpItem := Index.Items.NewItem;
TmpItem.Text := ParentElement.Name;
TmpItem.Local := FixHTMLpath(Allocator.GetFilename(ParentElement, 0));
end;
}
end;
// Sort
Index.Items.Sort(TListSortCompare(@TOCSort));
for i := 0 to Index.Items.Count-1 do
begin
Index.Items.Item[i].Children.Sort(TListSortCompare(@TOCSort));
end;
// save
Index.SaveToStream(Stream);
if not fnobinindex then
fchm.AppendBinaryindexFromSitemap(index,false);
Index.Free;
Stream.Position :=0 ;
FChm.AppendIndex(Stream);
Stream.Free;
end;
end;
procedure TCHMHTMLWriter.WriteHTMLPages;
var
i: Integer;
PageDoc: TXMLDocument;
FileStream: TMemoryStream;
FileName: String;
FilePath: String;
begin
FileName := Engine.Output;
if FileName = '' then
Raise Exception.Create('Error: no --output option used.');
if ExtractFileExt(FileName) <> FileNameExtension then
FileName := ChangeFileExt(FileName, FileNameExtension);
FOutChm := TFileStream.Create(FileName, fmOpenReadWrite or fmCreate);
FTempUncompressedName := GetTempFileName+IntToStr(GetProcessID) +'.raw';
FTempUncompressed := TFileStream.Create(FTempUncompressedName, fmOpenReadWrite or fmCreate);
FChm := TChmWriter.Create(FOutChm, False);
FChm.Title := FChmTitle;
FChm.TempRawStream := FTempUncompressed;
FChm.OnGetFileData := @RetrieveOtherFiles;
FChm.OnLastFile := @LastFileAdded;
fchm.hasbinarytoc:=not fnobintoc;;
fchm.hasbinaryindex:=not fnobinindex;
ProcessOptions;
FileStream := TMemoryStream.Create;
for i := 0 to PageInfos.Count - 1 do
with TPageInfo(PageInfos[i]) do
begin
PageDoc := CreateHTMLPage(Element, SubpageIndex);
try
FileName := ExtractFileName(Allocator.GetFilename(Element, SubpageIndex));
FilePath := '/'+FixHTMLpath(ExtractFilePath(Allocator.GetFilename(Element, SubpageIndex)));
try
WriteHTMLFile(PageDoc, FileStream);
FChm.AddStreamToArchive(FileName, FilePath, FileStream, True);
except
on E: Exception do
DoLog(Format(SErrCouldNotCreateFile, [FileName, e.Message]));
end;
finally
PageDoc.Free;
FileStream.Size := 0;
end;
end;
FileStream.Free;
DoLog('HTML Files written. Collecting other files and compressing...this could take some time');
//write any found images to CHM stream
FileStream := TMemoryStream.Create;
for i := 0 to FImageFileList.Count - 1 do
begin
{$ifdef imagetest} DoLog(' adding image: '+FImageFileList[i]); {$endif}
if FileExists(FImageFileList[i]) then
begin
{$ifdef imagetest} DoLog(' - found'); {$endif}
FileName := ExtractFileName(FImageFileList[i]);
FilePath := '/'+FixHTMLpath(ExtractFilePath(FImageFileList[i]));
FileStream.LoadFromFile(FImageFileList[i]);
FChm.AddStreamToArchive(FileName, FilePath, FileStream, True);
FileStream.Size := 0;
end
else
{$ifdef imagetest} DoLog(' - not found'){$endif};
end;
FileStream.Free;
FChm.Execute;
FChm.Free;
// we don't need to free FTempUncompressed
// FTempUncompressed.Free;
FOutChm.Free;
DeleteFile(FTempUncompressedName);
end;
function TCHMHTMLWriter.InterPretOption(const Cmd, Arg: String): boolean;
begin
Result:=True;
FNoBinToc:=False;
FnoBinIndex:=False;
if Cmd = '--toc-file' then
FTOCName := arg
else if Cmd = '--index-file' then
FIndexName := arg
else if Cmd = '--default-page' then
FDefaultPage := arg
else if Cmd = '--other-files' then
FOtherFiles := arg
else if Cmd = '--auto-index' then
FAutoIndex := True
else if Cmd = '--auto-toc' then
FAutoTOC := True
else if Cmd = '--no-bintoc' then
FNoBinToc := True
else if Cmd = '--no-binindex' then
FNoBinIndex := True
else if Cmd = '--make-searchable' then
FMakeSearchable := True
else if Cmd = '--chm-title' then
FChmTitle := arg
else
Result:=inherited InterPretOption(Cmd, Arg);
if Length(FChmTitle) = 0 then
FChmTitle := Copy(Package.Name, 2, Length(Package.Name));
end;
class procedure TCHMHTMLWriter.Usage(List: TStrings);
begin
THTMLWriter.Usage(List);
List.add('--default-page');
List.Add(SCHMUsageDefPage);
List.add('--toc-file');
List.Add(SCHMUsageTOC);
List.add('--index-file');
List.Add(SCHMUsageIndex);
List.add('--other-files');
List.Add(SCHMUsageOtrFiles);
List.add('--css-file');
List.Add(SCHMUsageCSSFile);
List.add('--auto-index');
List.Add(SCHMUsageAutoIDX);
List.add('--auto-toc');
List.Add(SCHMUsageAutoTOC);
List.add('--make-searchable');
List.Add(SCHMUsageMakeSearch);
List.Add('--chm-title');
List.Add(SCHMUsageChmTitle);
end;
Class Function TCHMHTMLWriter.FileNameExtension : String;
begin
result:='.chm';
end;
class procedure TCHMHTMLWriter.SplitImport(var AFilename, ALinkPrefix: String);
var
i: integer;
begin
i := Pos(',', AFilename);
if i > 0 then
begin //split into filename and prefix
ALinkPrefix := Copy(AFilename,i+1,Length(AFilename));
SetLength(AFilename, i-1);
end
else if ALinkPrefix = '' then
begin //synthesize outdir\pgk.xct, ms-its:pkg.chm::/
ALinkPrefix := 'ms-its:' + ChangeFileExt(ExtractFileName(AFilename), '.chm') + '::/';
AFilename := ChangeFileExt(AFilename, '.xct');
end;
end;
{$ENDIF}