Sfoglia il codice sorgente

* Markdown support for fpdoc

git-svn-id: trunk@47973 -
michael 4 anni fa
parent
commit
390be00327

+ 2 - 0
.gitattributes

@@ -19289,6 +19289,7 @@ utils/fpdoc/Makefile.fpc.fpcmake svneol=native#text/plain
 utils/fpdoc/README.txt svneol=native#text/plain
 utils/fpdoc/css.inc svneol=native#text/plain
 utils/fpdoc/dglobals.pp svneol=native#text/plain
+utils/fpdoc/dw_basemd.pp svneol=native#text/plain
 utils/fpdoc/dw_chm.pp svneol=native#text/plain
 utils/fpdoc/dw_dxml.pp svneol=native#text/plain
 utils/fpdoc/dw_html.pp svneol=native#text/plain
@@ -19297,6 +19298,7 @@ utils/fpdoc/dw_latex.pp svneol=native#text/plain
 utils/fpdoc/dw_linrtf.pp svneol=native#text/plain
 utils/fpdoc/dw_lintmpl.pp svneol=native#text/plain
 utils/fpdoc/dw_man.pp svneol=native#text/plain
+utils/fpdoc/dw_markdown.pp svneol=native#text/plain
 utils/fpdoc/dw_template.pp svneol=native#text/plain
 utils/fpdoc/dw_txt.pp svneol=native#text/plain
 utils/fpdoc/dw_xml.pp svneol=native#text/plain

+ 33 - 14
utils/fpdoc/dglobals.pp

@@ -50,10 +50,15 @@ resourcestring
   SDocConstsTypesVars        = 'Constants, types and variables';
   SDocResStrings             = 'Resource strings';
   SDocTypes                  = 'Types';
+  SDocType                   = 'Type';
   SDocConstants              = 'Constants';
+  SDocConstant               = 'Constant';
   SDocClasses                = 'Classes';
+  SDocClass                  = 'Class';
   SDocProceduresAndFunctions = 'Procedures and functions';
+  SDocProcedureOrFunction    = 'Procedure/function';
   SDocVariables              = 'Variables';
+  SDocVariable               = 'Variable';
   SDocIdentifierIndex        = 'Index';
   SDocPackageClassHierarchy  = 'Class hierarchy';
   SDocModuleIndex            = 'Index of all identifiers in unit ''%s''';
@@ -72,9 +77,13 @@ resourcestring
   SDocRemark                 = 'Remark:   ';
   SDocMethodOverview         = 'Method overview';
   SDocPropertyOverview       = 'Property overview';
+  SDocEventOverview          = 'Event overview';
   SDocInterfacesOverview     = 'Interfaces overview';
   SDocInterface              = 'Interfaces';
   SDocPage                   = 'Page';
+  SDocMember                 = 'Member';
+  SDocMembers                = 'Members';
+  SDocField                  = 'Field';
   SDocMethod                 = 'Method';
   SDocProperty               = 'Property';
   SDocAccess                 = 'Access';
@@ -83,6 +92,7 @@ resourcestring
   SDocMethods                = 'Methods';
   SDocEvents                 = 'Events';
   SDocByName                 = 'by Name';
+  SDocByInheritance          = 'By inheritance';
   SDocValue                  = 'Value';
   SDocExplanation            = 'Explanation';
   SDocProcedure              = 'Procedure';
@@ -95,6 +105,10 @@ resourcestring
   // The next line requires leading/trailing space due to XML comment layout:
   SDocGeneratedByComment     = ' Generated using FPDoc - (c) 2000-2012 FPC contributors and Sebastian Guenther, [email protected] ';
   SDocNotes                  = 'Notes';
+  SDocName                   = 'Name';
+  SDocType_s                 = 'Type(s)';
+  SDocTopic                  = 'Topic';
+  SDocNoneAVailable          = 'No members available';
   
   // Topics
   SDocRelatedTopics = 'Related topics';
@@ -1545,22 +1559,27 @@ end;
 function TFPDocEngine.FindDocNode(AElement: TPasElement): TDocNode;
 begin
   Result:=Nil;
-  If Assigned(AElement) then
+  If not Assigned(AElement) then
+    exit;
+  if aElement.CustomData is TDocNode then
+    Exit(TDocNode(aElement.CustomData));
+  if AElement.InheritsFrom(TPasUnresolvedTypeRef) then
+    Result := FindDocNode(AElement.GetModule, AElement.Name)
+  else
     begin
-    if AElement.InheritsFrom(TPasUnresolvedTypeRef) then
-      Result := FindDocNode(AElement.GetModule, AElement.Name)
-    else
-      begin
-      Result := RootDocNode.FindChild(AElement.PathName);
-      if (Result=Nil) and (AElement is TPasoperator) then
-        Result:=RootDocNode.FindChild(TPasOperator(AElement).OldName(True));
-      end;
-    if (Result=Nil) and
-       WarnNoNode and
-       (Length(AElement.PathName)>0) and
-       (AElement.PathName[1]='#') then
-      DoLog(Format('No documentation node found for identifier : %s',[AElement.PathName]));
+    Result := RootDocNode.FindChild(AElement.PathName);
+    if (Result=Nil) and (AElement is TPasoperator) then
+      Result:=RootDocNode.FindChild(TPasOperator(AElement).OldName(True));
     end;
