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