1 { Converter for wiki pages to fpdoc topics
2 
3   Copyright (C) 2012  Mattias Gaertner  mattias@freepascal.org
4 
5   This source is free software; you can redistribute it and/or modify it under
6   the terms of the GNU General Public License as published by the Free
7   Software Foundation; either version 2 of the License, or (at your option)
8   any later version.
9 
10   This code is distributed in the hope that it will be useful, but WITHOUT ANY
11   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13   details.
14 
15   A copy of the GNU General Public License is available on the World Wide Web
16   at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
17   to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
18   Boston, MA 02110-1335, USA.
19 
20 
21 <fpdoc>
22   <package="wiki"/>
23     <module name='wiki_page_name'>
24       <!-- header 1-->
25       <short>Page title</short>
26       <description> text under page header</description>
27       <!-- header 2-->
28       <topic name="wiki_page_name_title_1">
29         <short>Title header 1</short>
30         <description> text under header1</description>
31         <!-- header 3-->
32         <topic name="wiki_page_name_subtitle_1">
33           <short>Title header subtitle</short>
34           <descr>text</descr>
35         </topic>
36       </topic>
37     </module>
38   </package>
39 </fpdoc>
40 
41 
42   wptText,  // TWPTextToken
43   wptAttribute, // e.g. class="code" TWPNameValueToken
44   wptLineBreak, // <br> /br> <br/>
45   wptBold,    // '''
46   wptItalic,  // ''
47   wptStrikeTagShort, // <s>
48   wptUnderlineTag, // <u>
49   wptTT, // <tt>
50   wptSup, // <sup>
51   wptSub, // <sub>
52   wptSmall, // <small>
53   wptEm, // <em>
54   wptString, // <string>
55   wptVar, // <var>
56   wptKey, // <key>
57   wptCmt, // <cmt>
58   wptSpan, // <span>
59   wptCode, // TWPNameValueToken
60   wptSpecial, // double curly bracket: title, shortcut like Ctrl+Shift+1
61   wptPre,  // space at line start
62   wptP, // paragraph
63   wptCenter, // <center>
64   wptInternLink, // [[]]
65   wptExternLink, // []
66   wptHorizontalRow, // ----
67   wptNumberedList, // #
68   wptBulletList, // *
69   wptDefinitionList, // : or ;
70   wptListItem,
71   wptTable, // wiki tag for table
72   wptTableRow, // wiki tag for table row
73   wptTableHeadCell, // wiki tag for table head cell
74   wptTableCell, // wiki tag for table cell
75   wptSection, // started/ended by =
76   wptHeader, // =Text=
77 
78 }
79 unit Wiki2FPDocConvert;
80 
81 {$mode objfpc}{$H+}
82 
83 interface
84 
85 uses
86   Classes, SysUtils, WikiParser, laz2_DOM, LazFileUtils, laz2_XMLRead,
87   laz2_XMLWrite, LazLoggerBase, WikiFormat;
88 
89 type
90 
91   { TW2FPDocPage }
92 
93   TW2FPDocPage = class(TW2FormatPage)
94   private
95     DescrNode: TDOMElement; // the fpdoc <descr> node
96     CurNode: TDOMElement; // current fpdoc node
97   public
98     FPDoc: TXMLDocument;
99     procedure ClearConversion; override;
100   end;
101 
102   { TWiki2FPDocConverter }
103 
104   TWiki2FPDocConverter = class(TWiki2FormatConverter)
105   protected
106     FPackageName: string;
107     FRootName: string;
108     procedure ConvertPage(Page: TW2FPDocPage);
109     procedure ConvertContent(Page: TW2FPDocPage; DescrNode: TDOMElement);
110     procedure OnWikiToken(Token: TWPToken);
111     procedure SavePage(Page: TW2FPDocPage);
112     procedure SetPackageName(AValue: string);
113     procedure SetRootName(AValue: string);
114     procedure SaveProject;
115   public
116     constructor Create; override;
117     procedure Convert; override;
118     property RootName: string read FRootName write SetRootName;
119     property PackageName: string read FPackageName write SetPackageName;
120   end;
121 
122 implementation
123 
124 { TW2FPDocPage }
125 
126 procedure TW2FPDocPage.ClearConversion;
127 begin
128   FreeAndNil(FPDoc);
129 end;
130 
131 { TWiki2FPDocConverter }
132 
133 procedure TWiki2FPDocConverter.OnWikiToken(Token: TWPToken);
134 var
135   Page: TW2FPDocPage;
136 
137   procedure NodeNotOpen;
138   begin
139     raise Exception.Create('TWiki2FPDocConverter.OnWikiToken can not close:'
140       +' Token='+dbgs(Token.Token)+' '+DbgSName(Token)+' CurNode='+Page.CurNode.TagName);
141   end;
142 
143 var
144   TextToken: TWPTextToken;
145   Txt: String;
146   Node: TDOMElement;
147   LinkToken: TWPLinkToken;
148   URL: String;
149   Caption: String;
150   NodeName: String;
151   W: TWikiPage;
152   doc: TXMLDocument;
153 begin
154   Page:=TW2FPDocPage(Token.UserData);
155   W:=Page.WikiPage;
156   doc:=Page.FPDoc;
157   //debugln(['TWiki2FPDocConverter.OnWikiToken Token=',dbgs(Token.Token),' ',dbgs(Token)]);
158   case Token.Token of
159 
160   wptText:
161     if Token is TWPTextToken then begin
162       TextToken:=TWPTextToken(Token);
163       Txt:=copy(W.Src,TextToken.StartPos,TextToken.EndPos-TextToken.StartPos);
164       if Page.CurNode.FirstChild=nil then
165         Txt:=TrimLeft(Txt);
166       if Txt<>'' then begin
167         //debugln(['TWiki2FPDocConverter.OnWikiToken Text="',Txt,'"']);
168         Page.CurNode.AppendChild(doc.CreateTextNode(Txt));
169       end;
170       exit;
171     end;
172 
173   wptLineBreak, wptHorizontalRow:
174     begin
175       // ToDo: find out if fpdoc supports hr
176       // only append a br if there is something in front
177       if Page.CurNode.FirstChild<>nil then
178         Page.CurNode.AppendChild(doc.CreateElement('br'));
179       exit;
180     end;
181 
182   wptSection:
183     begin
184       // close descr
185       if Page.CurNode.TagName='descr' then
186         Page.CurNode:=Page.CurNode.ParentNode as TDOMElement;
187       if Token.Range=wprOpen then begin
188         Node:=doc.CreateElement('topic');
189         Page.CurNode.AppendChild(Node);
190         Page.CurNode:=Node;
191         Node:=doc.CreateElement('descr');
192         Page.CurNode.AppendChild(Node);
193         Page.CurNode:=Node;
194         exit;
195       end else if Token.Range=wprClose then begin
196         // close topic
197         if Page.CurNode.TagName<>'topic' then
198           NodeNotOpen;
199         Page.CurNode:=Page.CurNode.ParentNode as TDOMElement;
200         exit;
201       end;
202     end;
203 
204   wptHeader:
205     if Token.Range=wprOpen then begin
206       Node:=doc.CreateElement('short');
207       if Page.CurNode.TagName='descr' then
208         // insert <short> before <descr>
209         Page.CurNode.ParentNode.InsertBefore(Node,Page.CurNode)
210       else
211         Page.CurNode.AppendChild(Node);
212       Page.CurNode:=Node;
213       exit;
214     end else if Token.Range=wprClose then begin
215       // close header
216       if Page.CurNode.TagName<>'short' then
217         NodeNotOpen;
218       // continue in <descr>
219       Node:=TDOMElement(Page.CurNode.ParentNode.FindNode('descr'));
220       if Node<>nil then
221         Page.CurNode:=Node
222       else
223         Page.CurNode:=Page.CurNode.ParentNode as TDOMElement;
224       exit;
225     end;
226 
227   wptBold, wptItalic, wptUnderlineTag, wptTT, wptPre,
228   wptBulletList, wptNumberedList, wptListItem,
229   wptTable, wptTableRow, wptTableCell:
230     begin
231       // simple range
232       case Token.Token of
233       wptBold: NodeName:='b';
234       wptItalic: NodeName:='i';
235       wptUnderlineTag: NodeName:='u';
236       wptTT: NodeName:='var';
237       wptCode: NodeName:='code';
238       wptPre: NodeName:='pre';
239       wptNumberedList: NodeName:='ol';
240       wptBulletList: NodeName:='ul';
241       wptListItem: NodeName:='li';
242       wptTable: NodeName:='table';
243       wptTableRow: NodeName:='tr';
244       wptTableCell: NodeName:='td';
245       else NodeName:='';
246       end;
247       if Token.Range=wprOpen then begin
248         Node:=doc.CreateElement(NodeName);
249         Page.CurNode.AppendChild(Node);
250         Page.CurNode:=Node;
251         exit;
252       end else if Token.Range=wprClose then begin
253         if Page.CurNode.TagName<>NodeName then
254           NodeNotOpen;
255         Page.CurNode:=Page.CurNode.ParentNode as TDOMElement;
256         exit;
257       end;
258     end;
259 
260   wptInternLink, wptExternLink:
261     if Token is TWPLinkToken then begin
262       LinkToken:=TWPLinkToken(Token);
263       URL:=LinkToken.Link;
264       if URL<>'' then begin
265         // ToDo: convert URL
266         debugln(['TWiki2FPDocConverter.OnWikiToken ToDo: convert ',dbgs(Token.Token),' link "',URL,'"']);
267         Caption:=copy(W.Src,LinkToken.CaptionStartPos,LinkToken.CaptionEndPos-LinkToken.CaptionStartPos);
268         Node:=doc.CreateElement('link');
269         Node.SetAttribute('id',URL);
270         if Caption<>'' then
271           Node.AppendChild(doc.CreateTextNode(Caption));
272         Page.CurNode.AppendChild(Node);
273       end;
274       exit;
275     end;
276 
277   end;
278   debugln(['TWiki2FPDocConverter.OnWikiToken ToDo: Token=',dbgs(Token.Token),' Range=',dbgs(Token.Range),' Class=',Token.ClassName,' ',W.PosToStr(W.CurrentPos)]);
279 end;
280 
281 {   IsValidIdent returns true if the first character of Ident is in:
282     'A' to 'Z', 'a' to 'z' or '_' and the following characters are one of:
283     'A' to 'Z', 'a' to 'z', '0'..'9', '_' or '-'  }
IsValidNodeNamenull284 function IsValidNodeName(const Ident: string): boolean;
285 var
286   p: PChar;
287 begin
288   p:=PChar(Ident);
289   if not (p^ in ['A'..'Z', 'a'..'z', '_', '-']) then exit(false);
290   inc(p);
291   while true do begin
292     if p^ in ['A'..'Z', 'a'..'z', '0'..'9', '_', '-'] then
293       inc(p)
294     else if (p^=#0) and (p-PChar(Ident)=length(Ident)) then
295       exit(true)
296     else
297       exit(false);
298   end;
299 end;
300 
301 procedure TWiki2FPDocConverter.SetRootName(AValue: string);
302 begin
303   if (AValue='') or not IsValidNodeName(AValue) then
304     raise Exception.Create('invalid root name "'+AValue+'"');
305   if FRootName=AValue then Exit;
306   FRootName:=AValue;
307 end;
308 
309 procedure TWiki2FPDocConverter.SaveProject;
310 var
311   sl: TStringList;
312   Filename: String;
313   i: Integer;
314 begin
315   Filename:=AppendPathDelim(OutputDir)+PackageName+'.xml';
316   sl:=TStringList.Create;
317   try
318     sl.Add('<?xml version="1.0" encoding="utf-8"?>');
319     sl.Add('<docproject>');
320     sl.Add('  <options>');
321     sl.Add('    <option name="auto-index" value="1"/>');
322     sl.Add('    <option name="auto-toc" value="1"/>');
323     sl.Add('    <option name="make-searchable" value="1"/>');
324     sl.Add('    <option name="css-file" value="fpdoc.css"/>');
325     sl.Add('    <option name="charset" value="UTF8"/>');
326     // chm
327     //sl.Add('    <option name="format" value="chm"/>');
328     //sl.Add('    <option name="chm-title" value="Lazarus IDE Help"/>');
329     // html
330     //sl.Add('    <option name="format" value="html"/>');
331 
332     sl.Add('  </options>');
333     sl.Add('  <packages>');
334     // chm
335     //sl.Add('    <package name="'+PackageName+'" output="'+PackageName+'.chm"/>');
336     // html
337     sl.Add('    <package name="'+PackageName+'" output="."/>');
338     sl.Add('    <units>');
339     sl.Add('    </units>');
340     sl.Add('    <descriptions>');
341     for i:=0 to Count-1 do
342       sl.Add('      <description file="'+TW2FPDocPage(Pages[i]).WikiFilename+'"/>');
343     sl.Add('    </descriptions>');
344     sl.Add('  </packages>');
345     sl.Add('</docproject>');
346 
347     sl.SaveToFile(Filename);
348     if not Quiet then
349       debugln(['fpdoc project file: ',Filename]);
350   finally
351     sl.Free;
352   end;
353 end;
354 
355 procedure TWiki2FPDocConverter.ConvertPage(Page: TW2FPDocPage);
356 var
357   doc: TXMLDocument;
358   RootNode: TDOMElement;
359   PackageNode: TDOMElement;
360   ModuleNode: TDOMElement;
361   ModuleName: String;
362   ShortNode: TDOMElement;
363   DescrNode: TDOMElement;
364 begin
365   FreeAndNil(Page.FPDoc);
366   Page.FPDoc:=TXMLDocument.Create;
367   doc:=Page.FPDoc;
368   // <wiki>
369   RootNode:=doc.CreateElement(RootName);
370   doc.AppendChild(RootNode);
371   // <package name="wiki">
372   PackageNode:=doc.CreateElement('package');
373   RootNode.AppendChild(PackageNode);
374   PackageNode.SetAttribute('name',PackageName);
375   // <module name="wiki_page_name">
376   ModuleNode:=doc.CreateElement('module');
377   PackageNode.AppendChild(ModuleNode);
378   ModuleName:=ExtractFileNameOnly(Page.WikiFilename);
379   ModuleNode.SetAttribute('name',ModuleName);
380   // <short>Page title</short>
381   ShortNode:=doc.CreateElement('short');
382   ModuleNode.AppendChild(ShortNode);
383   ShortNode.AppendChild(doc.CreateTextNode(Page.WikiPage.Title));
384   // <descr>Text</descr>
385   DescrNode:=doc.CreateElement('descr');
386   ModuleNode.AppendChild(DescrNode);
387 
388   ConvertContent(Page,DescrNode);
389 end;
390 
391 procedure TWiki2FPDocConverter.ConvertContent(Page: TW2FPDocPage;
392   DescrNode: TDOMElement);
393 begin
394   try
395     Page.DescrNode:=DescrNode;
396     Page.CurNode:=DescrNode;
397     Page.WikiPage.Parse(@OnWikiToken,Page);
398   finally
399     Page.DescrNode:=nil;
400     Page.CurNode:=nil;
401   end;
402 end;
403 
404 procedure TWiki2FPDocConverter.SavePage(Page: TW2FPDocPage);
405 var
406   Filename: String;
407 begin
408   Filename:=AppendPathDelim(OutputDir)+ExtractFilename(Page.WikiFilename);
409   DebugLn(['TWiki2FPDocConverter.SavePage ',Filename]);
410   WriteXMLFile(Page.FPDoc,Filename);
411 end;
412 
413 procedure TWiki2FPDocConverter.SetPackageName(AValue: string);
414 begin
415   if not IsValidIdent(AValue) then
416     raise Exception.Create('invalid package name "'+AValue+'"');
417   if FPackageName=AValue then Exit;
418   FPackageName:=AValue;
419 end;
420 
421 constructor TWiki2FPDocConverter.Create;
422 begin
423   inherited Create;
424   FPageClass:=TW2FPDocPage;
425   FOutputDir:='fpdocxml';
426   FPackageName:='wiki';
427   FRootName:='fpdoc-descriptions';
428 end;
429 
430 procedure TWiki2FPDocConverter.Convert;
431 var
432   i: Integer;
433 begin
434   inherited Convert;
435   // convert
436   for i:=0 to Count-1 do
437     ConvertPage(TW2FPDocPage(Pages[i]));
438   // save
439   for i:=0 to Count-1 do
440     SavePage(TW2FPDocPage(Pages[i]));
441   SaveProject;
442 end;
443 
444 end.
445 
446