Author: Jim McKeeth
It was asked for an easy way to export all types of source code to HTML. The Open
Source SynEdit components provide this functionality. Using those I created a
simple utility to allow for command line driven exporting of most source code to
both HTML and RTF formats.
Answer:
1 {
2 Syntax Highlighted Source Code Export to HTML or RTF
3 Written and (c) 2002 by Jim McKeeth jim@bsdg.org
4
5 Pascal, Borland Dfm, HTML, CSS, HC11, ADSP21xx, AWK, Baan, Cache,
6 CAC, CPM, Fortran, Foxpro, Galaxy, Dml, General, GWScript, HP48, INI, Inno, Java,
7 JScript, Kix, Modelica, M3, VBScript, Bat, Perl, PHP, Progress, SDD, SQL, SML,
8 TclTk, VB, Asm, Cpp, Python to HTML or RTF with end user customization.
9 \Uses the open source SynEdit component suite.
10
11 It was asked for an easy way to export all types of source code to HTML. The Open
12 Source SynEdit components provide this functionality. Using those I created a
13 simple utility to allow for command line driven exporting of most source code to
14 both HTML and RTF formats.
15
16 Note, this was written in Delphi 6 but should work with C++ Builder 3 or better,
17 Delphi 3 or better or Kylix with only minimal changes.
18
19 First rule when developing with Delphi: No need to reinvent the wheel. Sure, I
20 could have come up with my own routines to format source code to HTML, but why when
21 SynEdit is freely available and works great. Before you start, you will need to
22 download and install the SynEdit suite of components from
23 http://synedit.sourceforge.net/ .
24
25 There are three main parts to this: Parse the command-line parameters; Verify the
26 parameters; Format the source code.
27
28 Here is the unit header along with a list of internal supported highlighters.
29 }
30 unit ExportUnit;
31
32 { Internal supported highlighter keywords
33 Pas
34 Dfm
35 HTML
36 Css
37 HC11
38 ADSP21xx
39 AWK
40 Baan
41 Cache
42 CAC
43 CPM
44 Fortran
45 Foxpro
46 Galaxy
47 Dml
48 General
49 GWScript
50 HP48
51 Ini
52 Inno
53 Java
54 JScript
55 Kix
56 Modelica
57 M3
58 VBScript
59 Bat
60 Perl
61 PHP
62 Progress
63 SDD
64 SQL
65 SML
66 TclTk
67 VB
68 Asm
69 Cpp
70 Python
71 }
72
73 interface
74
75 uses
76 Windows, Messages, SysUtils, Variants, Classes, Dialogs, Controls, Forms,
77 SynHighlighterAsm, SynHighlighterVB, SynHighlighterTclTk, SynHighlighterSml,
78 SynHighlighterSQL, SynHighlighterSDD, SynHighlighterPython,
79 SynHighlighterProgress,
80 SynHighlighterPHP, SynHighlighterPerl, SynHighlighterBat,
81 SynHighlighterVBScript,
82 SynHighlighterM3, SynHighlighterModelica, SynHighlighterKix,
83 SynHighlighterJScript,
84 SynHighlighterJava, SynHighlighterInno, SynHighlighterIni, SynHighlighterHtml,
85 SynHighlighterHP48, SynHighlighterGWS, SynHighlighterGeneral, SynHighlighterDml,
86 SynHighlighterGalaxy, SynHighlighterFoxpro, SynHighlighterFortran,
87 SynHighlighterDfm, SynHighlighterCPM, SynHighlighterCss, SynHighlighterCAC,
88 SynHighlighterCache, SynHighlighterCpp, SynHighlighterBaan, SynHighlighterAWK,
89 SynHighlighterADSP21xx, SynHighlighterHC11, SynEditHighlighter,
90 SynHighlighterPas,
91 SynExportRTF, SynEditExport, SynExportHTML, SynHighlighterMulti, StdCtrls,
92 ExtCtrls;
93
94 {
95 First we need to setup the form. I simple have a large TMemo called memoLog that
96 is set to client justified. Now we add the SynEdit components we need.
97 Simply add one TsynExporterHTML and one TsynExporterRTF from the SynEdit tab.
98 Rename them ExporterHTML and ExporterRTF respeively. Now add the
99 TsynHighlightManager. When you add this component it brings up a dialog allowing
100 you to choose which Highlighters to add. Simple click "Select All" and "Ok" to add
101 one of each. Leave the names as the defaults.
102 }
103
104 type
105 TformSynEdit = class(TForm)
106 ExporterHTML: TSynExporterHTML;
107 ExporterRTF: TSynExporterRTF;
108 memoLog: TMemo;
109 SynHC11Syn1: TSynHC11Syn;
110 SynADSP21xxSyn1: TSynADSP21xxSyn;
111 SynAWKSyn1: TSynAWKSyn;
112 SynBaanSyn1: TSynBaanSyn;
113 SynCppSyn1: TSynCppSyn;
114 SynCacheSyn1: TSynCacheSyn;
115 SynCACSyn1: TSynCACSyn;
116 SynCssSyn1: TSynCssSyn;
117 SynCPMSyn1: TSynCPMSyn;
118 SynDfmSyn1: TSynDfmSyn;
119 SynFortranSyn1: TSynFortranSyn;
120 SynFoxproSyn1: TSynFoxproSyn;
121 SynGalaxySyn1: TSynGalaxySyn;
122 SynDmlSyn1: TSynDmlSyn;
123 SynGeneralSyn1: TSynGeneralSyn;
124 SynGWScriptSyn1: TSynGWScriptSyn;
125 SynHP48Syn1: TSynHP48Syn;
126 SynHTMLSyn1: TSynHTMLSyn;
127 SynIniSyn1: TSynIniSyn;
128 SynInnoSyn1: TSynInnoSyn;
129 SynJavaSyn1: TSynJavaSyn;
130 SynJScriptSyn1: TSynJScriptSyn;
131 SynKixSyn1: TSynKixSyn;
132 SynModelicaSyn1: TSynModelicaSyn;
133 SynM3Syn1: TSynM3Syn;
134 SynVBScriptSyn1: TSynVBScriptSyn;
135 SynBatSyn1: TSynBatSyn;
136 SynPasSyn1: TSynPasSyn;
137 SynPerlSyn1: TSynPerlSyn;
138 SynPHPSyn1: TSynPHPSyn;
139 SynProgressSyn1: TSynProgressSyn;
140 SynPythonSyn1: TSynPythonSyn;
141 SynSDDSyn1: TSynSDDSyn;
142 SynSQLSyn1: TSynSQLSyn;
143 SynSMLSyn1: TSynSMLSyn;
144 SynTclTkSyn1: TSynTclTkSyn;
145 SynVBSyn1: TSynVBSyn;
146 SynAsmSyn1: TSynAsmSyn;
147 procedure PerformHighlight(const sInFile, sOutFile: string;
148 sceHighlighter: TSynCustomExporter);
149 procedure VerifyParameters(const sInFile: string; sOutFile: string = '';
150 sHighlighter: string = '');
151 procedure ParseParameters;
152 procedure FormShow(Sender: TObject);
153 private
154 { Private declarations }
155 public
156 { Public declarations }
157 procedure Log(s: string);
158
159 end;
160
161 var
162 formSynEdit: TformSynEdit;
163
164 {These are some simple string parsing rontines that wrote a long time ago and
165 use in many of my new programs.}
166 function pright(const s, divisor: string): string;
167 function pleft(const s, divisor: string): string;
168 function ReverseStr(const s: string): string;
169 function ValueOf(const S: string): string;
170 function NameOf(const S: string): string;
171
172 implementation
173
174 {$R *.dfm}
175
176 { I have a method for adding lines to he memo called log.
177 I use the ~ or character #126 for line breaks.}
178
179 procedure TformSynEdit.Log(s: string);
180 begin
181 memoLog.Lines.Add(StringReplace(s, #126, #13#10, [rfReplaceAll]));
182 end;
183
184 {Returns the portion of the string left of the divisor}
185
186 function pleft(const s, divisor: string): string;
187 begin
188 if pos(divisor, s) > 0 then
189 result := copy(s, 1, pos(divisor, s) - 1)
190 else
191 result := s;
192 end;
193
194 {Returns string in reverse}
195
196 function ReverseStr(const s: string): string;
197 var
198 ctr: Integer;
199 s2: string;
200 begin
201 s2 := s;
202 for ctr := 1 to length(s) do
203 s2[length(s) - ctr + 1] := s[ctr];
204 result := s2;
205 end;
206
207 {Returns the portion of the string right of the divisor}
208
209 function pright(const s, divisor: string): string;
210 var
211 rs: string;
212 begin
213 rs := ReverseStr(s);
214 result := ReverseStr(PLeft(rs, reverseStr(divisor)));
215 end;
216
217 {Returns the portion of the string right of the '='}
218
219 function ValueOf(const s: string): string;
220 begin
221 result := pright(s, pleft(s, '=') + '=');
222 end;
223
224 {Returns the portion of the string left of the '='}
225
226 function NameOf(const s: string): string;
227 begin
228 result := pleft(s, '=');
229 end;
230
231 {This is called from the VerifyParamters routine to find a matching highlighter
232 based on the extension of the input file. It works by seperating each extension of
233 the filter as a item in a string list and then look to see if the specified
234 extension is in the list.}
235
236 function FilterMatch(sExt, sFilter: string): Boolean;
237 var
238 slExts: TStringList;
239 begin
240 slExts := TStringList.Create;
241 try
242 slExts.Delimiter := ';';
243 slExts.DelimitedText := pright(sFilter, '|');
244 Result := slExts.indexof('*' + sExt) > -1;
245 finally
246 slExts.Free;
247 end;
248 end;
249
250 {This routine is not currently used, but was used to save some template
251 highlighters to disk.}
252 {
253 function ComponentToFile(Component: TComponent; const sFileName: string)
254 : boolean;
255 var
256 BinStream: TMemoryStream;
257 FileStream: TFileStream;
258 begin
259 BinStream := TMemoryStream.Create;
260 try
261 FileStream := TFileStream.Create(sFileName, fmCreate or fmShareExclusive);
262 try
263 BinStream.WriteComponent(Component); // write the component to the stream
264 BinStream.Seek(0, soFromBeginning); // seek to the origin of the stream
265 // convert the binary representation of the component to easily editable
266 // text format and save it to a FileStream
267 ObjectBinaryToText(BinStream, FileStream);
268 Result:= True;
269 finally
270 FileStream.Free;
271 end;
272 finally
273 BinStream.Free
274 end;
275 end;
276 }
277
278 {This is the routine used to load the external highlighter as a component.}
279
280 function FileToComponent(sFileName: string): TComponent;
281 var
282 FileStream: TFileStream;
283 BinStream: TMemoryStream;
284 begin
285 FileStream := TFileStream.Create(sFileName, fmOpenRead or fmShareExclusive);
286 try
287 BinStream := TMemoryStream.Create;
288 try
289 // convert the user editable text format to binary
290 ObjectTextToBinary(FileStream, BinStream);
291 BinStream.Seek(0, soFromBeginning); // seek to the origin of the stream
292 // create the component from the stream
293 Result := BinStream.ReadComponent(nil);
294 finally
295 BinStream.Free;
296 end;
297 finally
298 FileStream.Free;
299 end;
300 end;
301
302 {-- Parsing command-line parameters --
303
304 We accept up to three parameters, but only require one.
305 Here is the usage statement:
306
307 Command-line usage:
308 > SrcFormat IN=(Input File) [OUT=(Output File)] [HIGHLIGHTER=(Highligher Name)]
309
310 Where
311 > IN=(Input File) is the required input file.
312 Example: 'In="C:\My Documents\Source\SrcFormat.dpr"'
313 > OUT=(Output File) is the optional output file.
314 Format is based on extension HTML or RTF.
315 Default is same file name and path with an additional '.HTM'.
316 Example: 'Out=C:\Source.RTF'
317 > HIGHLIGHTER=(Highlighter Name) is the optional Highlighter to use.
318 If not provided then guessed based on extension.
319 Can also be the file name of a saved Highlighter.
320 Example: 'Highlighter=Pas'
321 Example: 'Highlighter="C:\My Documents\Highlighters\MyPascal.hi"'
322
323 We only require that they specify the input file, and in fact if they only
324 specify a single parameter, even without the "IN=" prefix, then we assume it is
325 the input file. We attempt an educated guess on the rest.
326
327 Here is the code we can use to parse the command-line parameters:}
328
329 procedure TformSynEdit.ParseParameters;
330 var
331 sInFile, sOutFile, sHighlighter: string;
332 iCtr: Integer;
333 begin
334 if (ParamCount = 1) and (FileExists(ParamStr(1))) then
335 sInFile := ParamStr(1) // if only one then it is the input file
336 else if ParamCount > 0 then
337 for iCtr := 1 to ParamCount do // spin though the parameters
338 begin
339 if CompareText(NameOf(ParamStr(iCtr)), 'IN') = 0 then
340 sInFile := ValueOf(ParamStr(iCtr)) // Input file
341 else if CompareText(NameOf(ParamStr(iCtr)), 'OUT') = 0 then
342 sOutFile := ValueOf(ParamStr(iCtr)) // Output file
343 else if CompareText(NameOf(ParamStr(iCtr)), 'HIGHLIGHTER') = 0 then
344 sHighlighter := ValueOf(ParamStr(iCtr)) // highlighter
345 end
346 else
347 begin // explain the usage
348 Log('Command-line usage: '#126 +
349 '> SrcFormat IN=(Input File) [OUT=(Output File)] ' +
350 '[HIGHLIGHTER=(Highligher Name)]' + #126 + #126 +
351 'Where' + #126 +
352 '> IN=(Input File) is the required input file. ' + #126 +
353 ' Example: ''In="C:\My Documents\Source\SrcFormat.dpr"''' + #126 +
354 '> OUT=(Output File) is the optional output file. ' + #126 +
355 ' Format is based on extension HTML or RTF.' + #126 +
356 ' Default is same file name and path with an additional ''.HTM''.'
357 + #126 +
358 ' Example: ''Out=C:\Source.RTF''' + #126 +
359 '> HIGHLIGHTER=(Highlighter Name) is the Highlighter to use.'
360 + #126 +
361 ' If not provided then guessed based on extension. ' + #126 +
362 ' Can also be the file name of a saved Highlighter.' + #126 +
363 ' Example: ''Highlighter=Pas''' + #126 +
364 ' Example: ''Highlighter="C:\My Documents\SrcExport\MyPascal.hi"'''
365 );
366 Exit;
367 end;
368
369 // Finally we pass all the variables to the VerifyParameters routine.
370 VerifyParameters(sInFile, sOutFile, sHighlighter);
371 end;
372
373 {You could actually call this routine from a GUI interface as well as the
374 ParseParameters method, but I will let you add that functionality. I'll step
375 you through each section of this routine.}
376
377 procedure TformSynEdit.VerifyParameters(const sInFile: string; sOutFile,
378 sHighlighter: string);
379 var
380 sInExt, sOutExt: string;
381 myExporter: TSynCustomExporter;
382 iCtr: Integer;
383 begin
384 {First verify that the input file does exist. We cannot format something that
385 has not been saved to disk yet (although that would make a great Delphi Expert!)
386 We simply add a log line and exit if the file is non-existent.}
387 if not FileExists(sInFile) then
388 begin
389 Log('The input file "' + sInFile + '" does not exist');
390 Exit;
391 end;
392
393 {If they did not specify an output file then we append an 'HTM' extension.}
394 if sOutFile = '' then
395 sOutFile := sInFile + '.HTM';
396
397 {Make sure the output file does not exist.}
398 if FileExists(sOutFile) then
399 try
400 DeleteFile(sOutFile);
401 except
402 log('Output file exists and cannot be deleted');
403 Exit;
404 end;
405
406 {Make sure we can create the output file.}
407 try
408 // Make sure we can create the path
409 ForceDirectories(ExtractFilePath(sOutFile));
410 // Create and close a test file
411 FileClose(FileCreate(sOutFile));
412 except
413 log('Cannot create output file!');
414 Exit;
415 end;
416
417 {Extract the extensions of the files for guessing the highlighter and format.}
418 sInExt := UpperCase(ExtractFileExt(sInFile));
419 sOutExt := UpperCase(ExtractFileExt(sOutFile));
420
421 {Now we guess the export format.
422 If it is not an .RTF extension then we assume HTML.}
423 if sOutExt = '.RTF' then
424 begin
425 log('Exporting to RTF');
426 myExporter := ExporterRTF;
427 end
428 else
429 begin
430 log('Exporting to HTML');
431 myExporter := ExporterHTML;
432 end;
433
434 {Now we guess the highlighter. To do this we will spin through all the
435 DefaultFilter properties of the highlighters we included on the form. Since we
436 stop on the first match, you may want to change the creation order (by right
437 clicking on the form) to put your most common highlighters first. }
438 myExporter.Highlighter := nil;
439
440 // only do with if no highlighter was specified at the command-line
441 if sHighlighter = '' then
442 begin
443 for iCtr := 0 to pred(ComponentCount) do // go through all the componets
444 // only look at highlighters
445 if Components[iCtr] is TSynCustomHighlighter then
446 // use the filter match method to see if the extension matches the filter
447 if FilterMatch(sInExt,
448 (Components[iCtr] as TSynCustomHighlighter).DefaultFilter) then
449 begin
450 // Set the name of the highlighter as the meaningful part of the
451 // component name.
452 sHighlighter := Copy(Components[iCtr].Name, 4,
453 Length(Components[iCtr].Name) - 7);
454 // set the actual highlighter property of the exporter
455 myExporter.Highlighter := Components[iCtr] as TSynCustomHighlighter;
456 // no more looping, we have what we want.
457 Break;
458 end;
459 end;
460
461 if sHighlighter = '' then
462 begin // we didn't find an internal one, but we might find an external one.
463 log('No highlighter was found for the extension ' + sInExt);
464 end;
465
466 // if they specified a highlighter at the command line find it now.
467 if (myExporter.Highlighter = nil) and (sHighlighter <> '') then
468 for iCtr := 0 to pred(ComponentCount) do
469 if Components[iCtr] is TSynCustomHighlighter then
470 if CompareText(Components[iCtr].Name, 'Syn' + sHighlighter + 'Syn1') = 0
471 then
472 begin
473 myExporter.Highlighter := Components[iCtr] as TSynCustomHighlighter;
474 Break;
475 end;
476
477 // we still don't have a highlighter but one was specified
478 if (myExporter.Highlighter = nil) and (sHighlighter <> '') then
479 begin
480 log('No internal highlighter named ''' + sHighlighter + ''' found!');
481 // but there is a file with the same name as the specified highlighter,
482 // maybe it is an external highlighter!
483 if FileExists(sHighlighter) then
484 begin
485 log('Loading highlighter: ' + sHighlighter);
486 // before you can load a component you need to register the class.
487 RegisterClasses([TSynExporterHTML, TSynExporterRTF, TSynPasSyn,
488 TSynDfmSyn, TSynHTMLSyn, TSynCssSyn, TSynHC11Syn, TSynADSP21xxSyn,
489 TSynAWKSyn, TSynBaanSyn, TSynCacheSyn, TSynCACSyn, TSynCPMSyn,
490 TSynFortranSyn, TSynFoxproSyn, TSynGalaxySyn, TSynDmlSyn,
491 TSynGeneralSyn, TSynGWScriptSyn, TSynHP48Syn, TSynIniSyn, TSynInnoSyn,
492 TSynJavaSyn, TSynJScriptSyn, TSynKixSyn, TSynModelicaSyn, TSynM3Syn,
493 TSynVBScriptSyn, TSynBatSyn, TSynPerlSyn, TSynPHPSyn, TSynProgressSyn,
494 TSynSDDSyn, TSynSQLSyn, TSynSMLSyn, TSynTclTkSyn, TSynVBSyn,
495 TSynAsmSyn, TSynCppSyn, TSynPythonSyn, TSynPasSyn]);
496 try // try to load the component
497 myExporter.Highlighter :=
498 FileToComponent(sHighlighter) as TSynCustomHighlighter;
499 except
500 // failed to load the component, it must have been invalid!
501 log('External highlighter named ''' + sHighlighter + ''' is inavlid!');
502 Exit;
503 end;
504 end
505 else
506 begin
507 log('No external highlighter named ''' + sHighlighter + ''' found!');
508 Exit;
509 end;
510 end;
511
512 // Note: if not highlighter was specifed, and none can be found based on
513 // extension then we can export without a highlighter which results in
514 // no syntax highlighting, but does change the format.
515
516 // nothing caused us to exit along the way, we must have everything we need.
517 // list it out in the log window
518 Log('Intput file: ' + sInFile + #126 +
519 'Output file: ' + sOutFile + #126 +
520 'Highlighter: ' + sHighlighter);
521
522 // Now we call Perform Highlight with the final parameters.
523 PerformHighlight(sInFile, sOutFile, myExporter);
524 end;
525
526 { After all that work, all we did is protect the actual functionality of
527 this program from the user. We should now have valid parameters for this
528 method. }
529
530 procedure TformSynEdit.PerformHighlight(const sInFile, sOutFile: string;
531 sceHighlighter: TSynCustomExporter);
532 var
533 slSrc: TStringList;
534 begin
535 slSrc := TStringList.Create; // to load the source code into
536 try
537 // load the source code from disk
538 slSrc.LoadFromFile(sInFile);
539 sceHighlighter.ExportAsText := True;
540 // Might be a good idea to make this user definable at some point, but for
541 // now we will just us a generic title
542 sceHighlighter.Title := ExtractFileName(sInFile) + ' source code';
543 // Read in the source code and convert it to the highlighter format
544 sceHighlighter.ExportAll(slSrc);
545 // Save the output to disk.
546 sceHighlighter.SaveToFile(sOutFile);
547 finally
548 slSrc.Free; // all done
549 end;
550 end;
551
552 {Now assign an event handler to the Form Show event. You could use a button
553 instead if you wanted. You might also want to close when it is done.}
554
555 procedure TformSynEdit.FormShow(Sender: TObject);
556 begin
557 Application.ProcessMessages; // finish drawing the form
558 ParseParameters; // Get Started.
559 end;
560
561 end.
|