+  if (Result<>Nil) then
+    begin
+    if aElement.CustomData=Nil then
+      aElement.CustomData:=Result;
+    end
+  else if WarnNoNode and
+          (Length(AElement.PathName)>0) and
+          (AElement.PathName[1]='#') then
+    DoLog(Format('No documentation node found for identifier : %s',[AElement.PathName]));
 end;
 
 function TFPDocEngine.FindDocNode(ARefModule: TPasModule;

+ 893 - 0
utils/fpdoc/dw_basemd.pp

@@ -0,0 +1,893 @@
+unit dw_basemd;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, dwriter, DOM, pastree, dglobals;
+
+Const
+  MaxIndents = 10;
+  MaxLists = 10;
+
+Type
+  THeaderLevel = 1..6;
+  TRender = (rStrong,rEmphasis,rCode);
+  TListType = (ltOrdered,ltUnordered, ltDefinition);
+
+  TMarkdownEngine = (meMkDocs,meNone);
+
+  { TBaseMarkdownWriter }
+
+  TBaseMarkdownWriter = class(TMultifileDocWriter)
+  private
+    FIgnoreCount : Integer;
+    FBaseImageURL: String;
+    FContentPrefix: String;
+    FCurrentIndentIndex: Word;
+    FCurrentLine: UTF8String;
+    FDefinitionSeparator: String;
+    FDefinitionTermRender: TRender;
+    FFileRendering: TRender;
+    FIndentSize: Byte;
+    FKeywordRendering: TRender;
+    FModule: TPasModule;
+    FPrefix : string;
+    FMetadata,
+    FMarkDown: TStrings;
+    FSymbolRendering: TRender;
+    FTheme: String;
+    FUnderLineRendering: TRender;
+    FVarRendering: TRender;
+    FLink : String;
+    FListStack : Integer;
+    FIndents : Array[0..MaxIndents] of Word;
+    FListTypes : Array[1..MaxLists] of TListType;
+    FTableColCount : Integer;
+    FMarkDownEngine: TMarkdownEngine;
+    function GetCurrentIndent: Word;
+    procedure SetIndentSize(AValue: Byte);
+    procedure clearIndent;
+    procedure CalcPrefix;
+  Protected
+    function CreateAllocator: TFileAllocator; override;
+    Procedure DescrEmitNotesHeader(AContext : TPasElement); override;
+    Procedure DescrEmitNotesFooter(AContext : TPasElement); override;
+    procedure DescrWriteText(const AText: DOMString); override;
+    procedure DescrBeginBold; override;
+    procedure DescrEndBold; override;
+    procedure DescrBeginItalic; override;
+    procedure DescrEndItalic; override;
+    procedure DescrBeginEmph; override;
+    procedure DescrEndEmph; override;
+    procedure DescrBeginUnderline; override;
+    procedure DescrEndUnderline; override;
+    procedure DescrWriteImageEl(const AFileName, ACaption, ALinkName : DOMString); override;
+    procedure DescrWriteFileEl(const AText: DOMString); override;
+    procedure DescrWriteKeywordEl(const AText: DOMString); override;
+    procedure DescrWriteVarEl(const AText: DOMString); override;
+    procedure DescrBeginLink(const AId: DOMString); override;
+    procedure DescrEndLink; override;
+    procedure DescrBeginURL(const AURL: DOMString); override;
+    procedure DescrEndURL; override;
+    procedure DescrWriteLinebreak; override;
+    procedure DescrBeginParagraph; override;
+    procedure DescrEndParagraph; override;
+    procedure DescrBeginCode(HasBorder: Boolean; const AHighlighterName: String); override;
+    procedure DescrWriteCodeLine(const ALine: String); override;
+    procedure DescrEndCode; override;
+    procedure DescrBeginOrderedList; override;
+    procedure DescrEndOrderedList; override;
+    procedure DescrBeginUnorderedList; override;
+    procedure DescrEndUnorderedList; override;
+    procedure DescrBeginDefinitionList; override;
+    procedure DescrEndDefinitionList; override;
+    procedure DescrBeginListItem; override;
+    procedure DescrEndListItem; override;
+    procedure DescrBeginDefinitionTerm; override;
+    procedure DescrEndDefinitionTerm; override;
+    procedure DescrBeginDefinitionEntry; override;
+    procedure DescrEndDefinitionEntry; override;
+    procedure DescrBeginSectionTitle; override;
+    procedure DescrBeginSectionBody; override;
+    procedure DescrEndSection; override;
+    procedure DescrBeginRemark; override;
+    procedure DescrEndRemark; override;
+    procedure DescrBeginTable(ColCount: Integer; HasBorder: Boolean); override;
+    procedure DescrEndTable; override;
+    procedure DescrBeginTableCaption; override;
+    procedure DescrEndTableCaption; override;
+    procedure DescrBeginTableHeadRow; override;
+    procedure DescrEndTableHeadRow; override;
+    procedure DescrBeginTableRow; override;
+    procedure DescrEndTableRow; override;
+    procedure DescrBeginTableCell; override;
+    procedure DescrEndTableCell; override;
+  Protected
+    // Emit current line, if any
+    Function OutputCurrentLine : Boolean;
+    function EscapeMarkDown(aText: Domstring): string;
+    function EscapeMarkDown(aText: String): string;
+    function CreateLink(Const aText,aLink : String) : String;
+    // Append to current line
+    procedure AppendToLine(aText: DomString; DoEscape: boolean = true);
+    procedure AppendToLine(aText: UTF8String; DoEscape: boolean = true); virtual;
+    procedure AppendToLine(aFmt: UTF8String; aArgs : Array of const; DoEscape: boolean = true);
+    // Write current line and append new line
+    procedure EmitLine(aText: UTF8String; DoEscape: boolean = true); virtual;
+    procedure EmitLine(aFmt: UTF8String; aArgs : Array of const; DoEscape: boolean = true);
+    // Append spans to current line
+    procedure AppendLink(Const aText,aLink : String);
+    Procedure AppendRendered(aText : String; aRender : TRender);
+    Procedure AppendKeyWord(aText : String); inline;
+    Procedure AppendSymbol(aText : String); inline;
+    Procedure AppendTableHeader(aHeaders: Array of String);
+    Procedure EmitCode(aCodeBlock : String; aIndent : Integer = 0);
+    Procedure EmitCode(aCodeBlock : TStrings; aIndent : Integer = 0);
+    Procedure EmitCodeLine(aCodeLine : string);
+    procedure EndSpan(aRender: TRender);
+    procedure StartSpan(aRender: TRender);
+    Procedure PushIndent(aNewIndent : Byte);
+    Procedure PopIndent;
+    Procedure StartList(aType : TListType);
+    Procedure StopList(aType : TListType);
+    Procedure BeginIgnore;
+    Procedure EndIgnore;
+    Procedure DoLineBreak;
+    Function InList : Boolean;
+    Function CurrentList : TListType;
+    Property ContentPrefix : String Read FContentPrefix Write FContentPrefix;
+  Public
+    Constructor Create(APackage: TPasPackage; AEngine: TFPDocEngine); override;
+    destructor Destroy;  override;
+    Procedure AppendHeader(aLevel : THeaderLevel; const AHeader : String; DoEscape : Boolean = true);
+    Procedure Indent;
+    Procedure Undent;
+    Procedure ClearMarkDown;
+    Procedure EnsureEmptyLine;
+    procedure SaveToFile(aFileName : string);
+    procedure AddMetaData(Const aName,aValue : string);
+    Property CurrentLine : UTF8String Read FCurrentLine Write FCurrentLine;
+    Property MarkDown : TStrings Read FMarkDown;
+    Property CurrentIndent : Word Read GetCurrentIndent;
+    Property Prefix : String Read FPrefix;
+    Property IndentSize : Byte Read FIndentSize Write SetIndentSize;
+    Property UnderLineRendering : TRender Read FUnderLineRendering Write FUnderLineRendering;
+    Property FileRendering : TRender Read FFileRendering Write FFileRendering;
+    Property VarRendering : TRender Read FVarRendering Write FVarRendering;
+    Property KeywordRendering : TRender Read FKeywordRendering Write FKeyWordRendering;
+    Property SymbolRendering : TRender Read FSymbolRendering Write FSymbolRendering;
+    Property DefinitionSeparator : String Read FDefinitionSeparator Write FDefinitionSeparator;
+    Property DefinitionTermRender : TRender Read FDefinitionTermRender Write FDefinitionTermRender;
+    Property BaseImageURL : String Read FBaseImageURL Write FBaseIMageURL;
+    Property MetaData : TStrings Read FMetaData;
+    Property MarkDownEngine : TMarkdownEngine Read FMarkDownEngine Write FMarkDownEngine;
+    Property Theme : String Read FTheme Write FTheme;
+  end;
+
+
+
+implementation
+
+resourcestring
+  SErrCannotChangeIndentSizeWhenIndented = 'Cannot change indent size while text is indented.';
+  SErrIndentMismatch = 'Indent mismatch: trying to undent when current indent too small';
+  SErrNotInList = 'Not in list';
+  SErrPopListStack = 'Pop list stack list type mismatch';
+  SErrMinListStack = 'Min list stack reached';
+  SErrMaxListStack = 'Max list stack reached';
+  SErrMinIndentStack = 'Min indent stack reached';
+  SErrMaxIndentStack = 'Max indent stack reached';
+
+procedure TBaseMarkdownWriter.SetIndentSize(AValue: Byte);
+begin
+  if FIndentSize=AValue then Exit;
+  if CurrentIndent>0 then
+    FPDocError(SErrCannotChangeIndentSizeWhenIndented);
+  FIndentSize:=AValue;
+end;
+
+function TBaseMarkdownWriter.GetCurrentIndent: Word;
+begin
+  Result:=FIndents[FCurrentIndentIndex];
+end;
+
+procedure TBaseMarkdownWriter.clearIndent;
+begin
+  FIndents[FCurrentIndentIndex]:=0;
+  CalcPrefix;
+end;
+
+procedure TBaseMarkdownWriter.CalcPrefix;
+
+begin
+  FPrefix:=StringOfChar(' ',CurrentIndent);
+end;
+
+function TBaseMarkdownWriter.CreateAllocator: TFileAllocator;
+begin
+  Result:=TLongNameFileAllocator.Create('.md');
+end;
+
+procedure TBaseMarkdownWriter.DescrEmitNotesHeader(AContext: TPasElement);
+begin
+  AppendHeader(2, SDocNotes);
+end;
+
+procedure TBaseMarkdownWriter.DescrEmitNotesFooter(AContext: TPasElement);
+begin
+  EnsureEmptyLine;
+end;
+
+function TBaseMarkdownWriter.EscapeMarkDown(aText: Domstring): string;
+
+begin
+  Result:=EscapeMarkDown(UTF8Encode(aText))
+end;
+
+function TBaseMarkdownWriter.EscapeMarkDown(aText: String): string;
+begin
+  Result:=StringReplace(aText,'*','\*',[rfReplaceAll]);
+  Result:=StringReplace(Result,'_','\_',[rfReplaceAll]);
+  Result:=StringReplace(Result,'`','\`',[rfReplaceAll]);
+end;
+
+function TBaseMarkdownWriter.CreateLink(const aText, aLink: String): String;
+begin
+  Result:=Format('[%s](%s)',[EscapeMarkDown(aText),aLink])
+end;
+
+procedure TBaseMarkdownWriter.AppendToLine(aText: DomString; DoEscape: boolean);
+
+begin
+  If FIgnoreCount>0 then
+    exit;
+  AppendToLine(UTF8Encode(aText),DoEscape);
+end;
+
+procedure TBaseMarkdownWriter.AppendToLine(aText: UTF8String; DoEscape: boolean
+  );
+begin
+  if DoEscape then
+     aText:=EscapeMarkDown(aText);
+  if (FCurrentLine='') and (FContentPrefix<>'') then
+     FCurrentLine:=FContentPrefix;
+  FCurrentLine:=FCurrentLine+aText;
+end;
+
+procedure TBaseMarkdownWriter.AppendToLine(aFmt: UTF8String;
+  aArgs: array of const; DoEscape: boolean);
+begin
+  AppendToLine(Format(aFmt,aArgs),DoEscape);
+end;
+
+procedure TBaseMarkdownWriter.EmitLine(aText: UTF8String; DoEscape: boolean);
+begin
+  OutputCurrentLine;
+  AppendToLine(aText,DoEscape);
+  OutputCurrentLine;
+end;
+
+procedure TBaseMarkdownWriter.EmitLine(aFmt: UTF8String; aArgs: array of const;
+  DoEscape: boolean);
+begin
+  EmitLine(Format(aFmt,aArgs),DoEscape);
+end;
+
+procedure TBaseMarkdownWriter.AppendLink(const aText, aLink: String);
+begin
+  AppendToLine(CreateLink(aText,aLink),False);
+end;
+
+procedure TBaseMarkdownWriter.AppendRendered(aText: String; aRender: TRender);
+begin
+  StartSpan(aRender);
+  AppendToLine(aText);
+  EndSpan(aRender);
+end;
+
+procedure TBaseMarkdownWriter.AppendKeyWord(aText: String);
+begin
+  AppendRendered(aText,KeywordRendering);
+end;
+
+procedure TBaseMarkdownWriter.AppendSymbol(aText: String);
+begin
+  AppendRendered(aText,SymbolRendering);
+end;
+
+procedure TBaseMarkdownWriter.AppendTableHeader(aHeaders: array of String);
+
+Var
+  S : String;
+
+begin
+  DescrBeginTable(Length(aHeaders),False);
+  DescrBeginTableHeadRow;
+  for S in aHeaders do
+    begin
+    DescrBeginTableCell;
+    AppendToLine(S);
+    DescrEndTableCell;
+    end;
+  DescrEndTableHeadRow;
+end;
+
+
+procedure TBaseMarkdownWriter.EmitCode(aCodeBlock: String; aIndent : Integer = 0);
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    L.Text:=aCodeBlock;
+    EmitCode(L,aIndent);
+  finally
+    L.Free;
+  end;
+end;
+
+procedure TBaseMarkdownWriter.EmitCode(aCodeBlock: TStrings; aIndent : Integer = 0);
+var
+  S,aPrefix : string;
+begin
+  aPrefix:=StringOfChar(' ',aIndent);
+  For S in aCodeBlock do
+    EmitCodeLine(aPrefix+S);
+end;
+
+procedure TBaseMarkdownWriter.EmitCodeLine(aCodeLine: string);
+begin
+  EmitLine(aCodeLine,False);
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteText(const AText: DOMString);
+begin
+  AppendToLine(aText);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginBold;
+begin
+  AppendToLine('**',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndBold;
+begin
+  AppendToLine('**',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginItalic;
+begin
+  AppendToLine('*',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndItalic;
+begin
+  AppendToLine('*',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginEmph;
+begin
+  AppendToLine('*',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndEmph;
+begin
+  AppendToLine('*',False);
+end;
+
+procedure TBaseMarkdownWriter.StartSpan(aRender : TRender);
+
+begin
+  case aRender of
+    rStrong : AppendToLine('**',False);
+    rEmphasis : AppendToLine('*',False);
+    rCode : AppendToLine('`',False);
+  end;
+end;
+
+procedure TBaseMarkdownWriter.PushIndent(aNewIndent: Byte);
+begin
+  if FCurrentIndentIndex>=MaxIndents then
+     FPDocError(SErrMaxIndentStack);
+  Inc(FCurrentIndentIndex);
+  Findents[FCurrentIndentIndex]:=aNewIndent;
+  CalcPrefix;
+end;
+
+procedure TBaseMarkdownWriter.PopIndent;
+begin
+  if FCurrentIndentIndex<=0 then
+     FPDocError(SErrMinIndentStack);
+  Dec(FCurrentIndentIndex);
+end;
+
+procedure TBaseMarkdownWriter.StartList(aType: TListType);
+begin
+  If FListStack>=MaxLists then
+    FPDocError(SErrMaxListStack);
+  Inc(FListStack);
+  FListTypes[FListStack]:=aType;
+end;
+
+procedure TBaseMarkdownWriter.StopList(aType: TListType);
+begin
+  If FListStack<=0 then
+    FPDocError(SErrMinListStack);
+  if FListTypes[FListStack]<>aType then
+    FPDocError(SErrPopListStack);
+  Dec(FListStack);
+end;
+
+procedure TBaseMarkdownWriter.BeginIgnore;
+begin
+  Inc(FIgnoreCount);
+end;
+
+procedure TBaseMarkdownWriter.EndIgnore;
+begin
+  If FignoreCount>0 then
+    Dec(FIgnoreCount);
+end;
+
+procedure TBaseMarkdownWriter.DoLineBreak;
+begin
+  if FCurrentLine<>'' then
+    begin
+    FCurrentLine:=FCurrentLine+'  ';
+    OutputCurrentLine;
+    end;
+end;
+
+function TBaseMarkdownWriter.InList: Boolean;
+begin
+  Result:=FlistStack>0;
+end;
+
+function TBaseMarkdownWriter.CurrentList: TListType;
+begin
+  if FListStack=0 then
+    FPDOcError(SErrNotInList);
+end;
+
+procedure TBaseMarkdownWriter.EndSpan(aRender : TRender);
+
+begin
+  case aRender of
+    rStrong : AppendToLine('**',False);
+    rEmphasis : AppendToLine('*',False);
+    rCode : AppendToLine('`',False);
+  end;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginUnderline;
+begin
+  StartSpan(UnderlineRendering);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndUnderline;
+begin
+  EndSpan(UnderlineRendering);
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteImageEl(const AFileName, ACaption, ALinkName : DOMString);
+
+Var
+  D,FN : String;
+  L : integer;
+begin
+  // Determine URL for image.
+  If (Module=Nil) then
+    D:=Allocator.GetRelativePathToTop(Package)
+  else
+    D:=Allocator.GetRelativePathToTop(Module);
+  L:=Length(D);
+  If (L>0) and (D[L]<>'/') then
+    D:=D+'/';
+
+  FN:=UTF8Decode(D + BaseImageURL) + AFileName;
+  EnsureEmptyLine;
+  AppendToLine('!['+aCaption+']('+FN+')',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteFileEl(const AText: DOMString);
+
+begin
+  AppendRendered(aText,FileRendering);
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteKeywordEl(const AText: DOMString);
+begin
+  AppendKeyWord(UTF8ENcode(aText));
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteVarEl(const AText: DOMString);
+begin
+  AppendRendered(aText,VarRendering);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginLink(const AId: DOMString);
+var
+  a,s,n : String;
+
+begin
+  a:=UTF8Encode(AId);
+  s := UTF8Encode(ResolveLinkID(a));
+
+  if Length(s) > 0 then
+    begin
+    FLink:=S;
+    AppendToLine('[');
+    end
+  else
+    begin
+    FLink:='';
+    if assigned(module) then
+      s:=module.name
+    else
+      s:='?';
+    if a='' then a:='<empty>';
+    if Assigned(CurrentContext) then
+      N:=CurrentContext.Name
+    else
+      N:='?';
+    DoLog(SErrUnknownLinkID, [s,n,a]);
+    end
+end;
+
+procedure TBaseMarkdownWriter.DescrEndLink;
+begin
+  AppendToLine(']('+FLink+') ',false);
+  FLink:='';
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginURL(const AURL: DOMString);
+begin
+  FLink:=aURL;
+  AppendToLine('[');
+end;
+
+procedure TBaseMarkdownWriter.DescrEndURL;
+begin
+  AppendToLine(']('+FLink+') ',false);
+  FLink:='';
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteLinebreak;
+begin
+  AppendToLine('  ');
+  OutputCurrentLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginParagraph;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrEndParagraph;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginCode(HasBorder: Boolean; const AHighlighterName: String);
+
+Var
+  hl : string;
+
+begin
+  hl:=AHighlighterName;
+  if SameText(hl,'Pascal') or (hl='') then
+    hl:='delphi';
+  OutputCurrentLine;
+  AppendToLine('```'+hl,False);
+  PushIndent(0);
+end;
+
+procedure TBaseMarkdownWriter.DescrWriteCodeLine(const ALine: String);
+begin
+  EmitCodeLine(aLine);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndCode;
+begin
+  OutputCurrentLine;
+  AppendToLine('```',False);
+  OutputCurrentLine;
+  PopIndent;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginOrderedList;
+begin
+  StartList(ltOrdered);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndOrderedList;
+begin
+  StopList(ltOrdered);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginUnorderedList;
+begin
+  StartList(ltUnordered);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndUnorderedList;
+begin
+  StopList(ltUnordered);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginDefinitionList;
+begin
+  StartList(ltDefinition);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndDefinitionList;
+begin
+  StopList(ltDefinition);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginListItem;
+begin
+  Case CurrentList of
+    ltOrdered   : AppendToLine('1. ');
+    ltUnordered : AppendToLine('- ');
+    ltDefinition :
+      begin
+      AppendToLine('- ');
+      end;
+  end;
+end;
+
+
+procedure TBaseMarkdownWriter.DescrEndListItem;
+begin
+  OutputCurrentLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginDefinitionTerm;
+begin
+  if MarkDownEngine=meMkDocs then
+    EnsureEmptyLine
+  else
+    begin
+    AppendToLine('- ');
+    StartSpan(DefinitionTermRender);
+    end;
+end;
+
+procedure TBaseMarkdownWriter.DescrEndDefinitionTerm;
+begin
+  if MarkDownEngine=meMkDocs then
+    OutputCurrentLine
+  else
+    EndSpan(DefinitionTermRender);
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginDefinitionEntry;
+begin
+  if MarkDownEngine=meMkDocs then
+    begin
+    AppendToLine(':    ');
+    Indent;
+    end
+  else
+    AppendToLine(DefinitionSeparator);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndDefinitionEntry;
+begin
+  OutputCurrentLine;
+  if MarkDownEngine=meMkDocs then
+    begin
+    Undent;
+    EnsureEmptyLine;
+    end;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginSectionTitle;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginSectionBody;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrEndSection;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginRemark;
+begin
+  if MarkDownEngine=meMkDocs then
+    begin
+    EnsureEmptyLine;
+    AppendToLine('!!! Remark',False);
+    OutputCurrentLine;
+    end
+  else
+    FContentPrefix:='> ';
+end;
+
+procedure TBaseMarkdownWriter.DescrEndRemark;
+begin
+  if MarkDownEngine=meMkDocs then
+    begin
+    OutputCurrentLine;
+    AppendToLine('!!!',False);
+    end;
+  EnsureEmptyLine;
+  FContentPrefix:='';
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginTable(ColCount: Integer; HasBorder: Boolean);
+begin
+  EnsureEmptyLine;
+  FTableColCount:=ColCount;
+end;
+
+procedure TBaseMarkdownWriter.DescrEndTable;
+begin
+  EnsureEmptyLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginTableCaption;
+begin
+  BeginIgnore;
+end;
+
+procedure TBaseMarkdownWriter.DescrEndTableCaption;
+begin
+  EndIgnore;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginTableHeadRow;
+
+begin
+end;
+
+procedure TBaseMarkdownWriter.DescrEndTableHeadRow;
+
+Var
+  I : Integer;
+
+begin
+  AppendToLine(' |',False);
+  OutputCurrentLine;
+  AppendToLine('|',False);
+  For I:=1 to FTableColCount do
+    AppendToLine('---|',False);
+  OutputCurrentLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginTableRow;
+begin
+end;
+
+procedure TBaseMarkdownWriter.DescrEndTableRow;
+begin
+  AppendToLine(' |',False);
+  OutputCurrentLine;
+end;
+
+procedure TBaseMarkdownWriter.DescrBeginTableCell;
+begin
+  AppendToLine('| ',False);
+end;
+
+procedure TBaseMarkdownWriter.DescrEndTableCell;
+begin
+  AppendToLine(' ',False);
+end;
+
+function TBaseMarkdownWriter.OutputCurrentLine: Boolean;
+begin
+  If FIgnoreCount>0 then
+    exit(False);
+  Result:=FCurrentLine<>'';
+  if Result then
+    begin
+    FMarkDown.Add(Prefix+FCurrentLine);
+    FCurrentLine:='';
+    end;
+end;
+
+constructor TBaseMarkdownWriter.Create(APackage: TPasPackage;
+  AEngine: TFPDocEngine);
+begin
+  inherited Create(APackage, AEngine);
+  FMarkDown:=TStringList.Create;
+  FMetaData:=TStringList.Create;
+  FMetaData.NameValueSeparator:=':';
+  FIndents[FCurrentIndentIndex]:=0;
+  CalcPrefix;
+  Theme:='readthedocs';
+end;
+
+destructor TBaseMarkdownWriter.Destroy;
+begin
+  FreeAndNil(FMarkDown);
+  inherited Destroy;
+end;
+
+procedure TBaseMarkdownWriter.AppendHeader(aLevel: THeaderLevel;
+  const AHeader: String; DoEscape : Boolean = true);
+begin
+  EnsureEmptyLine;
+  AppendToLine(StringOfChar('#',aLevel)+' ',False);
+  AppendToLine(aHeader,DoEscape);
+end;
+
+procedure TBaseMarkdownWriter.Indent;
+begin
+  Inc(FIndents[FCurrentIndentIndex],IndentSize);
+  CalcPrefix;
+end;
+
+procedure TBaseMarkdownWriter.Undent;
+begin
+  if IndentSize>CurrentIndent then
+    FPDocError(SErrIndentMismatch);
+  Dec(FIndents[FCurrentIndentIndex],IndentSize);
+  CalcPrefix;
+end;
+
+procedure TBaseMarkdownWriter.ClearMarkDown;
+begin
+  FMarkDown.Clear;
+  FMetaData.Clear;
+  FCurrentLine:='';
+  FContentPrefix:='';
+  FCurrentIndentIndex:=0;;
+  FIndents[FCurrentIndentIndex]:=0;
+  CalcPrefix;
+end;
+
+
+procedure TBaseMarkdownWriter.EnsureEmptyLine;
+begin
+  if OutputCurrentLine then
+    FMarkDown.Add('')
+  else if (FmarkDown.Count>0) and (FMarkDown[FmarkDown.Count-1]<>'') then
+    FMarkDown.Add('');
+ end;
+
+procedure TBaseMarkdownWriter.SaveToFile(aFileName: string);
+
+Var
+  Doc: TStrings;
+  I : Integer;
+  N,V : String;
+
+begin
+  Doc:=TStringList.Create;
+  try
+    if FMetadata.Count>0 then
+      begin
+      Doc.Add('---');
+      For I:=0 to FMetadata.Count-1 do
+        begin
+        FMetaData.GetNameValue(I,N,V);
+        Doc.Add(Lowercase(N)+': "'+V+'"');
+        end;
+      Doc.Add('---');
+      end;
+    Doc.AddStrings(FMarkDown);
+    Doc.SaveToFile(aFileName);
+  finally
+    Doc.Free;
+  end;
+end;
+
+procedure TBaseMarkdownWriter.AddMetaData(const aName, aValue: string);
+begin
+  FMetadata.Values[aName]:=aValue;
+end;
+
+end.
+

+ 99 - 3
utils/fpdoc/dw_chm.pp

@@ -7,6 +7,13 @@ uses Classes, DOM, DOM_HTML,
 
 type
 
+  { TCHmFileNameAllocator }
+
+  TCHmFileNameAllocator = Class(TLongNameFileAllocator)
+    // Override this, because the logic messes up the filenames for plain html files.
+    function GetFilename(AElement: TPasElement; ASubindex: Integer): String; override;
+  end;
+
   { TFpDocChmWriter }
 
   TFpDocChmWriter = class (TChmWriter)
@@ -44,7 +51,8 @@ type
     procedure GenerateTOC;
     procedure GenerateIndex;
   public
-    procedure WriteHTMLPages; override;
+    procedure WriteDoc; override;
+    function CreateAllocator: TFileAllocator; override;
     
     function  InterPretOption(const Cmd,Arg : String): boolean; override;
 
@@ -57,6 +65,89 @@ implementation
 
 uses SysUtils, HTMWrite;
 
+{ TCHmFileNameAllocator }
+
+function TCHmFileNameAllocator.GetFilename(AElement: TPasElement; ASubindex: Integer): String;
+var
+  n,s: String;
+  i: Integer;
+  excl: Boolean; //search
+begin
+  Result:='';
+  excl := False;
+  if AElement.ClassType = TPasPackage then
+  begin
+    Result := 'index';
+    excl := True;
+  end
+  else if AElement.ClassType = TPasModule then
+  begin
+    Result := LowerCase(AElement.Name) + PathDelim + 'index';
+    excl := True;
+  end
+  else
+  begin
+    if AElement is TPasOperator then
+    begin
+      if Assigned(AElement.Parent) then
+        result:=LowerCase(AElement.Parent.PathName);
+      With TPasOperator(aElement) do
+        Result:= Result + 'op-'+OperatorTypeToOperatorName(OperatorType);
+      s := '';
+      N:=LowerCase(aElement.Name); // Should not contain any weird chars.
+      Delete(N,1,Pos('(',N));
+      i := 1;
+      Repeat
+        I:=Pos(',',N);
+        if I=0 then
+          I:=Pos(')',N);
+        if I>1 then
+          begin
+          if (S<>'') then
+            S:=S+'-';
+          S:=S+Copy(N,1,I-1);
+          end;
+        Delete(N,1,I);
+      until I=0;
+      // First char is maybe :
+      if (N<>'') and  (N[1]=':') then
+        Delete(N,1,1);
+      Result:=Result + '-'+ s + '-' + N;
+    end
+      else
+    begin
+      Result := LowerCase(AElement.PathName);
+      excl := (ASubindex > 0);
+    end;
+    // searching for TPasModule - it is on the 2nd level
+    if Assigned(AElement.Parent) then
+      while Assigned(AElement.Parent.Parent) do
+        AElement := AElement.Parent;
+    // cut off Package Name
+    Result := Copy(Result, Length(AElement.Parent.Name) + 2, MaxInt);
+    // to skip dots in unit name
+    i := Length(AElement.Name);
+    while (i <= Length(Result)) and (Result[i] <> '.') do
+      Inc(i);
+    if (i <= Length(Result)) and (i > 0) then
+      Result[i] := PathDelim;
+    if excl or (Length(Result)=0) then
+      begin
+        // exclude the from full text search index
+        s:= '.'+ExtractFileName(Result + '.');
+        n:= ExtractFileDir(Result);
+        Result := n + DirectorySeparator + s;
+        Result := Copy(Result, 1, Length(Result)-1);
+      end;
+  end;
+
+  if ASubindex > 0 then
+    Result := Result + '-' + IntToStr(ASubindex);
+
+  Result := Result + Extension;
+//  Writeln('Result filename : ',Result);
+end;
+
 { TFpDocChmWriter }
 
 procedure TFpDocChmWriter.FileAdded ( AStream: TStream;
@@ -523,7 +614,7 @@ begin
   DoLog('Generating Index Done');
 end;
 
-procedure TCHMHTMLWriter.WriteHTMLPages;
+procedure TCHMHTMLWriter.WriteDoc;
 var
   i: Integer;
   PageDoc: TXMLDocument;
@@ -605,6 +696,11 @@ begin
   DeleteFile(FTempUncompressedName);
 end;
 
+function TCHMHTMLWriter.CreateAllocator: TFileAllocator;
+begin
+  Result:=TCHmFileNameAllocator.Create('.html');
+end;
+
 function TCHMHTMLWriter.InterPretOption(const Cmd, Arg: String): boolean;
 begin
   Result:=True;
@@ -660,7 +756,7 @@ begin
   List.Add(SCHMUsageChmTitle);
 end;
 
-Class Function TCHMHTMLWriter.FileNameExtension : String; 
+class function TCHMHTMLWriter.FileNameExtension: String;
 
 begin
   result:='.chm';

+ 27 - 575
utils/fpdoc/dw_html.pp

@@ -21,80 +21,26 @@ interface
 
 uses Classes, DOM, DOM_HTML, dGlobals, PasTree, dWriter;
 
-const
-  // Subpage indices for modules
-  ResstrSubindex = 1;
-  ConstsSubindex = 2;
-  TypesSubindex = 3;
-  ClassesSubindex = 4;
-  ProcsSubindex = 5;
-  VarsSubindex = 6;
-  // Maybe needed later for topic overview ??
-  TopicsSubIndex = 7;
-  IndexSubIndex = 8;
-  ClassHierarchySubIndex = 9;
-
-  // Subpage indices for classes
-  PropertiesByInheritanceSubindex = 1;
-  PropertiesByNameSubindex = 2;
-  MethodsByInheritanceSubindex = 3;
-  MethodsByNameSubindex = 4;
-  EventsByInheritanceSubindex = 5;
-  EventsByNameSubindex = 6;
 
 type
 
-  TFileAllocator = class
-  public
-    procedure AllocFilename(AElement: TPasElement; ASubindex: Integer); virtual;
-    function GetFilename(AElement: TPasElement;
-      ASubindex: Integer): String; virtual; abstract;
-    function GetRelativePathToTop(AElement: TPasElement): String; virtual;
-    function GetCSSFilename(ARelativeTo: TPasElement): DOMString; virtual;
-  end;
-
-  { TLongNameFileAllocator }
-
-  TLongNameFileAllocator = class(TFileAllocator)
-  private
-    FExtension: String;
-  public
-    constructor Create(const AExtension: String);
-    function GetFilename(AElement: TPasElement; ASubindex: Integer): String; override;
-    function GetRelativePathToTop(AElement: TPasElement): String; override;
-    property Extension: String read FExtension;
-  end;
-
-
-  TPageInfo = class
-    Element: TPasElement;
-    SubpageIndex: Integer;
-  end;
-
-
   { THTMLWriter }
 
-  THTMLWriter = class(TFPDocWriter)
+  THTMLWriter = class(TMultiFileDocWriter)
   private
     FImageFileList: TStrings;
     FOnTest: TNotifyEvent;
     FCharSet : String;
     procedure CreateMinusImage;
     procedure CreatePlusImage;
-    function GetPageCount: Integer;
     procedure SetOnTest(const AValue: TNotifyEvent);
   protected
     FCSSFile: String;
-    FAllocator: TFileAllocator;
-    CurDirectory: String;       // relative to curdir of process
-    BaseDirectory: String;      // relative path to package base directory
-    PageInfos: TObjectList;     // list of TPageInfo objects
 
     Doc: THTMLDocument;
     HeadElement,
     BodyElement, TitleElement: TDOMElement;
 
-    Module: TPasModule;
 
     OutputNodeStack: TList;
     CurOutputNode: TDOMNode;
@@ -111,12 +57,10 @@ type
     FUseMenuBrackets: Boolean;
 
     procedure AppendFragment(aParentNode: TDOMElement; aStream: TStream);
-    Procedure CreateAllocator; virtual;
+    function CreateAllocator : TFileAllocator; override;
+    procedure WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);  override;
+
     procedure CreateCSSFile; virtual;
-    function ResolveLinkID(const Name: String; Level : Integer = 0): DOMString;
-    function ResolveLinkIDInUnit(const Name,AUnitName: String): DOMString;
-    function ResolveLinkWithinPackage(AElement: TPasElement;
-      ASubpageIndex: Integer): String;
 
     // Helper functions for creating DOM elements
     function CreateEl(Parent: TDOMNode; const AName: DOMString): THTMLElement;
@@ -255,7 +199,6 @@ type
     function CreateXHTMLPage(AElement: TPasElement; ASubpageIndex: Integer): TXMLDocument;
 
     // Start producing html complete package documentation
-    procedure WriteHTMLPages; virtual;
     procedure WriteXHTMLPages;
 
     Function InterPretOption(Const Cmd,Arg : String) : boolean; override;
@@ -264,9 +207,7 @@ type
     class procedure Usage(List: TStrings); override;
     Class procedure SplitImport(var AFilename, ALinkPrefix: String); override;
     Property SearchPage: String Read FSearchPage Write FSearchPage;
-    property Allocator: TFileAllocator read FAllocator;
 
-    property PageCount: Integer read GetPageCount;
     Property IncludeDateInFooter : Boolean Read FIDF Write FIDF;
     Property DateFormat : String Read FDateFormat Write FDateFormat;
     property OnTest: TNotifyEvent read FOnTest write SetOnTest;
@@ -293,354 +234,8 @@ begin
   Result:=StringReplace(S,'\','/',[rfReplaceAll]);
 end;
 
-
-procedure TFileAllocator.AllocFilename(AElement: TPasElement;
-  ASubindex: Integer);
-begin
-end;
-
-function TFileAllocator.GetRelativePathToTop(AElement: TPasElement): String;
-begin
-  Result:='';
-end;
-
-function TFileAllocator.GetCSSFilename(ARelativeTo: TPasElement): DOMString;
-begin
-  Result := Utf8Decode(GetRelativePathToTop(ARelativeTo)) + 'fpdoc.css';
-end;
-
-
-constructor TLongNameFileAllocator.Create(const AExtension: String);
-begin
-  inherited Create;
-  FExtension := AExtension;
-end;
-
-function TLongNameFileAllocator.GetFilename(AElement: TPasElement; ASubindex: Integer): String;
-
-var
-  n,s: String;
-  i: Integer;
-  excl: Boolean; //search
-begin
-  Result:='';
-  excl := False;
-  if AElement.ClassType = TPasPackage then
-  begin
-    Result := 'index';
-    excl := True;
-  end
-    else if AElement.ClassType = TPasModule then
-  begin
-    Result := LowerCase(AElement.Name) + PathDelim + 'index';
-    excl := True;
-  end
-    else
-  begin
-    if AElement is TPasOperator then
-    begin
-      if Assigned(AElement.Parent) then
-        result:=LowerCase(AElement.Parent.PathName);
-      With TPasOperator(aElement) do
-        Result:= Result + 'op-'+OperatorTypeToOperatorName(OperatorType);
-      s := '';
-      N:=LowerCase(aElement.Name); // Should not contain any weird chars.
-      Delete(N,1,Pos('(',N));
-      i := 1;
-      Repeat
-        I:=Pos(',',N);
-        if I=0 then
-          I:=Pos(')',N);
-        if I>1 then
-          begin
-          if (S<>'') then
-            S:=S+'-';
-          S:=S+Copy(N,1,I-1);
-          end;
-        Delete(N,1,I);
-      until I=0;
-      // First char is maybe :
-      if (N<>'') and  (N[1]=':') then
-        Delete(N,1,1);
-      Result:=Result + '-'+ s + '-' + N;
-    end
-      else
-    begin
-      Result := LowerCase(AElement.PathName);
-      excl := (ASubindex > 0);
-    end;
-    // searching for TPasModule - it is on the 2nd level
-    if AElement.GetModule <> nil then
-      AElement := AElement.GetModule 
-    else
-      Raise EFPDocWriterError.Create(
-      'TLongNameFileAllocator error: Unresolved module name for element: ' +AElement.PathName);
-    // cut off Package Name
-    Result := Copy(Result, Length(AElement.Parent.Name) + 2, MaxInt);
-    // to skip dots in unit name
-    i := Length(AElement.Name);
-    while (i <= Length(Result)) and (Result[i] <> '.') do
-      Inc(i);
-    if (i <= Length(Result)) and (i > 0) then
-      Result[i] := PathDelim;
-    if excl or (Length(Result)=0) then
-      begin
-        // exclude the from full text search index
-        s:= '.'+ExtractFileName(Result + '.');
-        n:= ExtractFileDir(Result);
-        Result := n + DirectorySeparator + s;
-        Result := Copy(Result, 1, Length(Result)-1);
-      end;
-  end;
-
-  if ASubindex > 0 then
-    Result := Result + '-' + IntToStr(ASubindex);
-
-  Result := Result + Extension;
-//  Writeln('Result filename : ',Result);
-end;
-
-function TLongNameFileAllocator.GetRelativePathToTop(AElement: TPasElement): String;
-begin
-  if (AElement.ClassType=TPasPackage) then
-    Result := ''
-  else if (AElement.ClassType=TTopicElement) then
-    begin
-    If (AElement.Parent.ClassType=TTopicElement) then
-      Result:='../'+GetRelativePathToTop(AElement.Parent)
-    else if (AElement.Parent.ClassType=TPasPackage) then
-      Result:=''
-    else if (AElement.Parent.ClassType=TPasModule) then
-      Result:='../';
-    end
-  else
-    Result := '../';
-end;
-
-Type
-
-  { TLinkData }
-
-  TLinkData = Class(TObject)
-    FPathName,
-    FLink,
-    FModuleName : String;
-
-    Constructor Create(Const APathName,ALink,AModuleName : string);
-  end;
-
-{ TLinkData }
-
-constructor TLinkData.Create(Const APathName, ALink, AModuleName: string);
-begin
-  FPathName:=APathName;
-  FLink:=ALink;
-  FModuleName:=AModuleName;
-end;
-
 constructor THTMLWriter.Create(APackage: TPasPackage; AEngine: TFPDocEngine);
 
-  procedure AddPage(AElement: TPasElement; ASubpageIndex: Integer);
-  var
-    PageInfo: TPageInfo;
-  begin
-    PageInfo := TPageInfo.Create;
-    PageInfo.Element := AElement;
-    PageInfo.SubpageIndex := ASubpageIndex;
-    PageInfos.Add(PageInfo);
-    Allocator.AllocFilename(AElement, ASubpageIndex);
-    if ASubpageIndex = 0 then
-      Engine.AddLink(AElement.PathName,
-        Allocator.GetFilename(AElement, ASubpageIndex));
-  end;
-
-  procedure AddTopicPages(AElement: TPasElement);
-
-  var
-    PreviousTopic,
-    TopicElement : TTopicElement;
-    PageInfo : TPageInfo;
-    DocNode,
-    TopicNode : TDocNode;
-
-  begin
-    DocNode:=Engine.FindDocNode(AElement);
-    If not Assigned(DocNode) then
-      exit;
-    TopicNode:=DocNode.FirstChild;
-    PreviousTopic:=Nil;
-    While Assigned(TopicNode) do
-      begin
-      If TopicNode.TopicNode then
-        begin
-        TopicElement:=TTopicElement.Create(TopicNode.Name,AElement);
-        Topics.Add(TopicElement);
-        TopicElement.TopicNode:=TopicNode;
-        TopicElement.Previous:=PreviousTopic;
-        If Assigned(PreviousTopic) then
-          PreviousTopic.Next:=TopicElement;
-        PreviousTopic:=TopicElement;
-        if AElement is TTopicElement then
-          TTopicElement(AElement).SubTopics.Add(TopicElement);
-        PageInfo := TPageInfo.Create;
-        PageInfo.Element := TopicElement;
-        PageInfo.SubpageIndex := 0;
-        PageInfos.Add(PageInfo);
-        Allocator.AllocFilename(TopicElement,0);
-        Engine.AddLink(TopicElement.PathName, Allocator.GetFilename(TopicElement,0));
-        if AElement is TTopicElement then
-          TTopicElement(AElement).SubTopics.Add(TopicElement)
-        else // Only one level of recursion.
-          AddTopicPages(TopicElement);
-        end;
-      TopicNode:=TopicNode.NextSibling;
-      end;
-  end;
-
-
-  Function HaveClasses(AModule: TPasModule) : Boolean;
-
-  begin
-    result:=assigned(AModule)
-           and assigned(AModule.InterfaceSection)
-           and assigned(AModule.InterfaceSection.Classes)
-           and (AModule.InterfaceSection.Classes.Count>0);
-  end;
-
-  procedure AddPages(AElement: TPasElement; ASubpageIndex: Integer;
-    AList: TFPList);
-  var
-    i,j: Integer;
-    R : TPasRecordtype;
-    FPEl : TPasElement;
-    DocNode: TDocNode;
-  begin
-    if AList.Count > 0 then
-      begin
-      AddPage(AElement, ASubpageIndex);
-      for i := 0 to AList.Count - 1 do
-        begin
-        AddPage(TPasElement(AList[i]), 0);
-        if (TObject(AList[i]) is TPasRecordType) then
-          begin
-          R:=TObject(AList[I]) as TPasRecordType;
-          For J:=0 to R.Members.Count-1 do
-            begin
-            FPEl:=TPasElement(R.Members[J]);
-            if ((FPEL is TPasProperty) or (FPEL is TPasProcedureBase))
-               and Engine.ShowElement(FPEl) then
-                 begin
-                 DocNode := Engine.FindDocNode(FPEl);
-                 if Assigned(DocNode) then
-                   AddPage(FPEl, 0);
-                 end;
-            end;
-          end;
-        end;
-      end;
-  end;
-
-  Procedure AddClassMemberPages(AModule: TPasModule; LinkList : TObjectList);
-  var
-    i, j, k: Integer;
-    ClassEl: TPasClassType;
-    FPEl, AncestorMemberEl: TPasElement;
-    DocNode: TDocNode;
-    ALink : DOMString;
-    DidAutolink: Boolean;
-
-  begin
-  for i := 0 to AModule.InterfaceSection.Classes.Count - 1 do
-    begin
-    ClassEl := TPasClassType(AModule.InterfaceSection.Classes[i]);
-    AddPage(ClassEl, 0);
-    // !!!: Only add when there are items
-    AddPage(ClassEl, PropertiesByInheritanceSubindex);
-    AddPage(ClassEl, PropertiesByNameSubindex);
-    AddPage(ClassEl, MethodsByInheritanceSubindex);
-    AddPage(ClassEl, MethodsByNameSubindex);
-    AddPage(ClassEl, EventsByInheritanceSubindex);
-    AddPage(ClassEl, EventsByNameSubindex);
-    for j := 0 to ClassEl.Members.Count - 1 do
-      begin
-      FPEl := TPasElement(ClassEl.Members[j]);
-      if Not Engine.ShowElement(FPEl) then
-        continue;
-      DocNode := Engine.FindDocNode(FPEl);
-      if Assigned(DocNode) then
-        begin
-        if Assigned(DocNode.Node) then
-          ALink:=DocNode.Node['link']
-        else
-          ALink:='';
-        If (ALink<>'') then
-          LinkList.Add(TLinkData.Create(FPEl.PathName,UTF8Encode(ALink),AModule.name))
-        else
-          AddPage(FPEl, 0);
-        end
-      else
-        begin
-        DidAutolink := False;
-        if Assigned(ClassEl.AncestorType) and
-          (ClassEl.AncestorType.ClassType.inheritsfrom(TPasClassType)) then
-          begin
-          for k := 0 to TPasClassType(ClassEl.AncestorType).Members.Count - 1 do
-            begin
-            AncestorMemberEl :=
-              TPasElement(TPasClassType(ClassEl.AncestorType).Members[k]);
-            if AncestorMemberEl.Name = FPEl.Name then
-              begin
-              DocNode := Engine.FindDocNode(AncestorMemberEl);
-              if Assigned(DocNode) then
-                begin
-                DidAutolink := True;
-                Engine.AddLink(FPEl.PathName,
-                  Engine.FindAbsoluteLink(AncestorMemberEl.PathName));
-                break;
-                end;
-              end;
-            end;
-          end;
-        if not DidAutolink then
-          AddPage(FPEl, 0);
-        end;
-      end;
-    end;
-    end;
-
-  procedure ScanModule(AModule: TPasModule; LinkList : TObjectList);
-  var
-    i: Integer;
-    s: String;
-
-  begin
-    if not assigned(Amodule.Interfacesection) then
-      exit; 
-    AddPage(AModule, 0);
-    AddPage(AModule,IndexSubIndex);
-    AddTopicPages(AModule);
-    with AModule do
-      begin
-      if InterfaceSection.ResStrings.Count > 0 then
-        begin
-        AddPage(AModule, ResstrSubindex);
-        s := Allocator.GetFilename(AModule, ResstrSubindex);
-        for i := 0 to InterfaceSection.ResStrings.Count - 1 do
-          with TPasResString(InterfaceSection.ResStrings[i]) do
-            Engine.AddLink(PathName, s + '#' + LowerCase(Name));
-        end;
-      AddPages(AModule, ConstsSubindex, InterfaceSection.Consts);
-      AddPages(AModule, TypesSubindex, InterfaceSection.Types);
-      if InterfaceSection.Classes.Count > 0 then
-        begin
-        AddPage(AModule, ClassesSubindex);
-        AddClassMemberPages(AModule,LinkList);
-        end;
-
-      AddPages(AModule, ProcsSubindex, InterfaceSection.Functions);
-      AddPages(AModule, VarsSubindex, InterfaceSection.Variables);
-      end;
-  end;
 
 var
   i: Integer;
@@ -649,52 +244,19 @@ var
 
 begin
   inherited Create(APackage, AEngine);
-
   // should default to true since this is the old behavior
   UseMenuBrackets:=True;
-
   IndexColCount:=3;
   Charset:='iso-8859-1';
-  CreateAllocator;
   OutputNodeStack := TList.Create;
-
-  PageInfos := TObjectList.Create;
   FImageFileList := TStringList.Create;
-
-  // Allocate page for the package itself, if a name is given (i.e. <> '#')
-  if Length(Package.Name) > 1 then
-    begin
-    AddPage(Package, 0);
-    AddPage(Package,IndexSubIndex);
-    I:=0;
-    H:=False;
-    While (I<Package.Modules.Count) and Not H do
-      begin
-      H:=HaveClasses(TPasModule(Package.Modules[i]));
-      Inc(I);
-      end;
-    if H then
-      AddPage(Package,ClassHierarchySubIndex);
-    AddTopicPages(Package);
-    end;
-  L:=TObjectList.Create;
-  try
-    for i := 0 to Package.Modules.Count - 1 do
-      ScanModule(TPasModule(Package.Modules[i]),L);
-    // Resolve links
-    For I:=0 to L.Count-1 do
-      With TLinkData(L[i]) do
-        Engine.AddLink(FPathName,UTF8Encode(ResolveLinkIDInUnit(FLink,FModuleName)));
-  finally
-    L.Free;
-  end;
+  AllocatePages;
 end;
 
 destructor THTMLWriter.Destroy;
 begin
   PageInfos.Free;
   OutputNodeStack.Free;
-  FAllocator.Free;
   FImageFileList.Free;
   inherited Destroy;
 end;
@@ -746,57 +308,28 @@ begin
   Result := nil;
 end;
 
-procedure CreatePath(const AFilename: String);
-var
-  EndIndex: Integer;
-  Path: String;
-begin
-  EndIndex := Length(AFilename);
-  if EndIndex = 0 then
-    exit;
-  while not (AFilename[EndIndex] in AllowDirectorySeparators) do
-  begin
-    Dec(EndIndex);
-    if EndIndex = 0 then
-      exit;
-  end;
 
-  Path := Copy(AFilename, 1, EndIndex - 1);
-  if not DirectoryExists(Path) then
-  begin
-    CreatePath(Path);
-    MkDir(Path);
+procedure THTMLWriter.WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);
+
+Var
+  PageDoc: TXMLDocument;
+
+begin
+  PageDoc := CreateHTMLPage(aElement, aSubpageIndex);
+  try
+    //writeln('Element: ',Element.PathName, ' FileName: ', Filename);
+    WriteHTMLFile(PageDoc, aFilename);
+  except
+    on E: Exception do
+      DoLog(SErrCouldNotCreateFile, [aFileName, e.Message]);
   end;
+  PageDoc.Free;
 end;
 
-
-procedure THTMLWriter.WriteHTMLPages;
-var
-  i: Integer;
-  PageDoc: TXMLDocument;
-  Filename: String;
+procedure THTMLWriter.WriteDoc;
 
 begin
-  if Engine.Output <> '' then
-    Engine.Output := IncludeTrailingBackSlash(Engine.Output);
-  for i := 0 to PageInfos.Count - 1 do
-    with TPageInfo(PageInfos[i]) do
-    begin
-      PageDoc := CreateHTMLPage(Element, SubpageIndex);
-      try
-        Filename := Engine.Output + Allocator.GetFilename(Element, SubpageIndex);
-        try
-          CreatePath(Filename);
-          //writeln('Element: ',Element.PathName, ' FileName: ', Filename);
-          WriteHTMLFile(PageDoc, Filename);
-        except
-          on E: Exception do
-            DoLog(SErrCouldNotCreateFile, [FileName, e.Message]);
-        end;
-      finally
-        PageDoc.Free;
-      end;
-    end;
+  Inherited;
   CreateCSSFile;
   CreatePlusImage;
   CreateMinusImage;
@@ -912,60 +445,6 @@ end;
 }
 
 
-{ Used for:
-  - <link> elements in descriptions
-  - "see also" entries
-  - AppendHyperlink (for unresolved parse tree element links)
-}
-
-function THTMLWriter.ResolveLinkIDInUnit(const Name,AUnitName: String): DOMString;
-
-begin
-  Result:=ResolveLinkID(Name);
-  If (Result='') and (AUnitName<>'') and (length(Name)>0) and (Name[1]<>'#') then
-     Result:=ResolveLinkID(AUnitName+'.'+Name);
-end;
-
-function THTMLWriter.ResolveLinkID(const Name: String; Level : Integer = 0): DOMString;
-
-var
-  res,s: String;
-
-begin
-  res:=Engine.ResolveLink(Module,Name, True);
-  // engine can return backslashes on Windows
-  if Length(res) > 0 then
-   begin
-     s:=Copy(Res, 1, Length(CurDirectory) + 1);
-    if (S= CurDirectory + '/') or (s= CurDirectory + '\') then
-      Res := Copy(Res, Length(CurDirectory) + 2, Length(Res))
-    else if not IsLinkAbsolute(Res) then
-      Res := BaseDirectory + Res;
-   end;
-  Result:=UTF8Decode(Res);
-end;
-
-function THTMLWriter.ResolveLinkWithinPackage(AElement: TPasElement;
-  ASubpageIndex: Integer): String;
-var
-  ParentEl: TPasElement;
-  s : String;
-begin
-  ParentEl := AElement;
-  while Assigned(ParentEl) and not (ParentEl.ClassType = TPasPackage) do
-    ParentEl := ParentEl.Parent;
-  if Assigned(ParentEl) and (TPasPackage(ParentEl) = Engine.Package) then
-  begin
-    Result := Allocator.GetFilename(AElement, ASubpageIndex);
-    // engine/allocator can return backslashes on Windows
-    s:=Copy(Result, 1, Length(CurDirectory) + 1);
-    if (S= CurDirectory + '/') or (s= CurDirectory + '\') then
-      Result := Copy(Result, Length(CurDirectory) + 2, Length(Result))
-    else
-      Result := BaseDirectory + Result;
-  end else
-    SetLength(Result, 0);
-end;
 
 function THTMLWriter.CreateEl(Parent: TDOMNode;
   const AName: DOMString): THTMLElement;
@@ -2099,6 +1578,11 @@ begin
     end;
 end;
 
+function THTMLWriter.CreateAllocator: TFileAllocator;
+begin
+   Result:=TLongNameFileAllocator.Create('.html');
+end;
+
 procedure THTMLWriter.AppendMenuBar(ASubpageIndex: Integer);
 
 var
@@ -2406,7 +1890,7 @@ begin
     end;
 end;
 
-Procedure THTMLWriter.CreateTopicPageBody(AElement : TTopicElement);
+procedure THTMLWriter.CreateTopicPageBody(AElement: TTopicElement);
 
 var
   DocNode: TDocNode;
@@ -3122,22 +2606,6 @@ begin
 end;
 
 
-function PropertyFilter(AMember: TPasElement): Boolean;
-begin
-  Result := (AMember.ClassType = TPasProperty) and
-    (Copy(AMember.Name, 1, 2) <> 'On');
-end;
-
-function MethodFilter(AMember: TPasElement): Boolean;
-begin
-  Result := AMember.InheritsFrom(TPasProcedureBase);
-end;
-
-function EventFilter(AMember: TPasElement): Boolean;
-begin
-  Result := (AMember.ClassType = TPasProperty) and
-    (Copy(AMember.Name, 1, 2) = 'On');
-end;
 
 procedure THTMLWriter.CreateMemberDeclarations(AParent : TPasElement; Members : TFPList; TableEl : TDOmelement; AddEnd : Boolean);
 
@@ -3288,8 +2756,6 @@ end;
 
 procedure THTMLWriter.CreateClassPageBody(AClass: TPasClassType;
   ASubpageIndex: Integer);
-type
-  TMemberFilter = function(AMember: TPasElement): Boolean;
 var
   ParaEl: TDOMElement;
 
@@ -3869,11 +3335,6 @@ begin
     Result:=False;
 end;
 
-procedure THTMLWriter.WriteDoc;
-begin
-   DoLog(SWritingPages, [PageCount]);
-   WriteHTMLPages;
-end;
 
 class procedure THTMLWriter.Usage(List: TStrings);
 begin
@@ -3921,10 +3382,6 @@ end;
 
 // private methods
 
-function THTMLWriter.GetPageCount: Integer;
-begin
-  Result := PageInfos.Count;
-end;
 
 procedure THTMLWriter.SetOnTest(const AValue: TNotifyEvent);
 begin
@@ -3932,11 +3389,6 @@ begin
     FOnTest:=AValue;
 end;
 
-procedure THTMLWriter.CreateAllocator;
-begin
-  FAllocator:=TLongNameFileAllocator.Create('.html');
-end;
-
 
 initialization
   // Do not localize.

+ 1944 - 0
utils/fpdoc/dw_markdown.pp

@@ -0,0 +1,1944 @@
+{
+    FPDoc  -  Free Pascal Documentation Tool
+    Copyright (C) 2000 - 2005 by
+      Areca Systems GmbH / Sebastian Guenther, [email protected]
+
+    * HTML/XHTML output generator
+
+    See the file COPYING, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+}
+{$mode objfpc}
+{$H+}
+
+unit dw_markdown;
+{$WARN 5024 off : Parameter "$1" not used}
+interface
+
+uses Classes, dGlobals, PasTree, dWriter, dw_basemd, DOM;
+
+
+type
+
+  TMemberListOption = (mloAppendParent,mloAppendType,mloCheckVisibility);
+  TMemberListOptions = Set of TMemberListOption;
+
+  { TMarkdownWriter }
+
+  TMarkdownWriter = class(TBaseMarkdownWriter)
+  private
+    FBaseImageURL: String;
+    FImageFileList: TStrings;
+    FIndexColCount: Integer;
+    FAdditionalConfig : TStrings;
+    FFooterMarkDown : TStrings;
+    FHeaderMarkDown : TStrings;
+    FOnTest: TNotifyEvent;
+    FNavigation : TStrings;
+    function GetAdditionalConfig: TStrings;
+    function GetClassDeclaration(aEl: TPasClassType): String;
+    function GetClassDeclarationFirstLine(aEl: TPasClassType): String;
+    function GetDeclaration(aEl: TPasElement): String;
+    function GetFooterMarkDown: TStrings;
+    function GetHeaderMarkDown: TStrings;
+    function GetPageCount: Integer;
+    procedure SetOnTest(const AValue: TNotifyEvent);
+  protected
+    function GetFileBaseDir(aOutput: String): String; override;
+
+    // MkDocs
+    procedure WriteMkdocsYaml; virtual;
+    procedure CreateMkdocsYaml(mkDocs: TStrings); virtual;
+    procedure AddToNavigation(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);
+
+    // Some raw markdown functions
+
+    procedure AppendPageFooter; virtual;
+    procedure WriteMetadata; virtual;
+    procedure AppendPageHeader; virtual;
+    Procedure AppendText(Const S : String);
+    Procedure AppendTitle(Const S : String);
+    Procedure AppendTitle(Const Fmt : String; Const Args : Array of const);
+
+    // Description
+    Procedure AppendShortDescr(AContext : TPasElement; DocNode : TDocNode); virtual;
+    procedure AppendShortDescr(Element: TPasElement); virtual;
+    procedure AppendShortDescrCell(Element: TPasElement);  virtual;
+    procedure AppendDescr(AContext: TPasElement; DescrNode: TDOMElement; AutoInsertBlock: Boolean); virtual;
+    procedure AppendDescrSection(AContext: TPasElement; DescrNode: TDOMElement; const ATitle: String); virtual;
+    procedure AppendDescrSection(AContext: TPasElement; DescrNode: TDOMElement; const ATitle: DOMString); virtual;
+    Procedure AppendHyperlink(Element: TPasElement);
+    Function CreateHyperlink(Element: TPasElement) : string;
+
+    // Reusable routines
+    procedure CollectDeclarationTypes(aList: TStringList; aElement: TPasElement );
+    procedure CollectSeeAlsoNodes(aList: TStringList; aSeeAlso: TDomNode);
+    procedure AddModuleIdentifiers(AModule : TPasModule; L : TStrings);
+
+    procedure AppendSourceRef(AElement: TPasElement); virtual;
+    procedure AppendDeclaration(aEl: TPasElement; aKind: string; PrependVisibility: Boolean); virtual;
+    procedure FinishElementPage(AElement: TPasElement; SkipDescription: Boolean=false); virtual;
+    Procedure AppendSeeAlsoSection(AElement : TPasElement;DocNode : TDocNode); virtual;
+    Procedure AppendExampleSection(AElement : TPasElement;DocNode : TDocNode); virtual;
+    procedure AppendProcArgsSection(Element: TPasProcedureBase);  virtual;
+    procedure CreateMemberDeclarations(AParent: TPasElement; Members: TFPList; Options : TMemberListOptions); virtual;
+    Procedure CreateTopicLinks(Node : TDocNode; PasElement : TPasElement); virtual;
+    procedure CreateIndexPage(L : TStringList); virtual;
+    procedure CreateSimpleSubpage(aModule: TPasModule; const ATitle, aItem: String; AList: TFPList); virtual;
+
+    // Various kind of pages.
+    // Main entry
+    procedure CreatePageBody(AElement: TPasElement; ASubpageIndex: Integer); virtual;
+    // Package
+    procedure CreatePackagePageBody; virtual;
+    procedure CreatePackageIndex; virtual;
+    procedure CreatePackageClassHierarchy; virtual;
+    procedure CreateClassHierarchyPage(AddUnit : Boolean); virtual;
+    // Module
+    procedure CreateModuleIndexPage(AModule: TPasModule); virtual;
+    procedure CreateModuleMainPageBody(AModule: TPasModule); virtual;
+    procedure CreateModulePageBody(AModule: TPasModule; ASubpageIndex: Integer); virtual;
+    // Per simple type
+    procedure CreateResStringsPage(aModule: TPasModule); virtual;
+    Procedure CreateTopicPageBody(AElement : TTopicElement); virtual;
+    procedure CreateConstPageBody(AConst: TPasConst); virtual;
+    procedure CreateTypePageBody(AType: TPasType); virtual;
+    procedure CreateVarPageBody(AVar: TPasVariable); virtual;
+    procedure CreateProcPageBody(AProc: TPasProcedureBase); virtual;
+    // Class/Record
+    procedure CreateClassMainPage(aClass: TPasClassType); virtual;
+    procedure CreateClassPageBody(AClass: TPasClassType; ASubpageIndex: Integer); virtual;
+    procedure CreateClassMemberPageBody(AElement: TPasElement); virtual;
+    procedure CreateInheritanceSubpage(aClass: TPasClassType; aTitle : string; AFilter: TMemberFilter); virtual;
+    procedure CreateSortedSubpage(ACLass: TPasClassType; aTitle : string; AFilter: TMemberFilter ); virtual;
+  public
+    constructor Create(APackage: TPasPackage; AEngine: TFPDocEngine); override;
+    destructor Destroy; override;
+
+    // Single-page generation
+    procedure WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer); override;
+
+    // Start producing html complete package documentation
+    function  ModuleForElement(AnElement:TPasElement):TPasModule;
+
+    Function InterPretOption(Const Cmd,Arg : String) : boolean; override;
+    Procedure WriteDoc; override;
+    Class Function FileNameExtension : String; override;
+    class procedure Usage(List: TStrings); override;
+    Class procedure SplitImport(var AFilename, ALinkPrefix: String); override;
+
+    property OnTest: TNotifyEvent read FOnTest write SetOnTest;
+    Property IndexColCount : Integer Read FIndexColCount write FIndexColCount;
+    Property BaseImageURL : String Read FBaseImageURL Write FBaseImageURL;
+    Property HeaderMarkDown : TStrings Read GetHeaderMarkDown;
+    Property FooterMarkDown : TStrings Read GetFooterMarkDown;
+    property AdditionalConfig : TStrings Read GetAdditionalConfig;
+  end;
+
+implementation
+
+uses SysUtils, fpdocclasstree;
+
+
+Function FixHTMLpath(S : String) : STring;
+
+begin
+  Result:=StringReplace(S,'\','/',[rfReplaceAll]);
+end;
+
+constructor TMarkdownWriter.Create(APackage: TPasPackage; AEngine: TFPDocEngine);
+
+begin
+  inherited Create(APackage, AEngine);
+  IndexColCount:=3;
+  FImageFileList := TStringList.Create;
+  FNavigation:=TStringList.Create;
+end;
+
+destructor TMarkdownWriter.Destroy;
+
+begin
+  FreeAndNil(FImageFileList);
+  FreeAndNil(FAdditionalConfig);
+  FreeAndNil(FFooterMarkDown);
+  FreeAndNil(FHeaderMarkDown);
+  FreeAndNil(FNavigation);
+  inherited Destroy;
+end;
+
+procedure TMarkdownWriter.AddToNavigation(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);
+
+  Procedure AddToNav(aLevel : Integer; aName,aFile : String);
+  begin
+    if aName<>'' then
+      aName:=''''+aName+''':';
+    if aFile<>'' then
+      aFile:=' '''+aFile+'''';
+    FNavigation.Add(StringOfChar(' ',aLevel*4)+'- '+aName+aFile);
+  end;
+
+begin
+  if aElement is TPasPackage then
+    begin
+      case aSubPageIndex of
+        IdentifierIndex:
+          begin
+          AddToNav(1,SDocPackageLinkTitle,'');
+          AddToNav(2,SDocOverview,aFileName);
+          end;
+        IndexSubIndex :  AddToNav(2,SDocIdentifierIndex,aFileName);
+        ClassHierarchySubIndex :  AddToNav(2,SDocPackageClassHierarchy,aFileName);
+      end
+    end
+  else if (aElement is TPasModule) then
+    begin
+      case aSubPageIndex of
+        IdentifierIndex :
+          begin
+          AddToNav(1,Format(StringReplace(SDocUnitMenuTitle,'''','',[rfReplaceALl]),[aElement.Name]),'');
+          AddToNav(2,SDocOverview,aFileName);
+          end;
+        ResstrSubindex:  AddToNav(2,SDocResStrings,aFileName);
+        ConstsSubindex:  AddToNav(2,SDocConstants,aFileName);
+        TypesSubindex: AddToNav(2,SDocTypes,aFileName);
+        ClassesSubindex: AddToNav(2,SDocClasses,aFileName);
+        ProcsSubindex: AddToNav(2,SDocProceduresAndFunctions,aFileName);
+        VarsSubindex: AddToNav(2,SDocVariables,aFileName);
+      end;
+    end
+end;
+
+procedure TMarkdownWriter.WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);
+
+begin
+  if MarkDownEngine=meMkDocs then
+    AddToNavigation(aFileName,aElement,aSubPageIndex);
+  ClearMarkDown;
+  WriteMetaData;
+  AppendPageHeader;
+  CreatePageBody(AElement, ASubpageIndex);
+  AppendPageFooter;
+  SaveToFile(GetFileBaseDir(Engine.Output)+aFileName);
+end;
+
+procedure TMarkdownWriter.WriteMetadata;
+
+begin
+
+end;
+
+procedure TMarkdownWriter.AppendPageHeader;
+
+Var
+  S : String;
+begin
+  if Assigned(FHeaderMarkDown) then
+    begin
+    EnsureEmptyLine;
+    For S in FHeaderMarkDown do
+      AppendToLine(S,False)
+    end;
+end;
+
+procedure TMarkdownWriter.WriteMkdocsYaml;
+
+Var
+  mkDocs : TStrings;
+
+begin
+  mkDocs:=TStringList.Create;
+  try
+    CreatemkDocsYaml(mkDocs);
+    mkDocs.SaveToFile(Engine.Output+PathDelim+'mkdocs.yml');
+  finally
+    Mkdocs.Free;
+  end;
+end;
+
+procedure TMarkdownWriter.CreateMkdocsYaml(mkDocs: TStrings);
+
+begin
+  With MKDocs do
+    begin
+    add('site_name: '+SDocPackageTitle,[Package.Name]);
+    add('');
+    add('docs_dir: docs');
+    add('');
+    add('site_dir: site');
+    add('');
+    add('markdown_extensions:');
+    add('  - def_list');
+    add('  - codehilite');
+    add('  - admonition');
+    add('');
+    add('theme: '+Theme);
+    If Assigned(FAdditionalConfig) then
+      begin
+      add('');
+      AddStrings(FAdditionalConfig);
+      end;
+    add('');
+    add('nav:');
+    AddStrings(FNavigation);
+    end;
+end;
+
+
+
+procedure TMarkdownWriter.WriteDoc;
+
+begin
+  Inherited;
+  If MarkDownEngine=memkDocs then
+    WriteMkdocsYaml;
+end;
+
+
+function TMarkdownWriter.GetFooterMarkDown: TStrings;
+begin
+  If FFooterMarkDown=Nil then
+    FFooterMarkDown:=TStringList.Create;
+  Result:=FFooterMarkDown;
+end;
+
+function TMarkdownWriter.GetHeaderMarkDown: TStrings;
+begin
+  If FHeaderMarkDown=Nil then
+    FHeaderMarkDown:=TStringList.Create;
+  Result:=FHeaderMarkDown;
+end;
+
+
+
+function  TMarkdownWriter.ModuleForElement(AnElement:TPasElement):TPasModule;
+
+begin
+  result:=TPasModule(AnElement);
+  while assigned(result) and not (result is TPasModule) do 
+        result:=TPasModule(result.parent);
+  if not (result is TPasModule) then
+   result:=nil;
+end;
+
+procedure TMarkdownWriter.AppendShortDescr(AContext: TPasElement; DocNode: TDocNode) ;
+
+Var
+  N : TDocNode;
+
+begin
+  if Not Assigned(DocNode) then
+   exit;
+  N:=Nil;
+  If (DocNode.Link<>'') then
+    N:=Engine.FindLinkedNode(DocNode);
+  If (N=Nil) then
+    N:=DocNode;
+  If Assigned(N.ShortDescr) then
+    if not ConvertShort(AContext,N.ShortDescr) then
+      Warning(AContext, SErrInvalidShortDescr)
+end;
+
+procedure TMarkdownWriter.AppendShortDescr(Element: TPasElement);
+
+begin
+  AppendShortDescr(Element,Engine.FindDocNode(Element));
+end;
+
+procedure TMarkdownWriter.AppendShortDescrCell(Element: TPasElement);
+
+begin
+  if Not Assigned(Engine.FindShortDescr(Element)) then
+    exit;
+  DescrBeginTableCell;
+  AppendShortDescr(Element);
+  DescrEndTableCell;
+end;
+
+procedure TMarkdownWriter.AppendDescr(AContext: TPasElement; DescrNode: TDOMElement; AutoInsertBlock: Boolean);
+begin
+  if Not Assigned(DescrNode) then
+    exit;
+  EnsureEmptyLine;
+  ConvertDescr(AContext, DescrNode, AutoInsertBlock);
+end;
+
+procedure TMarkdownWriter.AppendDescrSection(AContext: TPasElement;  DescrNode: TDOMElement; const ATitle: String);
+begin
+  if IsDescrNodeEmpty(DescrNode) then
+    exit;
+  If (ATitle<>'') then // Can be empty for topic.
+    AppendHeader(2,ATitle);
+  AppendDescr(AContext, DescrNode, True);
+end;
+
+procedure TMarkdownWriter.AppendDescrSection(AContext: TPasElement; DescrNode: TDOMElement; const ATitle: DOMString);
+begin
+  AppendDescrSection(aContext,DescrNode,UTF8Encode(aTitle));
+end;
+
+procedure TMarkdownWriter.AppendHyperlink(Element: TPasElement);
+
+begin
+  if Not Assigned(Element) then
+    begin
+    AppendText('<NIL>');
+    exit;
+    end;
+  AppendToLine(CreateHyperLink(Element),False)
+end;
+
+function TMarkdownWriter.CreateHyperlink(Element: TPasElement): string;
+
+var
+  s: DOMString;
+  UnitList: TFPList;
+  i: Integer;
+  ThisPackage: TLinkNode;
+begin
+  if Not Assigned(Element) then
+    Exit('\<NIL\>');
+  if Not Element.InheritsFrom(TPasUnresolvedTypeRef) then
+    begin
+    if Element is TPasEnumValue then
+      s := ResolveLinkID(Element.Parent.PathName)
+    else
+      s := ResolveLinkID(Element.PathName);
+    end
+  else
+    begin
+    s := ResolveLinkID(Element.Name);
+    if Length(s) = 0 then
+      begin
+      { Try all packages }
+      ThisPackage := Engine.RootLinkNode.FirstChild;
+      while Assigned(ThisPackage) do
+        begin
+        s := ResolveLinkID(ThisPackage.Name + '.' + Element.Name);
+        if Length(s) > 0 then
+          break;
+        ThisPackage := ThisPackage.NextSibling;
+        end;
+      if Length(s) = 0 then
+        begin
+        { Okay, then we have to try all imported units of the current module }
+        UnitList := Module.InterfaceSection.UsesList;
+        for i := UnitList.Count - 1 downto 0 do
+          begin
+          { Try all packages }
+          ThisPackage := Engine.RootLinkNode.FirstChild;
+          while Assigned(ThisPackage) do
+            begin
+            s := ResolveLinkID(ThisPackage.Name + '.' +
+              TPasType(UnitList[i]).Name + '.' + Element.Name);
+            if Length(s) > 0 then
+              break;
+            ThisPackage := ThisPackage.NextSibling;
+            end;
+          if length(s)=0 then
+            s := ResolveLinkID('#rtl.System.' + Element.Name);
+          if Length(s) > 0 then
+            break;
+          end;
+        end;
+      end;
+    end;
+
+  if Length(s) > 0 then
+    Result:=CreateLink(Element.Name,UTF8Encode(S))
+  else
+    Result:=Element.Name;
+end;
+
+procedure TMarkdownWriter.AppendProcArgsSection(Element: TPasProcedureBase);
+
+var
+  HasFullDescr, IsFirstType: Boolean;
+  ResultEl: TPasResultElement;
+  DocNode: TDocNode;
+  aList : TStringList;
+
+  Procedure CollectVariant(AProc: TPasProcedure);
+
+  var
+    i: Integer;
+    Arg: TPasArgument;
+    aType : TPasProcedureType;
+
+  begin
+    aType:=aProc.ProcType;
+    for I:=0 to aType.Args.Count-1 do
+      begin
+      Arg:=TPasArgument(aType.Args[i]);
+      if IsDescrNodeEmpty(Engine.FindShortDescr(Arg)) then
+        Continue;
+      aList.AddObject(Arg.Name,Arg);
+      end;
+    if (aType is TPasFunctionType) and (ResultEl=Nil) then
+      ResultEl:=TPasFunctionType(aType).ResultEl;
+  end;
+
+  Procedure WriteType(aProc : TPasProcedure; aName: string);
+  var
+    i: Integer;
+    Arg: TPasArgument;
+    aType : TPasProcedureType;
+
+  begin
+    aType:=aProc.ProcType;
+     for I:=0 to aType.Args.Count-1 do
+      begin
+      Arg:=TPasArgument(aType.Args[i]);
+      if SameText(Arg.Name,aName) then
+        begin
+        if not IsFirstType then
+          AppendText(', ');
+        AppendHyperlink(Arg.ArgType);
+        end;
+      if IsDescrNodeEmpty(Engine.FindShortDescr(Arg)) then
+        Continue;
+      aList.AddObject(Arg.Name,Arg);
+      end;
+
+  end;
+
+  Procedure WriteTypes(aName: string);
+
+  Var
+    I : Integer;
+
+  begin
+    IsFirstType:=True;
+    if Element.ClassType <> TPasOverloadedProc then
+      WriteType(TPasProcedure(Element),aName)
+    else for i := 0 to TPasOverloadedProc(Element).Overloads.Count - 1 do
+      WriteType(TPasProcedure(TPasOverloadedProc(Element).Overloads[i]),AName);
+  end;
+
+Var
+  I: Integer;
+
+begin
+  ResultEl:=Nil;
+  aList:=TStringList.Create;
+  try
+    AList.Duplicates:=DupIgnore;
+    if Element.ClassType <> TPasOverloadedProc then
+      CollectVariant(TPasProcedure(Element))
+    else for i := 0 to TPasOverloadedProc(Element).Overloads.Count - 1 do
+      CollectVariant(TPasProcedure(TPasOverloadedProc(Element).Overloads[i]));
+    If AList.Count<>0 then
+      begin
+      AppendHeader(2,SDocArguments);
+      AppendTableHeader([SDocName,SDocTypes,SDocDescription]);
+      for I:=0 to aList.Count-1 do
+        begin
+        DescrBeginTableRow;
+        DescrBeginTableCell;
+        AppendText(aList[i]);
+        DescrEndTableCell;
+        DescrBeginTableCell;
+        WriteTypes(aList[i]);
+        DescrEndTableCell;
+        AppendShortDescrCell(TPasArgument(aList.Objects[i]));
+        DescrEndTableRow;
+        end;
+      DescrEndTable;
+      end;
+  finally
+    aList.Free;
+  end;
+  if Not Assigned(ResultEl) then
+    Exit;
+  DocNode := Engine.FindDocNode(ResultEl);
+  HasFullDescr := Assigned(DocNode) and not IsDescrNodeEmpty(DocNode.Descr);
+  if HasFullDescr or
+    (Assigned(DocNode) and not IsDescrNodeEmpty(DocNode.ShortDescr)) then
+    begin
+    AppendHeader(2, SDocFunctionResult);
+    if HasFullDescr then
+      AppendDescr(ResultEl,DocNode.Descr, True)
+    else
+      AppendDescr(ResultEl,DocNode.ShortDescr, False);
+    end;
+end;
+
+
+
+procedure TMarkdownWriter.AppendSourceRef(AElement: TPasElement);
+begin
+  EnsureEmptyLine;
+  AppendToLine(Format(SDocSourcePosition,
+    [ExtractFileName(AElement.SourceFilename), AElement.SourceLinenumber]));
+  EnsureEmptyLine;
+end;
+
+
+procedure TMarkdownWriter.CollectSeeAlsoNodes(aList : TStringList; aSeeAlso: TDomNode);
+
+Var
+  Node : TDOMNode;
+  L : String;
+  El : TDOMElement;
+
+begin
+  Node:=aSeeAlso.FirstChild;
+  While Assigned(Node) do
+    begin
+    if (Node.NodeType=ELEMENT_NODE) and (Node.NodeName='link') then
+      begin
+      El:=TDOMElement(Node);
+      l:=UTF8encode(El['id']);
+      aList.AddObject(L,El);
+      end;
+    Node:=Node.NextSibling;
+    end;
+end;
+
+procedure TMarkdownWriter.CollectDeclarationTypes(aList : TStringList; aElement : TPasElement);
+
+  Procedure MaybeAdd(aType : TPasType);
+
+  Var
+    N : TDocNode;
+  begin
+     if aType is TPasPointerType then
+       aType:=TPasPointerType(aType).DestType;
+     if (aType=Nil) or (aType.Name='') then
+       exit;
+     N:=Engine.FindDocNode(aType);
+     if N<>Nil then
+       aList.AddObject(aType.Name,aType);
+  end;
+
+Var
+  I : integer;
+
+begin
+  if aElement is TPasVariable then
+    MaybeAdd(TPasVariable(aElement).VarType)
+  else if aElement is TPasMembersType then
+    begin
+    for I:=0 to TPasMembersType(aElement).Members.Count-1 do
+      if TObject(TPasMembersType(aElement).Members[i]) is TPasVariable then
+        MaybeAdd(TPasVariable(TPasMembersType(aElement).Members[i]).VarType);
+    end;
+end;
+
+procedure TMarkdownWriter.AppendSeeAlsoSection(AElement: TPasElement; DocNode: TDocNode) ;
+
+  Procedure AppendSeeALso(aID : String; El: TDomElement);
+  Var
+     S,N : DOMString;
+     doBold : Boolean;
+  begin
+    s:= ResolveLinkID(aID);
+    doBold:=Length(S)=0;
+    if DoBold then
+      begin
+      if assigned(module) then
+        s:=UTF8Decode(module.name)
+      else
+        s:='?';
+      if aID='' then aID:='<empty>';
+      if Assigned(AElement) then
+        N:=UTF8Decode(AElement.Name)
+      else
+        N:='?';
+      DoLog(SErrUnknownLinkID, [s,N,aID]);
+      end ;
+     if doBold then
+       DescrBeginBold
+     else
+       DescrBeginURL(S);
+     if Not IsDescrNodeEmpty(El) then
+       ConvertBaseShortList(AElement, El, True)
+     else
+       AppendText(aID);
+     if doBold then
+       DescrEndBold
+     else
+       DescrEndURL();
+  end;
+
+  Procedure AppendTypeRef(aName : String; El: TPasType);
+
+  begin
+    AppendHyperLink(El);
+  end;
+
+var
+  I : Integer;
+  aList : TStringList;
+  DescrEl : TDomElement;
+
+begin
+  if Not (Assigned(DocNode) and Assigned(DocNode.SeeAlso)) then
+    Exit;
+  AList:=TStringList.Create;
+  Try
+    aList.Duplicates:=dupIgnore;
+    // AList will have a TDomElement (seealso) or a TPasType element as object
+    CollectSeeAlsoNodes(aList,DocNode.SeeAlso);
+    CollectDeclarationTypes(aList,aElement);
+    if aList.Count = 0 then
+      exit;
+    aList.Sort;
+    AppendHeader(2,SDocSeeAlso);
+    AppendTableHeader([SDocName,SDocDescription]);
+    For I:=0 to aList.Count-1 do
+      begin
+      DescrEl:=Nil;
+      DescrBeginTableRow;
+      DescrBeginTableCell;
+      if aList.Objects[I] is TDomElement then
+        AppendSeeAlso(aList[i],TDomElement(aList.Objects[i]))
+      else if aList.Objects[i] is TPasType then
+        AppendTypeRef(aList[i],TPasType(aList.Objects[i]));
+
+      DescrEndTableCell;
+      DescrBeginTableCell;
+      DescrEl:=Engine.FindShortDescr(ModuleForElement(AElement),UTF8Encode(aList[i]));
+      if Assigned(DescrEl) then
+        ConvertShort(AElement, DescrEl)
+      else
+        AppendToLine(' ',False);
+      DescrEndTableCell;
+      DescrEndTableRow;
+      end;
+    DescrEndTable;
+  Finally
+    aList.Free;
+  end;
+end;
+
+procedure TMarkdownWriter.AppendExampleSection ( AElement: TPasElement;
+  DocNode: TDocNode ) ;
+
+var
+  Node: TDOMNode;
+  fn,s: String;
+  f: Text;
+
+begin
+  if not (Assigned(DocNode) and Assigned(DocNode.FirstExample)) then
+    Exit;
+  Node := DocNode.FirstExample;
+  while Assigned(Node) do
+    begin
+    if (Node.NodeType = ELEMENT_NODE) and (Node.NodeName = 'example') then
+      begin
+      fn:=Engine.GetExampleFilename(TDOMElement(Node));
+      If (fn<>'') then
+        begin
+        AppendHeader(2, SDocExample);
+        try
+          Assign(f, FN);
+          Reset(f);
+          try
+            DescrBeginCode(False, UTF8Encode(TDOMElement(Node)['highlighter']));
+            while not EOF(f) do
+              begin
+              ReadLn(f, s);
+              DescrWriteCodeLine(s);
+              end;
+          finally
+            Close(f);
+            DescrEndCode;
+          end;
+        except
+          on e: Exception do
+            begin
+            e.Message := '[example] ' + e.Message;
+            raise;
+            end;
+        end;
+        end;
+      end;
+    Node := Node.NextSibling;
+    end;
+end;
+
+procedure TMarkdownWriter.AppendPageFooter;
+
+Var
+  S : String;
+begin
+  if Assigned(FFooterMarkDown) then
+    begin
+    EnsureEmptyLine;
+    For S in FFooterMarkDown do
+      AppendToLine(S,False)
+    end;
+end;
+
+procedure TMarkdownWriter.FinishElementPage(AElement: TPasElement; SkipDescription : Boolean = false);
+
+var
+  DocNode: TDocNode;
+
+begin
+  DocNode := Engine.FindDocNode(AElement);
+  If Not Assigned(DocNode) then
+    exit;
+  // Description
+  if Assigned(DocNode.Descr) and not SkipDescription then
+    AppendDescrSection(AElement, DocNode.Descr, UTF8Encode(SDocDescription));
+
+  // Append "Errors" section
+  if Assigned(DocNode.ErrorsDoc) then
+    AppendDescrSection(AElement, DocNode.ErrorsDoc, UTF8Encode(SDocErrors));
+
+  // Append Version info
+  if Assigned(DocNode.Version) then
+    AppendDescrSection(AElement, DocNode.Version, UTF8Encode(SDocVersion));
+
+  // Append "See also" section
+  AppendSeeAlsoSection(AElement,DocNode);
+
+  // Append examples, if present
+  AppendExampleSection(AElement,DocNode);
+  // Append notes, if present
+  ConvertNotes(AElement,DocNode.Notes);
+end;
+
+procedure TMarkdownWriter.CreateTopicPageBody(AElement: TTopicElement);
+
+var
+  DocNode: TDocNode;
+begin
+  DocNode:=AElement.TopicNode;
+  if not Assigned(DocNode) then  // should always be true, but we're being careful.
+    exit;
+  AppendShortDescr(AElement, DocNode);
+  if Assigned(DocNode.Descr) then
+     AppendDescrSection(AElement, DocNode.Descr, '');
+  AppendSeeAlsoSection(AElement,DocNode);
+  CreateTopicLinks(DocNode,AElement);
+  AppendExampleSection(AElement,DocNode);
+  ConvertNotes(AElement,DocNode.Notes);
+end;
+
+procedure TMarkdownWriter.CreateClassHierarchyPage(AddUnit : Boolean);
+type
+  TypeEN = (NPackage, NModule, NName);
+
+
+  Procedure AddClassList;
+  begin
+    DescrBeginUnorderedList;
+  end;
+
+  Procedure EndClassList;
+  begin
+    DescrEndUnorderedList;
+  end;
+
+
+  function ExtractName(APathName: String; Tp: TypeEN):String;
+  var
+  l:TStringList;
+  begin
+    Result:= Trim(APathName);
+    if Result = '' then exit;
+    l:=TStringList.Create;
+    try
+      l.AddDelimitedText(Result, '.', True);
+      if l.Count=3 then
+        Result:= l.Strings[Integer(Tp)]
+      else
+        Result:='';
+    finally
+      l.free;
+    end;
+  end;
+
+  Procedure AppendClass(EN : TPasElementNode);
+
+  Var
+    PE,PM : TPasElement;
+    I : Integer;
+
+  begin
+    if not Assigned(EN) then exit;
+    PE:=EN.Element;
+    DescrBeginListItem;
+    AppendHyperLink(PE);
+    PM:=ModuleForElement(PE);
+    if (PM<>Nil) then
+      begin
+      AppendText(' (');
+      AppendHyperLink(PM);
+      AppendText(')');
+      end
+    else
+      AppendText(EN.Element.Name);
+    if EN.ChildCount>0 then
+      begin
+      AddClassList;
+      For I:=0 to EN.ChildCount-1 do
+        AppendClass(EN.Children[i] as TPasElementNode);
+      EndClassList;
+      end;
+    DescrEndListItem;
+  end;
+
+begin
+  AddClassList;
+  AppendClass(TreeClass.RootNode);
+  EndClassList;
+end;
+
+procedure TMarkdownWriter.CreatePackageClassHierarchy;
+
+
+Var
+  S : String;
+
+begin
+  S:=Package.Name;
+  If Length(S)>0 then
+    Delete(S,1,1);
+  AppendTitle(SDocPackageClassHierarchy, [S]);
+  CreateClassHierarchyPage(True);
+end;
+
+procedure TMarkdownWriter.CreatePageBody(AElement: TPasElement; ASubpageIndex: Integer);
+var
+  i: Integer;
+  Element: TPasElement;
+begin
+  CurDirectory := Allocator.GetFilename(AElement, ASubpageIndex);
+  i := Length(CurDirectory);
+  while (i > 0) and not (CurDirectory[i] in AllowDirectorySeparators) do
+    Dec(i);
+  CurDirectory := Copy(CurDirectory, 1, i);
+  BaseDirectory := Allocator.GetRelativePathToTop(AElement);
+  if AElement.ClassType = TPasPackage then
+    begin
+    Module:=Nil;
+    If (ASubPageIndex=0) then
+      CreatePackagePageBody
+    else if ASubPageIndex=IndexSubIndex then
+      CreatePackageIndex  
+    else if ASubPageIndex=ClassHierarchySubIndex then
+      CreatePackageClassHierarchy
+    end
+  else
+    begin
+    Element := AElement;
+    while (Element<>Nil) and (not (Element.ClassType.inheritsfrom(TPasModule))) do
+      Element := Element.Parent;
+    Module := TPasModule(Element);
+
+    if AElement.ClassType.inheritsfrom(TPasModule) then
+      CreateModulePageBody(TPasModule(AElement), ASubpageIndex)
+    else if AElement.Parent.InheritsFrom(TPasClassType) then
+      CreateClassMemberPageBody(AElement)
+    else if AElement.ClassType = TPasConst then
+      CreateConstPageBody(TPasConst(AElement))
+    else if AElement.InheritsFrom(TPasClassType) then
+      CreateClassPageBody(TPasClassType(AElement), ASubpageIndex)
+    else if AElement.InheritsFrom(TPasType) then
+      CreateTypePageBody(TPasType(AElement))
+    else if AElement.ClassType = TPasVariable then
+      CreateVarPageBody(TPasVariable(AElement))
+    else if AElement.InheritsFrom(TPasProcedureBase) then
+      CreateProcPageBody(TPasProcedureBase(AElement))
+    else if AElement.ClassType = TTopicELement then
+      CreateTopicPageBody(TTopicElement(AElement))
+    else if AElement.ClassType = TPasProperty then
+      CreateClassMemberPageBody(TPasProperty(AElement))
+    else
+      writeln('Unknown classtype: ',AElement.classtype.classname);
+    end;
+end;
+
+procedure TMarkdownWriter.CreateIndexPage(L : TStringList);
+
+Var
+  Lists  : Array['A'..'Z'] of TStringList;
+  CL : TStringList;
+  E : TPasElement;
+  CCount,I,Rows,J,Index : Integer;
+  S : String;
+  C : Char;
+
+begin
+  For C:='A' to 'Z' do
+    Lists[C]:=Nil;
+  L.Sort;
+  Cl:=Nil;
+  // Divide over alphabet
+  For I:=0 to L.Count-1 do
+    begin
+    S:=L[i];
+    E:=TPasElement(L.Objects[i]);
+    If not (E is TPasUnresolvedTypeRef) then
+      begin
+      If (S<>'') then 
+        begin
+        C:=Upcase(S[1]);
+        If C='_' then
+          C:='A';
+        If (C in ['A'..'Z']) and (Lists[C]=Nil) then
+          begin
+          CL:=TStringList.Create;
+          Lists[C]:=CL;
+          end;
+        end;
+      if assigned(cl) then  
+        CL.AddObject(S,E);
+      end;  
+    end;  
+  Try  
+  // Create a quick jump table to all available letters.    
+  CCount:=0;
+  for C:='A' to 'Z' do
+    If (Lists[C]<>Nil) then
+      Inc(CCount);
+  DescrBeginTable(CCount,False);
+  DescrBeginTableHeadRow;
+  for C:='A' to 'Z' do
+    If (Lists[C]<>Nil) then
+      begin
+      DescrBeginTableCell;
+      AppendLink(C,'#'+LowerCase(C));
+      DescrendTableCell;
+      end;
+  DescrEndTableHeadRow;
+  // Now emit all identifiers.
+  For C:='A' to 'Z' do
+    begin
+    CL:=Lists[C];
+    If CL<>Nil then
+      begin
+      AppendHeader(3,C);
+      DescrBeginTable(IndexColCount,False);
+      DescrBeginTableHeadRow;
+      For I:=1 to IndexColCount do
+         begin
+         DescrBeginTableCell;
+         AppendToLine('&nbsp; ',False);
+         DescrEndTableCell;
+         end;
+      DescrEndTableHeadRow;
+      // Determine number of rows needed
+      Rows:=(CL.Count div IndexColCount);
+      If ((CL.Count Mod IndexColCount)<>0) then
+        Inc(Rows);
+      // Fill rows  
+      For I:=0 to Rows-1 do
+        begin
+        DescrBeginTableRow;
+        For J:=0 to IndexColCount-1 do
+          begin
+          DescrBeginTableCell;
+          Index:=(J*Rows)+I;
+          If (Index<CL.Count) then
+            begin
+            S:=CL[Index];
+            E:=TPasElement(CL.Objects[Index]);
+            AppendHyperlink(E);
+            end;
+          end;
+        DescrEndTableRow;
+        end;
+      end; // have List
+    end;  // For C:=
+  Finally
+    for C:='A' to 'Z' do
+      FreeAndNil(Lists[C]);
+  end;  
+end;
+
+
+procedure TMarkdownWriter.AddModuleIdentifiers(AModule : TPasModule; L : TStrings);
+
+begin
+  if assigned(AModule.InterfaceSection) Then
+   begin
+      AddElementsFromList(L,AModule.InterfaceSection.Consts);
+      AddElementsFromList(L,AModule.InterfaceSection.Types);
+      AddElementsFromList(L,AModule.InterfaceSection.Functions);
+      AddElementsFromList(L,AModule.InterfaceSection.Classes);
+      AddElementsFromList(L,AModule.InterfaceSection.Variables);
+      AddElementsFromList(L,AModule.InterfaceSection.ResStrings);
+   end;
+end;
+
+
+procedure TMarkdownWriter.CreatePackageIndex;
+
+Var
+  L : TStringList;
+  I : Integer;
+  M : TPasModule;
+  S : String;
+  
+begin
+  L:=TStringList.Create;
+  try
+    L.Capacity:=PageInfos.Count; // Too much, but that doesn't hurt.
+    For I:=0 to Package.Modules.Count-1 do
+      begin
+      M:=TPasModule(Package.Modules[i]);
+      L.AddObject(M.Name,M);
+      AddModuleIdentifiers(M,L);
+      end;
+    S:=Package.Name;
+    If Length(S)>0 then
+      Delete(S,1,1);
+    AppendTitle(SDocPackageIndex, [S]);
+    CreateIndexPage(L);
+  Finally
+    L.Free;
+  end;
+end;
+
+procedure TMarkdownWriter.CreatePackagePageBody;
+var
+  DocNode: TDocNode;
+  i: Integer;
+  ThisModule: TPasModule;
+  L : TStringList;
+
+begin
+  AppendTitle(SDocPackageTitle, [Copy(Package.Name, 2, 256)]);
+  AppendShortDescr(Package);
+
+  AppendHeader(2,SDocUnits);
+  L:=TStringList.Create;
+  Try
+    L.Sorted:=True;
+    // Sort modules.
+    For I:=0 to Package.Modules.Count-1 do
+      L.AddObject(TPasModule(Package.Modules[i]).Name,TPasModule(Package.Modules[i]));
+    AppendTableHeader([SDocUnits,SDocDescription]);
+    // Now create table.
+    for i:=0 to L.Count - 1 do
+      begin
+      ThisModule := TPasModule(L.Objects[i]);
+      DescrBeginTableRow;
+      DescrBeginTableCell;
+      AppendHyperlink(ThisModule);
+      DescrEndTableCell;
+      AppendShortDescrCell(ThisModule);
+      DescrEndTableRow;
+      end;
+    DescrEndTable;
+  Finally
+    L.Free;
+  end;
+
+  DocNode := Engine.FindDocNode(Package);
+  if Assigned(DocNode) then
+    begin
+    if Assigned(DocNode.Descr) then
+       AppendDescrSection(nil, DocNode.Descr, UTF8Decode(SDocDescription));
+    CreateTopicLinks(DocNode,Package);
+    end;
+end;
+
+procedure TMarkdownWriter.CreateTopicLinks ( Node: TDocNode; PasElement: TPasElement ) ;
+
+var
+  DocNode: TDocNode;
+  First : Boolean;
+  ThisTopic: TPasElement;
+
+begin
+  DocNode:=Node.FirstChild;
+  First:=True;
+  While Assigned(DocNode) do
+    begin
+    If DocNode.TopicNode then
+      begin
+      if first then
+        begin
+        First:=False;
+        AppendHeader(2,SDocRelatedTopics);
+        AppendTableHeader([SDocTopic,SDocDescription]);
+        end;
+      ThisTopic:=FindTopicElement(DocNode);
+      if Assigned(ThisTopic) then
+        begin
+        DescrBeginTableRow;
+        DescrBeginTableCell;
+        AppendHyperlink(ThisTopic);
+        DescrEndTableCell;
+        AppendShortDescrCell(ThisTopic);
+        DescrEndTableRow;
+        end;
+      end;
+    DocNode:=DocNode.NextSibling;
+    end;
+  if not First then
+    DescrEndTable;
+end;
+
+function TMarkdownWriter.GetFileBaseDir(aOutput: String): String;
+begin
+  Result:=inherited GetFileBaseDir(aOutput)+'docs'+pathdelim;
+end;
+
+procedure TMarkdownWriter.CreateModuleIndexPage(AModule: TPasModule);
+
+Var
+  L : TStringList;
+
+begin
+  L:=TStringList.Create;
+  try
+    AddModuleIdentifiers(AModule,L);
+    AppendTitle(SDocModuleIndex, [AModule.Name]);
+    CreateIndexPage(L);
+  Finally
+    L.Free;
+  end;  
+end;
+
+procedure TMarkdownWriter.CreateModuleMainPageBody(AModule: TPasModule);
+
+var
+  i: Integer;
+  UnitRef: TPasType;
+  DocNode: TDocNode;
+begin
+  AppendTitle(SDocUnitTitle, [AModule.Name]);
+  AppendShortDescr(AModule);
+
+  if AModule.InterfaceSection.UsesList.Count > 0 then
+    begin
+    AppendTableHeader(['Uses unit',SDocDescription]);
+    for i := 0 to AModule.InterfaceSection.UsesList.Count - 1 do
+      begin
+      UnitRef := TPasType(AModule.InterfaceSection.UsesList[i]);
+      DocNode := Engine.FindDocNode(UnitRef);
+      if Assigned(DocNode) and DocNode.IsSkipped then
+        continue;
+      DescrBeginTableRow;
+      DescrBeginTableCell;
+      AppendHyperlink(UnitRef);
+      DescrEndTableCell;
+      AppendShortDescrCell(UnitRef);
+      end;
+    DescrEndTable;
+    end;
+  DocNode := Engine.FindDocNode(AModule);
+  if Assigned(DocNode) then
+    begin
+    if Assigned(DocNode.Descr) then
+      AppendDescrSection(AModule, DocNode.Descr, UTF8Decode(SDocOverview));
+    ConvertNotes(AModule,DocNode.Notes);
+    CreateTopicLinks(DocNode,AModule);
+    end;
+end;
+
+
+procedure TMarkdownWriter.CreateSimpleSubpage(aModule : TPasModule; const ATitle,aItem: String; AList: TFPList);
+var
+  i: Integer;
+  Decl: TPasElement;
+  SortedList: TFPList;
+  DocNode: TDocNode;
+begin
+  AppendTitle(SDocUnitTitle + ': %s', [AModule.Name, aTitle]);
+  SortedList := TFPList.Create;
+  try
+    for i := 0 to AList.Count - 1 do
+      begin
+      Decl := TPasElement(AList[i]);
+      DocNode := Engine.FindDocNode(Decl);
+      if not (Assigned(DocNode) and DocNode.IsSkipped) then
+        SortedList.Add(Decl);
+      end;
+    SortedList.Sort(@SortPasElements);
+    AppendTableHeader([aItem,SDocDescription]);
+    for i := 0 to SortedList.Count - 1 do
+      begin
+      Decl:=TPasElement(SortedList[i]);
+      DescrBeginTableRow;
+      DescrBeginTableCell;
+      AppendHyperlink(Decl);
+      DescrEndTableCell;
+      AppendShortDescrCell(Decl);
+      DescrEndTableRow;
+      end;
+    DescrEndTable;
+  finally
+    SortedList.Free;
+  end;
+end;
+
+
+procedure TMarkdownWriter.CreateResStringsPage(aModule : TPasModule);
+
+var
+  i: Integer;
+  Decl: TPasResString;
+
+begin
+  AppendTitle(SDocUnitTitle + ': %s', [AModule.Name, SDocResStrings]);
+  If AModule.InterfaceSection.ResStrings.Count = 0 then
+    exit;
+  for i := 0 to AModule.InterfaceSection.ResStrings.Count - 1 do
+    begin
+    Decl := TPasResString(AModule.InterfaceSection.ResStrings[i]);
+    AppendToLine('<a name="%s"> %s',[LowerCase(Decl.Name),Decl.Name]);
+    Indent;
+    UTF8Decode(Decl.Expr.getDeclaration(true));
+    undent;
+    end;
+end;
+
+
+procedure TMarkdownWriter.CreateModulePageBody(AModule: TPasModule;
+  ASubpageIndex: Integer);
+
+begin
+  case ASubpageIndex of
+    0:
+      CreateModuleMainPageBody(aModule);
+    ResstrSubindex:
+      CreateResStringsPage(aModule);
+    ConstsSubindex:
+      CreateSimpleSubpage(aModule,SDocConstants, SDocConstant, AModule.InterfaceSection.Consts);
+    TypesSubindex:
+      CreateSimpleSubpage(aModule,SDocTypes, SDocType, AModule.InterfaceSection.Types);
+    ClassesSubindex:
+      CreateSimpleSubpage(aModule,SDocClasses, SDocClass, AModule.InterfaceSection.Classes);
+    ProcsSubindex:
+      CreateSimpleSubpage(aModule,SDocProceduresAndFunctions, SDocProcedureOrFunction, AModule.InterfaceSection.Functions);
+    VarsSubindex:
+      CreateSimpleSubpage(aModule,SDocVariables, SDocVariable, AModule.InterfaceSection.Variables);
+    IndexSubIndex: 
+      CreateModuleIndexPage(AModule);
+  end;
+end;
+
+procedure TMarkdownWriter.CreateConstPageBody(AConst: TPasConst);
+
+begin
+  AppendTitle(AConst.Name);
+  AppendShortDescr(AConst);
+
+  AppendHeader(2,SDocDeclaration);
+  AppendSourceRef(AConst);
+
+  DescrBeginCode(False,'Pascal');
+  EmitLine('const');
+  EmitLine(aConst.GetDeclaration(True));
+  DescrEndCode;
+
+  FinishElementPage(AConst);
+end;
+
+
+procedure TMarkdownWriter.CreateTypePageBody(AType: TPasType);
+begin
+  AppendTitle(AType.Name);
+  AppendShortDescr(AType);
+  AppendHeader(2,SDocDeclaration);
+  AppendSourceRef(AType);
+
+  DescrBeginCode(False,'Pascal');
+  EmitLine('Type');
+  EmitLine(aType.GetDeclaration(True));
+  DescrEndCode;
+
+  FinishElementPage(AType);
+end;
+
+
+
+
+procedure TMarkdownWriter.CreateMemberDeclarations(AParent: TPasElement; Members: TFPList; Options: TMemberListOptions);
+
+  function GetMemberType(aMember : TPasElement) : string;
+
+  begin
+    if aMember is TPasProcedure then
+      Result:=SDocMethod
+    else if aMember is TPasProperty then
+      Result:=SDocProperty
+    else if aMember is TPasConst then
+      Result:=SDocConstant
+    else if aMember is TPasType then
+      Result:=SDocType
+    else
+      Result:=SDocField;
+  end;
+
+var
+  Member: TPasElement;
+  MVisibility : TPasMemberVisibility;
+  i,aCount: Integer;
+  // isRecord,
+  isOverLoad : Boolean;
+
+begin
+  // isRecord:=AParent is TPasRecordType;
+  if Members.Count = 0 then
+    begin
+    AppendText(SDocNoneAVailable);
+    Exit;
+    end;
+  if mloAppendType in Options then
+    AppendTableHeader([SDocMember,SDocType,SDocVisibility,SDocDescription])
+  else
+    AppendTableHeader([SDocMember,SDocVisibility,SDocDescription]);
+  aCount:=0;
+  Members.Sort(@SortPasElements);
+  for i := 0 to Members.Count - 1 do
+    begin
+    Member := TPasElement(Members[i]);
+    MVisibility:=Member.Visibility;
+    if mloCheckVisibility in Options then
+      if not Engine.ShowElement(Member) then
+        Continue;
+    isOverLoad:=(Member is TPasOverloadedProc);
+    if isOverload then
+      Member:=TPasElement((Member as TPasOverloadedProc).Overloads[0]);
+    Inc(aCount);
+    DescrBeginTableRow;
+
+    DescrBeginTableCell;
+    AppendHyperlink(Member);
+    if mloAppendParent in options then
+      begin
+      AppendText('(');
+      AppendHyperLink(Member.Parent);
+      AppendText(')');
+      end;
+    DescrEndTableCell;
+    if mloAppendType in Options then
+      begin
+      DescrBeginTableCell;
+      AppendText(GetMemberType(Member));
+      DescrEndTableCell;
+      end;
+
+    DescrBeginTableCell;
+    AppendText(VisibilityNames[MVisibility]);
+    DescrEndTableCell;
+    AppendShortDescrCell(member);
+    DescrEndTableRow;
+    end;
+  DescrEndTable;
+  if ACount=0 then
+    AppendText(SDocNoneAVailable)
+end;
+
+procedure TMarkdownWriter.AppendTitle(const S: String);
+begin
+  AddMetaData('title',S);
+  AppendHeader(1,S);
+  EnsureEmptyLine;
+end;
+
+procedure TMarkdownWriter.AppendTitle(const Fmt: String;
+  const Args: array of const);
+
+begin
+  AppendTitle(Format(Fmt,Args));
+end;
+
+function TMarkdownWriter.GetClassDeclarationFirstLine(aEl: TPasClassType): String;
+
+Var
+  aLine : string;
+  I : Integer;
+
+begin
+  aLine:=aEL.Name;
+  if aEl.GenericTemplateTypes.Count>0 then
+    begin
+    aLine:='generic '+aLine+'<';
+    For I:=0 to  aEl.GenericTemplateTypes.Count-1 do
+      begin
+      if I>0 then
+        aLine:=aLine+', ';
+      aLine:=aLine+TPasGenericTemplateType(aEl.GenericTemplateTypes[i]).Name;
+      end;
+    aLine:=aLine+'>';
+    end;
+  aLine:=aLine+' = '+aEl.ElementTypeName;
+  if aEl.HelperForType<>Nil then
+    aLine:=aLine+' for '+aEl.HelperForType.Name;
+  if aEL.ExternalName<>'' then
+    aLine:=aLine+' external name '''+ael.ExternalName+'''';
+  if Assigned(aEL.AncestorType) then
+    begin
+    aLine:=aLine+' ('+ael.AncestorType.Name;
+    if Assigned(ael.Interfaces) and (aEl.Interfaces.Count>0) then
+      For I:=0 to aEl.Interfaces.Count-1 do
+        aLine:=aLine+', '+TPasElement(aEl.Interfaces[i]).Name;
+    aLine:=aLine+')';
+    end;
+  if Assigned(aEl.GUIDExpr) then
+    aLine:=aLine+' ['+aEl.GUIDExpr.GetDeclaration(True)+']';
+  Result:=aLine;
+end;
+
+function TMarkdownWriter.GetClassDeclaration(aEl: TPasClassType): String;
+
+Var
+  S,T : TStrings;
+  I,J : Integer;
+  LastVis : TPasMemberVisibility;
+  aMember : TPasElement;
+
+begin
+  T:=Nil;
+  lastVis:=VisDefault;
+  S:=TStringList.Create;
+  try
+    T:=TStringList.Create;
+    S.Add(GetClassDeclarationFirstLine(aEl));
+    for I:=0 to aEl.Members.Count-1 do
+      begin
+      aMember:=TPasElement(aEl.Members[i]);
+      if aMember.Visibility<>LastVis then
+        begin
+        LastVis:=aMember.Visibility;
+        S.Add(VisibilityNames[LastVis]);
+        end;
+      T.Text:=GetDeclaration(aMember);
+      for J:=0 to T.count-1 do
+        S.Add('  '+T[J]);
+      end;
+    if not aEl.IsShortDefinition then
+      S.Add('end');
+    Result:=S.Text;
+  finally
+    S.Free;
+    T.Free;
+  end;
+end;
+
+function TMarkdownWriter.GetDeclaration(aEl: TPasElement): String;
+
+Var
+  I : Integer;
+  Ovl : TPasOverloadedProc;
+  S : String;
+begin
+  if (aEl is TPasClassType) then
+    exit(GetClassDeclaration(TPasClassType(aEl))+';');
+  if Not (aEl is TPasOverloadedProc) then
+    Exit(aEl.GetDeclaration(True)+';');
+  ovl:=aEl as TPasOverloadedProc;
+  S:='';
+  for I:=0 to ovl.Overloads.Count-1 do
+    begin
+    if s<>'' then
+       S:=S+sLineBreak;
+    S:=S+TPasElement(Ovl.Overloads[i]).GetDeclaration(True)+';';
+    end;
+  Result:=S;
+end;
+
+procedure TMarkdownWriter.AppendDeclaration(aEl : TPasElement; aKind : string; PrependVisibility : Boolean);
+
+Var
+  S : String;
+begin
+  DescrBeginCode(False,'Pascal');
+  if PrependVisibility then
+    S:=VisibilityNames[aEL.Visibility]+' '
+  else
+    S:='';
+  S:=S+aKind;
+  EmitLine(S);
+  S:=GetDeclaration(aEl);
+  EmitCode(S,2);
+  DescrEndCode;
+end;
+
+
+procedure TMarkdownWriter.CreateClassMainPage(aClass : TPasClassType);
+
+  procedure AppendMemberListLink(AListSubpageIndex: Integer;  const AText: String);
+  begin
+    AppendToLine('\[',False);
+    AppendLink(aText,FixHtmlPath(ResolveLinkWithinPackage(AClass, AListSubpageIndex)));
+    AppendText(' (');
+    AppendLink(SDocByName,FixHtmlPath(ResolveLinkWithinPackage(AClass, AListSubpageIndex+1)));
+    AppendToLine(')\]',False);
+  end;
+
+
+
+var
+  i: Integer;
+  ThisInterface,
+  ThisClass: TPasClassType;
+  ThisTreeNode: TPasElementNode;
+  DocNode: TDocNode;
+
+begin
+  DocNode := Engine.FindDocNode(aClass);
+
+  //WriteLn('@ClassPageBody.CreateMainPage Class=', AClass.Name);
+  AppendTitle(AClass.Name);
+
+  AppendMemberListLink(PropertiesByInheritanceSubindex,SDocProperties);
+  AppendMemberListLink(MethodsByInheritanceSubindex, SDocMethods);
+  AppendMemberListLink(EventsByInheritanceSubindex, SDocEvents);
+
+  EnsureEmptyLine;
+
+  AppendShortDescr(AClass);
+  AppendHeader(2,SDocDeclaration);
+  AppendSourceRef(AClass);
+
+  AppendDeclaration(aClass,'Type',False);
+
+  // Description
+  if Assigned(DocNode) and Assigned(DocNode.Descr) then
+    AppendDescrSection(aClass, DocNode.Descr, SDocDescription);
+
+  AppendHeader(2,SDocMembers);
+
+  CreateMemberDeclarations(aClass, AClass.Members,[mloAppendType]);
+
+  AppendHeader(2,SDocInheritance);
+
+  EnsureEmptyLine;
+
+  AppendTableHeader([SDocClass,SDocDescription]);
+
+  ThisClass := AClass;
+  ThisTreeNode := Nil;
+
+  if AClass.ObjKind = okInterface then
+    ThisTreeNode := TreeInterface.GetPasElNode(AClass)
+  else
+    ThisTreeNode := TreeClass.GetPasElNode(AClass);
+  while True do
+    begin
+    DescrBeginTableRow;
+    DescrBeginTableCell;
+    // Show class item
+    if Assigned(ThisClass) Then
+      AppendHyperlink(ThisClass);
+    if Assigned(ThisClass) and (ThisClass.Interfaces.count>0) then
+      begin
+      AppendText('(');
+      for i:=0 to ThisClass.interfaces.count-1 do
+        begin
+        ThisInterface:=TPasClassType(ThisClass.Interfaces[i]);
+        if I>0 then
+          AppendText(', ');
+        AppendHyperlink( ThisInterface);
+        end;
+      AppendText(')');
+      end;
+    DescrEndTableCell;
+    AppendShortDescrCell(ThisClass);
+    DescrEndTableRow;
+
+    if Not Assigned(ThisTreeNode) then
+      Break
+    else if not Assigned(ThisTreeNode.ParentNode) then
+      begin
+      ThisClass := nil;
+      ThisTreeNode:= nil;
+      break;
+      end
+    else
+      begin
+      DescrBeginTableRow;
+      DescrBeginTableCell;
+      AppendText('|');
+      DescrEndTableCell;
+      DescrBeginTableCell;
+      DescrEndTableCell;
+      ThisClass := ThisTreeNode.ParentNode.Element;
+      ThisTreeNode := ThisTreeNode.ParentNode;
+      end;
+    end;
+  DescrEndTable;
+  FinishElementPage(AClass,True);
+end;
+
+
+procedure TMarkdownWriter.CreateInheritanceSubpage(aClass: TPasClassType; aTitle: string; AFilter: TMemberFilter);
+
+var
+  ThisClass: TPasClassType;
+  i,aCount: Integer;
+  Member: TPasElement;
+  aList : TFPList;
+
+begin
+  aTitle:=aClass.Name+' : '+aTitle+ ' '+SDocByInheritance;
+  AppendTitle(aTitle);
+  ThisClass := AClass;
+  aCount:=0;
+  aList:=TFPList.Create;
+  try
+    while assigned(ThisClass) do
+      begin
+      aList.Clear;
+      for i := 0 to ThisClass.Members.Count - 1 do
+        begin
+        Member := TPasElement(ThisClass.Members[i]);
+        if (Engine.ShowElement(Member) and AFilter(Member)) then
+          aList.Add(Member);
+        end;
+      aCount:=aCount+aList.Count;
+      if AList.Count>0 then
+        begin
+        AppendHeader(2,CreateHyperLink(ThisClass),False);
+        CreateMemberDeclarations(aClass, aList, []);
+        end;
+      if ThisClass.AncestorType is TPasClassType then
+        ThisClass := TPasClassType(ThisClass.AncestorType)
+      else
+        ThisClass:=Nil;
+      end;
+    if aCount=0 then
+      AppendText(SDocNoneAVailable);
+  finally
+    aList.Free;
+  end;
+end;
+
+procedure TMarkdownWriter.CreateSortedSubpage(ACLass: TPasClassType; aTitle: string; AFilter: TMemberFilter);
+
+var
+  List: TFPList;
+  ThisClass: TPasClassType;
+  i : Integer;
+  Member: TPasElement;
+
+begin
+  aTitle:=aClass.Name+' : '+aTitle+' '+SDocByName;
+  AppendTitle(aTitle);
+
+  List := TFPList.Create;
+  try
+    ThisClass := AClass;
+    while Assigned(ThisClass) do
+      begin
+      for i := 0 to ThisClass.Members.Count - 1 do
+        begin
+        Member := TPasElement(ThisClass.Members[i]);
+        if Engine.ShowElement(Member) and AFilter(Member) then
+          List.Add(Member)
+        end;
+      if (ThisClass.AncestorType is TPasClassType) then
+        ThisClass := TPasClassType(ThisClass.AncestorType)
+      else
+        ThisClass := Nil;
+      end;
+    CreateMemberDeclarations(aClass, List, [mloAppendParent]);
+  finally
+    List.Free;
+  end;
+end;
+
+function TMarkdownWriter.GetAdditionalConfig: TStrings;
+begin
+  if FAdditionalConfig=Nil then
+    FAdditionalConfig:=TstringList.Create;
+  Result:=FAdditionalConfig;
+end;
+
+procedure TMarkdownWriter.CreateClassPageBody(AClass: TPasClassType;
+  ASubpageIndex: Integer);
+
+
+begin
+  case ASubpageIndex of
+    0:
+      CreateClassMainPage(aClass);
+    PropertiesByInheritanceSubindex:
+      CreateInheritanceSubpage(aClass,SDocPropertyOverview,@PropertyFilter);
+    PropertiesByNameSubindex:
+      CreateSortedSubpage(aClass,SDocPropertyOverview, @PropertyFilter);
+    MethodsByInheritanceSubindex:
+      CreateInheritanceSubpage(aClass,SDocMethodOverview,@MethodFilter);
+    MethodsByNameSubindex:
+      CreateSortedSubpage(aClass,SDocMethodOverview,@MethodFilter);
+    EventsByInheritanceSubindex:
+      CreateInheritanceSubpage(aClass,SDocEventOverview,@EventFilter);
+    EventsByNameSubindex:
+      CreateSortedSubpage(aClass,SDocEventOverview, @EventFilter);
+  end;
+end;
+
+procedure TMarkdownWriter.CreateClassMemberPageBody(AElement: TPasElement);
+
+  procedure CreateVarPage(Element: TPasVariable);
+  begin
+    AppendDeclaration(Element,'Var',True);
+  end;
+
+  procedure CreateTypePage(Element: TPasType);
+  begin
+    AppendDeclaration(Element,'Type',True);
+  end;
+
+  procedure CreateConstPage(Element: TPasConst);
+  begin
+    AppendDeclaration(Element,'Const',True);
+  end;
+
+  procedure CreatePropertyPage(Element: TPasProperty);
+
+  begin
+    AppendDeclaration(Element,'Property',True);
+  end;
+
+var
+  s: String;
+
+begin
+  AppendTitle(aElement.FullName);
+  AppendShortDescr(AElement);
+  AppendHeader(2, SDocDeclaration);
+  AppendSourceRef(AElement);
+
+  if AElement is TPasProperty then
+    S:='Property'
+  else if AElement is TPasConst then
+    S:='Const'
+  else if (AElement is TPasVariable) then
+    S:='var'
+  else if AElement is TPasProcedureBase then
+    s:=''
+  else if AElement is TPasType then
+    S:='Type'
+  else
+    AppendText('<' + AElement.ClassName + '>');
+
+  AppendDeclaration(aElement,S,True);
+
+  FinishElementPage(AElement);
+end;
+
+procedure TMarkdownWriter.CreateVarPageBody(AVar: TPasVariable);
+
+begin
+  AppendTitle(AVar.FullName);
+  AppendShortDescr(AVar);
+  AppendHeader(2, SDocDeclaration);
+  AppendSourceRef(AVar);
+
+  AppendDeclaration(aVar,'var',false);
+
+  FinishElementPage(AVar);
+end;
+
+procedure TMarkdownWriter.CreateProcPageBody(AProc: TPasProcedureBase);
+begin
+
+  AppendTitle(AProc.Name);
+  AppendShortDescr(AProc);
+  AppendHeader(2,SDocDeclaration);
+  AppendSourceRef(AProc);
+
+  AppendDeclaration(AProc,'',False);
+
+  FinishElementPage(AProc);
+end;
+
+function TMarkdownWriter.InterPretOption ( const Cmd, Arg: String ) : boolean;
+
+  procedure ReadFile(aStrings : TStrings; aFileName : string);
+
+  begin
+    aFileName:= SetDirSeparators(aFileName);
+    if copy(aFileName,1,1)<>'@' then
+      aStrings.text:=aFileName
+    else
+      begin
+      Delete(aFileName,1,1);
+      aStrings.LoadFromFile(aFileName);
+      end;
+  end;
+
+begin
+  Result:=True;
+  if Cmd = '--footer' then
+    ReadFile(FooterMarkDown,Arg)
+  else if Cmd = '--header' then
+    ReadFile(HeaderMarkDown,Arg)
+  else if Cmd = '--index-colcount' then
+    IndexColCount := StrToIntDef(Arg,IndexColCount)
+  else if Cmd = '--image-url' then
+    FBaseImageURL  := Arg
+  else if Cmd = '--theme' then
+    if arg='-' then
+      Theme:=''
+    else
+      Theme  := Arg
+end;
+
+class procedure TMarkdownWriter.Usage(List: TStrings);
+begin
+  List.add('--header=file');
+  List.Add(SHTMLUsageHeader);
+  List.add('--footer=file');
+  List.Add(SHTMLUsageFooter);
+  List.Add('--index-colcount=N');
+  List.Add(SHTMLIndexColcount);
+  List.Add('--image-url=url');
+  List.Add(SHTMLImageUrl);
+end;
+
+class procedure TMarkdownWriter.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, ..\pkg
+    ALinkPrefix := '../' + ChangeFileExt(ExtractFileName(AFilename), '');
+    AFilename := ChangeFileExt(AFilename, '.xct');
+    end;
+end;
+
+class function TMarkdownWriter.FileNameExtension: String;
+begin
+  result:='md';
+end;
+
+// private methods
+
+function TMarkdownWriter.GetPageCount: Integer;
+begin
+  Result := PageInfos.Count;
+end;
+
+procedure TMarkdownWriter.SetOnTest(const AValue: TNotifyEvent);
+begin
+  if FOnTest=AValue then exit;
+    FOnTest:=AValue;
+end;
+
+
+procedure TMarkdownWriter.AppendText(const S: String);
+begin
+  AppendToLine(S,True);
+end;
+
+
+initialization
+  // Do not localize.
+  RegisterWriter(TMarkdownWriter,'md','Markdown output.');
+
+finalization
+  UnRegisterWriter('md');
+end.

+ 661 - 1
utils/fpdoc/dwriter.pp

@@ -25,7 +25,7 @@ unit dWriter;
 {$WARN 5024 off : Parameter "$1" not used}
 interface
 
-uses Classes, DOM, dGlobals, PasTree, SysUtils, fpdocclasstree;
+uses Classes, DOM, contnrs, dGlobals, PasTree, SysUtils, fpdocclasstree;
 
 resourcestring
   SErrFileWriting = 'An error occurred during writing of file "%s": %s';
@@ -65,6 +65,26 @@ type
     Destructor Destroy; override;
   end;
 
+  TFileAllocator = class
+  public
+    procedure AllocFilename(AElement: TPasElement; ASubindex: Integer); virtual;
+    function GetFilename(AElement: TPasElement;
+      ASubindex: Integer): String; virtual; abstract;
+    function GetRelativePathToTop(AElement: TPasElement): String; virtual;
+    function GetCSSFilename(ARelativeTo: TPasElement): DOMString; virtual;
+  end;
+
+  TLongNameFileAllocator = class(TFileAllocator)
+  private
+    FExtension: String;
+  public
+    constructor Create(const AExtension: String);
+    function GetFilename(AElement: TPasElement; ASubindex: Integer): String; override;
+    function GetRelativePathToTop(AElement: TPasElement): String; override;
+    property Extension: String read FExtension;
+  end;
+
+
   TWriterLogEvent = Procedure(Sender : TObject; Const Msg : String) of object;
   TWriterNoteEvent = Procedure(Sender : TObject; Note : TDomElement; Var EmitNote : Boolean) of object;
   
@@ -191,9 +211,104 @@ type
     Property BeforeEmitNote : TWriterNoteEvent Read FBeforeEmitNote Write FBeforeEmitNote;
   end;
 
+const
+  // The Multi-Page doc writer identifies each page by it's index.
+  IdentifierIndex = 0;
+
+  // Subpage indices for modules
+  ResstrSubindex = 1;
+  ConstsSubindex = 2;
+  TypesSubindex = 3;
+  ClassesSubindex = 4;
+  ProcsSubindex = 5;
+  VarsSubindex = 6;
+  // Maybe needed later for topic overview ??
+  TopicsSubIndex = 7;
+  IndexSubIndex = 8;
+  ClassHierarchySubIndex = 9;
+
+  // Subpage indices for classes
+  PropertiesByInheritanceSubindex = 11;
+  PropertiesByNameSubindex = 12;
+  MethodsByInheritanceSubindex = 13;
+  MethodsByNameSubindex = 14;
+  EventsByInheritanceSubindex = 15;
+  EventsByNameSubindex = 16;
+
+Type
+  { TMultiFileDocWriter }
+
+  { TPageInfo }
+
+  TPageInfo = class
+  Public
+    Element: TPasElement;
+    SubpageIndex: Integer;
+    Constructor Create(aElement : TPasElement; aIndex : Integer);
+  end;
+
+  { TLinkData }
+
+  TLinkData = Class(TObject)
+    FPathName,
+    FLink,
+    FModuleName : String;
+    Constructor Create(Const APathName,ALink,AModuleName : string);
+  end;
+
+  TMultiFileDocWriter = Class(TFPDocWriter)
+  Private
+    FAllocator: TFileAllocator;
+    FBaseDirectory: String;
+    FCurDirectory: String;
+    FModule: TPasModule;
+    FPageInfos: TFPObjectList;     // list of TPageInfo objects
+    function GetPageCount: Integer;
+
+  Protected
+    function ResolveLinkID(const Name: String; Level: Integer=0): DOMString;
+    function ResolveLinkIDInUnit(const Name,AUnitName: String): DOMString;
+    function ResolveLinkWithinPackage(AElement: TPasElement; ASubpageIndex: Integer): String;
+    Function CreateAllocator : TFileAllocator; virtual; abstract;
+    // aFileName is the filename allocated by the Allocator, nothing prefixed.
+    procedure WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer); virtual; abstract;
+    procedure AllocatePages; virtual;
+    // Default page allocation mechanism.
+    function AddPage(AElement: TPasElement; ASubpageIndex: Integer): TPageInfo; virtual;
+    procedure AddPages(AElement: TPasElement; ASubpageIndex: Integer; AList: TFPList);  virtual;
+    procedure AddTopicPages(AElement: TPasElement);   virtual;
+    procedure AllocateClassMemberPages(AModule: TPasModule; LinkList: TObjectList); virtual;
+    procedure AllocateModulePages(AModule: TPasModule; LinkList: TObjectList); virtual;
+    procedure AllocatePackagePages; virtual;
+    // Prefix every filename generated with the eesult of this.
+    function GetFileBaseDir(aOutput: String): String; virtual;
+
+    function  ModuleHasClasses(AModule: TPasModule): Boolean;
+    Property PageInfos : TFPObjectList Read FPageInfos;
+  Public
+    constructor Create(APackage: TPasPackage; AEngine: TFPDocEngine); override;
+    Destructor Destroy; override;
+    procedure WriteDoc; override;
+    property PageCount: Integer read GetPageCount;
+    Property Allocator : TFileAllocator Read FAllocator Write FAllocator;
+    Property Module: TPasModule  Read FModule Write FModule;
+    Property CurDirectory: String Read FCurDirectory Write FCurDirectory;    // relative to curdir of process
+    property BaseDirectory: String read FBaseDirectory Write FBaseDirectory; // relative path to package base directory
+ end;
+
   TFPDocWriterClass = Class of TFPDocWriter;
   EFPDocWriterError = Class(Exception);
 
+// Member Filter Callback type
+  TMemberFilter = function(AMember: TPasElement): Boolean;
+
+//  Filter Callbacks
+function PropertyFilter(AMember: TPasElement): Boolean;
+function MethodFilter(AMember: TPasElement): Boolean;
+function EventFilter(AMember: TPasElement): Boolean;
+
+
+
 // Register backend
 Procedure RegisterWriter(AClass : TFPDocWriterClass; Const AName,ADescr : String);
 // UnRegister backend
@@ -204,9 +319,41 @@ Function  GetWriterClass(AName : String) : TFPDocWriterClass;
 Function  FindWriterClass(AName : String) : Integer;
 // List of backend in name=descr form.
 Procedure EnumWriters(List : TStrings);
+// Sort elements on name
+function SortPasElements(Item1, Item2: Pointer): Integer;
+
 
 implementation
 
+function SortPasElements(Item1, Item2: Pointer): Integer;
+begin
+  Result:=CompareText(TPasElement(Item1).Name,TPasElement(Item2).Name)
+end;
+
+
+
+{ ---------------------------------------------------------------------
+  Filter callbacks
+  ---------------------------------------------------------------------}
+
+
+function PropertyFilter(AMember: TPasElement): Boolean;
+begin
+  Result := (AMember.ClassType = TPasProperty) and
+    (Copy(AMember.Name, 1, 2) <> 'On');
+end;
+
+function MethodFilter(AMember: TPasElement): Boolean;
+begin
+  Result := AMember.InheritsFrom(TPasProcedureBase);
+  // Writeln(aMember.Name,' (',aMember.ClassName,') is Method ',Result);
+end;
+
+function EventFilter(AMember: TPasElement): Boolean;
+begin
+  Result := (AMember.ClassType = TPasProperty) and
+    (Copy(AMember.Name, 1, 2) = 'On');
+end;
 
 { ---------------------------------------------------------------------
   Writer registration
@@ -225,6 +372,406 @@ Type
     Constructor Create (AClass : TFPDocWriterClass; Const AName,ADescr : String);
   end;
 
+{ TPageInfo }
+
+constructor TPageInfo.Create(aElement: TPasElement; aIndex: Integer);
+begin
+  Element:=aELement;
+  SubpageIndex:=aIndex;
+end;
+
+{ TLinkData }
+
+constructor TLinkData.Create(Const APathName, ALink, AModuleName: string);
+begin
+  FPathName:=APathName;
+  FLink:=ALink;
+  FModuleName:=AModuleName;
+end;
+
+
+{ TMultiFileDocWriter }
+
+constructor TMultiFileDocWriter.Create(APackage: TPasPackage;
+  AEngine: TFPDocEngine);
+begin
+  inherited Create(APackage, AEngine);
+  FAllocator:=CreateAllocator;
+  FPageInfos:=TFPObjectList.Create;
+end;
+
+destructor TMultiFileDocWriter.Destroy;
+begin
+  FreeAndNil(FPageInfos);
+  FreeAndNil(FAllocator);
+  inherited Destroy;
+end;
+
+function TMultiFileDocWriter.GetPageCount: Integer;
+begin
+  Result := PageInfos.Count;
+end;
+
+function TMultiFileDocWriter.ResolveLinkID(const Name: String; Level : Integer = 0): DOMString;
+
+var
+  res,s: String;
+
+begin
+  res:=Engine.ResolveLink(Module,Name, True);
+  // engine can return backslashes on Windows
+  if Length(res) > 0 then
+   begin
+     s:=Copy(Res, 1, Length(CurDirectory) + 1);
+    if (S= CurDirectory + '/') or (s= CurDirectory + '\') then
+      Res := Copy(Res, Length(CurDirectory) + 2, Length(Res))
+    else if not IsLinkAbsolute(Res) then
+      Res := BaseDirectory + Res;
+   end;
+  Result:=UTF8Decode(Res);
+end;
+
+{ Used for:
+  - <link> elements in descriptions
+  - "see also" entries
+  - AppendHyperlink (for unresolved parse tree element links)
+}
+
+function TMultiFileDocWriter.ResolveLinkIDInUnit(const Name,AUnitName: String): DOMString;
+
+begin
+  Result:=ResolveLinkID(Name);
+  If (Result='') and (AUnitName<>'') and (length(Name)>0) and (Name[1]<>'#') then
+     Result:=ResolveLinkID(AUnitName+'.'+Name);
+end;
+
+
+function TMultiFileDocWriter.ResolveLinkWithinPackage(AElement: TPasElement;
+  ASubpageIndex: Integer): String;
+var
+  ParentEl: TPasElement;
+  s : String;
+begin
+  ParentEl := AElement;
+  while Assigned(ParentEl) and not (ParentEl.ClassType = TPasPackage) do
+    ParentEl := ParentEl.Parent;
+  if Assigned(ParentEl) and (TPasPackage(ParentEl) = Engine.Package) then
+  begin
+    Result := Allocator.GetFilename(AElement, ASubpageIndex);
+    // engine/allocator can return backslashes on Windows
+    s:=Copy(Result, 1, Length(CurDirectory) + 1);
+    if (S= CurDirectory + '/') or (s= CurDirectory + '\') then
+      Result := Copy(Result, Length(CurDirectory) + 2, Length(Result))
+    else
+      Result := BaseDirectory + Result;
+  end else
+    SetLength(Result, 0);
+end;
+
+
+Function TMultiFileDocWriter.AddPage(AElement: TPasElement; ASubpageIndex: Integer) : TPageInfo;
+
+begin
+  Result:= TPageInfo.Create(aElement,aSubPageIndex);
+  PageInfos.Add(Result);
+  Allocator.AllocFilename(AElement, ASubpageIndex);
+  if ASubpageIndex = 0 then
+    Engine.AddLink(AElement.PathName,Allocator.GetFilename(AElement, ASubpageIndex));
+end;
+
+procedure TMultiFileDocWriter.AddTopicPages(AElement: TPasElement);
+
+var
+  PreviousTopic,
+  TopicElement : TTopicElement;
+  DocNode,
+  TopicNode : TDocNode;
+
+begin
+  DocNode:=Engine.FindDocNode(AElement);
+  If not Assigned(DocNode) then
+    exit;
+  TopicNode:=DocNode.FirstChild;
+  PreviousTopic:=Nil;
+  While Assigned(TopicNode) do
+    begin
+    If TopicNode.TopicNode then
+      begin
+      TopicElement:=TTopicElement.Create(TopicNode.Name,AElement);
+      Topics.Add(TopicElement);
+      TopicElement.TopicNode:=TopicNode;
+      TopicElement.Previous:=PreviousTopic;
+      If Assigned(PreviousTopic) then
+        PreviousTopic.Next:=TopicElement;
+      PreviousTopic:=TopicElement;
+      if AElement is TTopicElement then
+        TTopicElement(AElement).SubTopics.Add(TopicElement);
+      AddPage(TopicElement,IdentifierIndex);
+      if AElement is TTopicElement then
+        TTopicElement(AElement).SubTopics.Add(TopicElement)
+      else // Only one level of recursion.
+        AddTopicPages(TopicElement);
+      end;
+    TopicNode:=TopicNode.NextSibling;
+    end;
+end;
+
+
+Function TMultiFileDocWriter.ModuleHasClasses(AModule: TPasModule) : Boolean;
+
+begin
+  result:=assigned(AModule)
+         and assigned(AModule.InterfaceSection)
+         and assigned(AModule.InterfaceSection.Classes)
+         and (AModule.InterfaceSection.Classes.Count>0);
+end;
+
+procedure TMultiFileDocWriter.AddPages(AElement: TPasElement; ASubpageIndex: Integer;
+  AList: TFPList);
+var
+  i,j: Integer;
+  R : TPasRecordtype;
+  FPEl : TPasElement;
+  DocNode: TDocNode;
+begin
+  if AList.Count > 0 then
+    begin
+    AddPage(AElement, ASubpageIndex);
+    for i := 0 to AList.Count - 1 do
+      begin
+      AddPage(TPasElement(AList[i]), 0);
+      if (TObject(AList[i]) is TPasRecordType) then
+        begin
+        R:=TObject(AList[I]) as TPasRecordType;
+        For J:=0 to R.Members.Count-1 do
+          begin
+          FPEl:=TPasElement(R.Members[J]);
+          if ((FPEL is TPasProperty) or (FPEL is TPasProcedureBase))
+             and Engine.ShowElement(FPEl) then
+               begin
+               DocNode := Engine.FindDocNode(FPEl);
+               if Assigned(DocNode) then
+                 AddPage(FPEl, 0);
+               end;
+          end;
+        end;
+      end;
+    end;
+end;
+
+Procedure TMultiFileDocWriter.AllocateClassMemberPages(AModule: TPasModule; LinkList : TObjectList);
+var
+  i, j, k: Integer;
+  ClassEl: TPasClassType;
+  FPEl, AncestorMemberEl: TPasElement;
+  DocNode: TDocNode;
+  ALink : DOMString;
+  DidAutolink: Boolean;
+
+begin
+  for i := 0 to AModule.InterfaceSection.Classes.Count - 1 do
+    begin
+    ClassEl := TPasClassType(AModule.InterfaceSection.Classes[i]);
+    AddPage(ClassEl, 0);
+    // !!!: Only add when there are items
+    AddPage(ClassEl, PropertiesByInheritanceSubindex);
+    AddPage(ClassEl, PropertiesByNameSubindex);
+    AddPage(ClassEl, MethodsByInheritanceSubindex);
+    AddPage(ClassEl, MethodsByNameSubindex);
+    AddPage(ClassEl, EventsByInheritanceSubindex);
+    AddPage(ClassEl, EventsByNameSubindex);
+    for j := 0 to ClassEl.Members.Count - 1 do
+      begin
+      FPEl := TPasElement(ClassEl.Members[j]);
+      if Not Engine.ShowElement(FPEl) then
+        continue;
+      DocNode := Engine.FindDocNode(FPEl);
+      if Assigned(DocNode) then
+        begin
+        if Assigned(DocNode.Node) then
+          ALink:=DocNode.Node['link']
+        else
+          ALink:='';
+        If (ALink<>'') then
+          LinkList.Add(TLinkData.Create(FPEl.PathName,UTF8Encode(ALink),AModule.name))
+        else
+          AddPage(FPEl, 0);
+        end
+      else
+        begin
+        DidAutolink := False;
+        if Assigned(ClassEl.AncestorType) and
+          (ClassEl.AncestorType.ClassType.inheritsfrom(TPasClassType)) then
+          begin
+          for k := 0 to TPasClassType(ClassEl.AncestorType).Members.Count - 1 do
+            begin
+            AncestorMemberEl :=
+              TPasElement(TPasClassType(ClassEl.AncestorType).Members[k]);
+            if AncestorMemberEl.Name = FPEl.Name then
+              begin
+              DocNode := Engine.FindDocNode(AncestorMemberEl);
+              if Assigned(DocNode) then
+                begin
+                DidAutolink := True;
+                Engine.AddLink(FPEl.PathName,
+                  Engine.FindAbsoluteLink(AncestorMemberEl.PathName));
+                break;
+                end;
+              end;
+            end;
+          end;
+        if not DidAutolink then
+          AddPage(FPEl, 0);
+        end;
+      end;
+    end;
+end;
+
+procedure TMultiFileDocWriter.AllocateModulePages(AModule: TPasModule; LinkList : TObjectList);
+
+var
+  i: Integer;
+  s: String;
+
+begin
+  if not assigned(Amodule.Interfacesection) then
+    exit;
+  AddPage(AModule, 0);
+  AddPage(AModule,IndexSubIndex);
+  AddTopicPages(AModule);
+  with AModule do
+    begin
+    if InterfaceSection.ResStrings.Count > 0 then
+      begin
+      AddPage(AModule, ResstrSubindex);
+      s := Allocator.GetFilename(AModule, ResstrSubindex);
+      for i := 0 to InterfaceSection.ResStrings.Count - 1 do
+        with TPasResString(InterfaceSection.ResStrings[i]) do
+          Engine.AddLink(PathName, s + '#' + LowerCase(Name));
+      end;
+    AddPages(AModule, ConstsSubindex, InterfaceSection.Consts);
+    AddPages(AModule, TypesSubindex, InterfaceSection.Types);
+    if InterfaceSection.Classes.Count > 0 then
+      begin
+      AddPage(AModule, ClassesSubindex);
+      AllocateClassMemberPages(AModule,LinkList);
+      end;
+
+    AddPages(AModule, ProcsSubindex, InterfaceSection.Functions);
+    AddPages(AModule, VarsSubindex, InterfaceSection.Variables);
+    end;
+end;
+
+  
+procedure TMultiFileDocWriter.AllocatePackagePages;
+
+Var
+  I : Integer;
+  H : Boolean;
+
+begin
+  if Length(Package.Name) <= 1 then
+    exit;
+  AddPage(Package, 0);
+  AddPage(Package,IndexSubIndex);
+  I:=0;
+  H:=False;
+  While (I<Package.Modules.Count) and Not H do
+    begin
+    H:=ModuleHasClasses(TPasModule(Package.Modules[i]));
+    Inc(I);
+    end;
+  if H then
+    AddPage(Package,ClassHierarchySubIndex);
+  AddTopicPages(Package);
+end;
+
+procedure TMultiFileDocWriter.AllocatePages;
+
+Var
+  L : TObjectList;
+  ML : TFPList;
+  I : Integer;
+
+
+begin
+  // Allocate page for the package itself, if a name is given (i.e. <> '#')
+  AllocatePackagePages;
+  ML:=Nil;
+  L:=TObjectList.Create;
+  try
+    ML:=TFPList.Create;
+    ML.AddList(Package.Modules);
+    ML.Sort(@SortPasElements);
+    for i := 0 to ML.Count - 1 do
+      AllocateModulePages(TPasModule(ML[i]),L);
+    // Resolve links
+    For I:=0 to L.Count-1 do
+      With TLinkData(L[i]) do
+        Engine.AddLink(FPathName,UTF8Encode(ResolveLinkIDInUnit(FLink,FModuleName)));
+  finally
+    L.Free;
+  end;
+end;
+
+function TMultiFileDocWriter.GetFileBaseDir(aOutput: String) : String;
+
+begin
+  Result:=Engine.Output;
+  if Result<>'' then
+    Result:=IncludeTrailingPathDelimiter(Result);
+end;
+
+procedure TMultiFileDocWriter.WriteDoc;
+
+  procedure CreatePath(const AFilename: String);
+
+  var
+    EndIndex: Integer;
+    Path: String;
+  begin
+    EndIndex := Length(AFilename);
+    if EndIndex = 0 then
+      exit;
+    while not (AFilename[EndIndex] in AllowDirectorySeparators) do
+    begin
+      Dec(EndIndex);
+      if EndIndex = 0 then
+        exit;
+    end;
+
+    Path := Copy(AFilename, 1, EndIndex - 1);
+    if not DirectoryExists(Path) then
+    begin
+      CreatePath(Path);
+      MkDir(Path);
+    end;
+  end;
+
+
+var
+  i: Integer;
+  FileName : String;
+  FinalFilename: String;
+
+begin
+  AllocatePages;
+  DoLog(SWritingPages, [PageCount]);
+  if Engine.Output <> '' then
+    Engine.Output := IncludeTrailingBackSlash(Engine.Output);
+   for i := 0 to PageInfos.Count - 1 do
+     with TPageInfo(PageInfos[i]) do
+       begin
+       FileName:= Allocator.GetFilename(Element, SubpageIndex);
+       FinalFilename := GetFileBaseDir(Engine.Output) + FileName;
+       CreatePath(FinalFilename);
+       WriteDocPage(FileName,ELement,SubPageIndex);
+       end;
+end;
+
+
+
+
 { TWriterRecord }
 
 constructor TWriterRecord.Create(AClass: TFPDocWriterClass; const AName,
@@ -328,6 +875,116 @@ begin
     end;
 end;
 
+{ ---------------------------------------------------------------------
+  TFileAllocator
+  ---------------------------------------------------------------------}
+
+
+procedure TFileAllocator.AllocFilename(AElement: TPasElement;
+  ASubindex: Integer);
+begin
+end;
+
+function TFileAllocator.GetRelativePathToTop(AElement: TPasElement): String;
+begin
+  Result:='';
+end;
+
+function TFileAllocator.GetCSSFilename(ARelativeTo: TPasElement): DOMString;
+begin
+  Result := Utf8Decode(GetRelativePathToTop(ARelativeTo)) + 'fpdoc.css';
+end;
+
+{ ---------------------------------------------------------------------
+  TLongNameFileAllocator
+  ---------------------------------------------------------------------}
+
+
+constructor TLongNameFileAllocator.Create(const AExtension: String);
+begin
+  inherited Create;
+  FExtension := AExtension;
+end;
+
+function TLongNameFileAllocator.GetFilename(AElement: TPasElement; ASubindex: Integer): String;
+
+var
+  n,s: String;
+  i: Integer;
+
+begin
+  Result:='';
+  if AElement.ClassType = TPasPackage then
+    Result := 'index'
+  else if AElement.ClassType = TPasModule then
+    Result := LowerCase(AElement.Name) + PathDelim + 'index'
+  else
+  begin
+    if AElement is TPasOperator then
+    begin
+      if Assigned(AElement.Parent) then
+        result:=LowerCase(AElement.Parent.PathName);
+      With TPasOperator(aElement) do
+        Result:= Result + 'op-'+OperatorTypeToOperatorName(OperatorType);
+      s := '';
+      N:=LowerCase(aElement.Name); // Should not contain any weird chars.
+      Delete(N,1,Pos('(',N));
+      i := 1;
+      Repeat
+        I:=Pos(',',N);
+        if I=0 then
+          I:=Pos(')',N);
+        if I>1 then
+          begin
+          if (S<>'') then
+            S:=S+'-';
+          S:=S+Copy(N,1,I-1);
+          end;
+        Delete(N,1,I);
+      until I=0;
+      // First char is maybe :
+      if (N<>'') and  (N[1]=':') then
+        Delete(N,1,1);
+      Result:=Result + '-'+ s + '-' + N;
+    end else
+      Result := LowerCase(AElement.PathName);
+    // searching for TPasModule - it is on the 2nd level
+    if Assigned(AElement.Parent) then
+      while Assigned(AElement.Parent.Parent) do
+        AElement := AElement.Parent;
+    // cut off Package Name
+    Result := Copy(Result, Length(AElement.Parent.Name) + 2, MaxInt);
+    // to skip dots in unit name
+    i := Length(AElement.Name);
+    while (i <= Length(Result)) and (Result[i] <> '.') do
+      Inc(i);
+    if (i <= Length(Result)) and (i > 0) then
+      Result[i] := PathDelim;
+  end;
+
+  if ASubindex > 0 then
+    Result := Result + '-' + IntToStr(ASubindex);
+
+  Result := Result + Extension;
+end;
+
+function TLongNameFileAllocator.GetRelativePathToTop(AElement: TPasElement): String;
+begin
+  if (AElement.ClassType=TPasPackage) then
+    Result := ''
+  else if (AElement.ClassType=TTopicElement) then
+    begin
+    If (AElement.Parent.ClassType=TTopicElement) then
+      Result:='../'+GetRelativePathToTop(AElement.Parent)
+    else if (AElement.Parent.ClassType=TPasPackage) then
+      Result:=''
+    else if (AElement.Parent.ClassType=TPasModule) then
+      Result:='../';
+    end
+  else
+    Result := '../';
+end;
+
 
 { ---------------------------------------------------------------------
   TFPDocWriter
@@ -1289,6 +1946,9 @@ begin
   List.Sorted:=False;
 end;
 
+
+
+
 initialization
   InitWriterList;
 finalization

+ 22 - 5
utils/fpdoc/fpdoc.lpi

@@ -28,15 +28,15 @@
     </PublishOptions>
     <RunParams>
       <local>
-        <CommandLineParams Value="--macro=FPCDIR=../fpcsrc --project=rtl-project.xml --format=html --output=rtl --footer-date=&quot;mmm dd yyyy&quot;"/>
-        <WorkingDirectory Value="/home/michael/FPC/build/tag_3_2_0/fpcdocs"/>
+        <CommandLineParams Value="--macro=FPCDIR=/Users/Michael/fpc/ --project=rtl-project.xml --format=md --output=rtl"/>
+        <WorkingDirectory Value="/Users/Michael/FPC_SRC/docs"/>
       </local>
       <FormatVersion Value="2"/>
       <Modes Count="1">
         <Mode0 Name="default">
           <local>
-            <CommandLineParams Value="--macro=FPCDIR=../fpcsrc --project=rtl-project.xml --format=html --output=rtl --footer-date=&quot;mmm dd yyyy&quot;"/>
-            <WorkingDirectory Value="/home/michael/FPC/build/tag_3_2_0/fpcdocs"/>
+            <CommandLineParams Value="--macro=FPCDIR=/Users/Michael/fpc/ --project=rtl-project.xml --format=md --output=rtl"/>
+            <WorkingDirectory Value="/Users/Michael/FPC_SRC/docs"/>
           </local>
         </Mode0>
       </Modes>
@@ -46,7 +46,7 @@
         <PackageName Value="FCL"/>
       </Item1>
     </RequiredPackages>
-    <Units Count="16">
+    <Units Count="19">
       <Unit0>
         <Filename Value="fpdoc.pp"/>
         <IsPartOfProject Value="True"/>
@@ -118,6 +118,18 @@
         <Filename Value="fpdocclasstree.pp"/>
         <IsPartOfProject Value="True"/>
       </Unit15>
+      <Unit16>
+        <Filename Value="dw_markdown.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit16>
+      <Unit17>
+        <Filename Value="dw_chm.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit17>
+      <Unit18>
+        <Filename Value="dw_basemd.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit18>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -129,6 +141,11 @@
       <IncludeFiles Value="$(ProjOutDir)"/>
       <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
     </SearchPaths>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf3"/>
+      </Debugging>
+    </Linking>
   </CompilerOptions>
   <Debugging>
     <Exceptions Count="3">

+ 2 - 2
utils/fpdoc/fpdoc.pp

@@ -32,12 +32,12 @@ uses
   dw_dxml,   // Delphi XML doc.
   dw_HTML,   // HTML writer
   dw_chm,    // CHM Writer
-  // dw_markdown, // Markdown writer
+  dw_markdown, // Markdown writer
   dw_ipflin, // IPF writer (new linear output)
   dw_man,    // Man page writer
   dw_linrtf, // linear RTF writer
   dw_txt,    // TXT writer
-  fpdocproj, mkfpdoc;
+  fpdocproj, mkfpdoc, dw_basemd;
 
 
 Type

+ 4 - 0
utils/fpdoc/fpmake.pp

@@ -53,6 +53,8 @@ begin
     T.Dependencies.AddUnit('dwlinear');
     T.Dependencies.AddUnit('dw_txt');
     T.Dependencies.AddUnit('dw_linrtf');
+    T.Dependencies.AddUnit('dw_basemd');
+    T.Dependencies.AddUnit('dw_markdown');
 
     T:=P.Targets.AddProgram('makeskel.pp');
     T.ResourceStrings:=true;
@@ -78,6 +80,8 @@ begin
     P.Targets.AddUnit('dw_xml.pp').install:=false;
     P.Targets.AddUnit('sh_pas.pp').install:=false;
     P.Targets.AddUnit('dw_html.pp').install:=false;
+    P.Targets.AddUnit('dw_basemd.pp').install:=false;
+    P.Targets.AddUnit('dw_markdown.pp').install:=false;
     T:=P.Targets.AddUnit('dw_latex.pp');
     T.install:=false;
     T.ResourceStrings:=true;