Wie versprochen kommt heute der POD-Parser, der die gute alte Perl-Dokumentation in das vom Linux-Magazin für Artikel verwendete Quark-Express-Format verwandelt.
Um Artikel für's Linux-Magazin im von Perlprogrammierern bevorzugten Dokumentationsformat POD (Plain Old Documentation) zu schreiben, fügten wir letztes Mal zwei neue Befehle ins POD ein, nannten das Ergebnis flugs PND (Plain New Documentation) und schrieben einen Filter, der PND wieder nach POD wandelte.
Heute fügen wir nun der pod2xxx-Familie ein neues Mitglied hinzu:
pod2quark wandelt das erweiterte POD in ein Format, das die
Redakteure des Linux-Magazins problemlos in ihr Layoutsystem
Quark-Express einspeisen können. Das Format zeichnet den Text mit
speziellen Marken aus, die Überschriften, Listings, Abbildungen etc.
kennzeichnen. Listing 1 zeigt einen in PND geschriebenen
Testartikel, Listing 2 dessen Darstellung nach der Behandlung
mit den Filtern pnd2pod
und und dem heute vorgestellten pod2quark
.
Wie immer dienen die
Zeilennummern nur der Illustration, die echten Dateien führen
natürlich keine in sich.
01 =head1 Artikelüberschrift 02 03 Ein wunderbarer Aufmacher. 04 05 Der erste Textabschnitt befasst sich mit 06 dem wichtigen Perl-Code 07 08 # Wie alles begann: 09 10 perl -le 'print "Hello World!"' 11 12 den jeder Perl-Programmierer im Schlaf 13 herbeten muss ([1] ist ein gutes Buch zum Thema). 14 Abbildung 1 illustriert die Einzelheiten. 15 16 =figure fig/pod.gif Michael I<schläft>. 17 18 =head2 Das Sonderzeichen C<E<lt>> 19 20 Listing 1 zeigt, wie das Kleiner-Zeichen C<E<lt>> 21 und der Backslash C<\> elegant maskiert werden. 22 23 =include maskiere.pl listing 24 25 Das war's für heute, bis zum nächsten Mal! 26 27 =head2 Referenzen 28 29 =over 4 30 31 =item [1] 32 33 "Hello World ohne Ballast", Bernd Brabbel, 34 Salbader-Verlag, 2001 35 36 =back 37 38 =include autorenkasten.pod
01 @T:Artikelüberschrift 02 03 @V:Ein wunderbarer Aufmacher. 04 05 @L:Der erste Textabschnitt befasst sich mit 06 dem wichtigen Perl-Code 07 08 @LI: 09 # Wie alles begann: 10 11 perl -le 'print "Hello World!"' 12 13 @L:den jeder Perl-Programmierer im Schlaf 14 herbeten muss ([1] ist ein gutes Buch zum Thema). 15 Abbildung 1 illustriert die Einzelheiten. 16 17 @Bi:fig/pod.gif 18 @B:Abb. 1: Michael <I>schläft<I>. 19 20 @ZT:Das Sonderzeichen <I><\<><I> 21 22 @L:Listing 1 zeigt, wie das Kleiner-Zeichen <I><\<><I> 23 und der Backslash <I><\\><I> elegant maskiert werden. 24 25 @KT: Listing 1: maskiere.pl 26 @LI: [Hier bitte Listing maskiere.pl einfügen] 27 28 @L:Das war's für heute, bis zum nächsten Mal! 29 30 @ZT:Referenzen 31 32 [1] "Hello World ohne Ballast", Bernd Brabbel, 33 Salbader-Verlag, 2001 34 35 @L:Michael schreibt über Perl und fährt auf 36 der Autobahn schon mal schneller als erlaubt.
Die vom Linux-Magazin verwendeten Layouttags stehen
am Zeilenanfang -- eingeleitet von einem @
-Zeichen und
von einem Doppelpunkt abgeschlossen. Hier sind die wichtigsten:
@T: Artikelüberschrift @V: Der Vorspann @L: Ein Absatz im Text @ZT: Eine Zwischenüberschrift @KT: Ein Kasten (z.B. Listings) @LI: Code eines Listings @Bi: Eine Abbildung @B: Abbildungsunterschrift
Einen Eintrag in der dem Artikel angehängten Literaturliste leitet die in eckige Klammern eingebettete Referenznummer ein:
[1] "Hello World ohne Ballast", Bernd Brabbel, Salbader-Verlag, 2001
Im fließenden Text zeichnet die Tagfolge <I>...<I>
kursiv zu druckenen Text aus. Da @
, \
und <
im Quark-Format
Sonderbedeutung haben, müssen sie, wenn sie wörtlich gemeint sind,
maskiert werden:
@ wird zu <\@> < wird zu <\<> \ wird zu <\\>
Umlaute sind übrigens keine Sonderzeichen und dürfen in Latin-1 vorliegen,
wenn die ursprüngliche POD-Datei in Latin-1 vorliegt.
In der Ausgangssprache POD
sind bekanntlich <
und >
Sonderzeichen und
werden dort, wenn sie wörtlich gemeint sind,
als E<lt>
und E<gt>
geschrieben.
Jedem POD-Tag lässt sich ein entsprechendes Quark-Pendant zuordnen:
Die mit =head1
gekennzeichnete Hauptüberschrift wird zu
@T:
, der erste Absatz des Artikels zum Vorspann (@V:
), alle anderen
Absätze werden mit @L:
ausgezeichnet. Mit =head2
ausgezeichnete
Zwischenüberschriften werden zu @ZT:
. Und der letztes Mal
vorgestellte Filter pnd2pod
erzeugte schon die Mechanik, um
Listings in Kästen zu befördern (@KT:
und @LI:
) und Abbildungen
und deren Beschreibungstexte mit @Bi:
und @B:
auszuzeichnen.
Dieses simple Regelwerk weist jedem POD-Tag ein entsprechendes Pendant im Quark-Format zu -- fragt sich nur, wie man schnellsten den POD-Salat einliest und die Konvertierung vornimmt?
Hierzu gibt's zum Glück schon das standardmäßig mitgelieferte
Modul Pod::Parser
, das eine POD-Datei ansaugt und bei jedem
erkannten POD-Kommando einen Handler anspringt, der dann Transformationen
und Ausgaben vornehmen darf. Pod::Parser
selbst definiert dabei nur
allgemeine Dummy-Handler. Benutzerdefinierte Transformationswerkzeuge
erben dann zwar die die Schnittstelle von Pod::Parser
,
überschreiben die Handler aber mit eigenen, die die notwendigen
Umwandlungen vornehmen und das Ergebnis ausgeben.
Listing 3 zeigt die Implementierung des Transferskripts pod2quark
, das
lediglich das neue Modul Quark
hereinzieht, ein neues Quark
-Objekt
erzeugt und dessen Methode parse_from_file()
aufruft. In Wahrheit
steckt hinter dem weiter unten vorgestellten Quark
-Modul natürlich
Pod::Parser
, dessen Methode parse_from_file()
die
Quark
-Klasse einfach erbt.
Der Aufruf
pod2quark datei.pod
gibt den ins Quark-Format umgewandelten POD-Artikel einfach auf der
Standardausgabe aus. Da Quark.pm
nicht im üblichen Perl-Baum installiert
wurde, sondern unter /u/mschilli/perl-modules
, lässt Zeile
4 in pod2quark
den Interpreter perl
wissen,
dass er auch dort nach eingebundenen
Modulen suchen soll.
1 #!/usr/bin/perl -w 2 3 use strict; 4 use lib '/home/mschilli/perl-modules'; 5 use Quark; 6 7 my $parser = Quark->new(); 8 $parser->parse_from_file ($ARGV[0]);
Listing 4 zeigt mit Quark.pm
die eigentliche Implementierung des
heute vorgestellten Werkzeugs. Zeile 8 stellt Pod::Parser
in den
mit our
global deklarierten @ISA
-Array und weist Quark
damit
als von Pod::Parser
abgeleitete Klasse aus.
Quark.pm
überschreibt vier Methoden der Basisklasse Pod::Parser
,
deren Funktion in den folgenden Abschnitten besprochen wird.
001 ################################################## 002 package Quark; 003 ################################################## 004 005 use Pod::Parser; 006 use constant MAX_VERBATIM_LEN => 35; 007 008 our @ISA = qw(Pod::Parser); 009 010 ################################################## 011 sub initialize { 012 ################################################## 013 my ($parser) = @_; 014 015 $parser->{Intro} = 1; 016 } 017 018 ################################################## 019 sub command { 020 ################################################## 021 my ($parser, $command, $para, $line) = @_; 022 023 my $tag = ""; 024 my $quark = 0; 025 026 $parser->{InVerbatim} = 0; 027 028 return if $command eq 'pod'; 029 return if $command eq 'over'; 030 031 if ($command eq 'head1') { 032 $tag = '@T:'; 033 } elsif ($command eq 'head2') { 034 $tag = '@ZT:'; 035 } elsif ($command eq 'item') { 036 $para =~ s/\s+$//s; 037 $parser->{Item} = $para; 038 return; 039 } elsif ($command eq 'for') { 040 return unless $para =~ /^quark/; 041 $quark = 1; 042 $para =~ s/^quark\s*\n//; 043 } 044 045 my $text = $parser->interpolate($para, $line); 046 #$text = quark_esc($text, '@\\') unless $quark; 047 $parser->output("$tag$text"); 048 } 049 050 ################################################## 051 sub verbatim { 052 ################################################## 053 my($parser, $text) = @_; 054 055 return if $text =~ /^\s*$/s; 056 057 # Zeilenlänge prüfen 058 while($text =~ /(.*)/g) { 059 if(length($1) > MAX_VERBATIM_LEN) { 060 warn "Verbatim line too long: $1"; 061 } 062 } 063 064 #$text = quark_esc($text, '@\\'); 065 $text =~ s/^ {4}//mg; 066 067 # Absatz im Verbatim-Stück? 068 if($parser->{InVerbatim}) { 069 $parser->output("$text"); 070 return 1; 071 } 072 073 $parser->{InVerbatim} = 1; 074 075 $parser->output("\@LI:\n$text"); 076 } 077 078 ################################################## 079 sub interior_sequence { 080 ################################################## 081 my ($parser, $cmd, $arg) = @_; 082 083 # B<.>, I<.> wird <I>.<I> 084 if($cmd =~ /B|I/) { 085 return "<I>$arg<I>"; 086 } 087 088 # C<.> wird <C>.<C> 089 if($cmd =~ /C/) { 090 return "<C>$arg<C>"; 091 } 092 093 # E<.> Notierung für Sonderzeichen in POD 094 if($cmd eq "E") { 095 $arg eq "gt" && return ">"; 096 $arg eq "lt" && return "<"; 097 } 098 } 099 100 ################################################## 101 sub textblock { 102 ################################################## 103 my ($parser, $para, $line) = @_; 104 105 my $tag = ""; 106 107 if(exists $parser->{Item}) { 108 $para = "$parser->{Item} $para"; 109 delete $parser->{Item}; 110 } else { 111 $tag = '@L:'; 112 } 113 114 my $text = $parser->interpolate($para, $line); 115 #$text = quark_esc($text, '@\\'); 116 117 118 if($parser->{Intro}) { 119 $tag = "\@V:"; 120 $parser->{Intro} = 0; 121 } 122 123 $parser->{InVerbatim} = 0; 124 $parser->output("$tag$text"); 125 } 126 127 ################################################## 128 sub output { 129 ################################################## 130 my($parser, $text) = @_; 131 132 my $out_fh = $parser->output_handle; 133 print $out_fh $text; 134 } 135 136 ################################################## 137 sub quark_esc { 138 ################################################## 139 my ($string, $chars) = @_; 140 141 # Backslash drin oder nicht? 142 my $backslash = ($chars =~ s-\\--); 143 144 # Alles ausser Backslashes ersetzen 145 $string =~ s-([$chars])-<\\$1>-g; 146 147 if($backslash) { 148 # Sonderbehandlung 149 $string =~ s-(?<!<)(\\)-<\\$1>-g; 150 } 151 152 return $string; 153 }
initialize()
ab Zeile 11
springt der Parser zu Beginn des Parse-Vorgangs an.
Quark.pm
setzt hier nur die frech hinzudefinierte neue Instanzvariable
Intro
, die später helfen wird, festzustellen, ob der aktuelle
Absatz der Aufmacher oder einer der folgenden Absätze ist.
command()
ab Zeile 19 ruft der Parser jedes Mal auf, wenn er
auf ein mit =
eingeleitetes POD-Kommando (wie z.B. =head1
)
stößt. Vier Parameter liegen dann vor: $parser
als eine Referenz auf
das Parser-Objekt, $command
als der Name des Kommandos
(z.B. head1
), $para
als der dem Kommando beigefügte Textabsatz
(z.B. der Text der Überschrift) und die Zeilennummer $line
in der ursprünglichen POD-Datei.
Die Zeilen 28 und 29 ignorieren die Kommandos =pod
und =over
,
die pod2quark
nicht bearbeitet. Das if
-Konstrukt ab Zeile 31
nimmt für die Kommandos =head1
, =head2
, =item
und =for
die
richtigen Aktionen vor. Für =item
-Kommandos, die zur Auflistung
von Literaturverweisen dienen, speichert Zeile 37 den übergebenen
String (im allgemeinen eine Referenzennummer im Format [1]
) in
der Instanzvariablen Item
des Parser-Objekts. Vorher löscht es
mit s/\s+$//s
abschließende Leerzeilen aus dem String, da der Parser die
Angewohnheit hat, nicht nur den =item
übergebenen String zu melden,
sondern auch noch zwei Zeilenumbrüche. Wegen des Modifizierers /s
(für single line) des regulären Ausdrucks passen
auf /\s+$/
eine beliebige Anzahl von Leerzeichen und Zeilenumbrüchen
am Ende eines mehrzeiligen Strings.
Im Fall von =for quark
-Anweisungen
(alle anderen =for
s ignoriert Quark.pm
geflissentlich)
löscht es den quark
-String, übernimmt aber den Rest des
Absatzes -- wo unser letztes Mal gezeigtes
pnd2pod
vorher bereits fertige Quark-Kommandos
für Listings und Abbildungen abgelegt hat.
Zeile 45 ruft die Methode interpolate()
des Parsers auf, um die
im Text der erfassten Überschrift (oder auch Bildunterschrift)
vorkommenden POD-Macros
wie C<...>
oder I<...>
mittels der
weiter unten definierten Methode interior_sequence()
zu bearbeiten,
in entsprechende Quark-Sequenzen umzusetzen und das interpolierte
Ergebnis zurückzugeben.
Zeile 46 maskiert mittels der ab Zeile 131 definierten Funktion
quark_esc()
die Zeichen @
und \
durch
<\@>
und <\\>
. Die Funktion
quark_esc()
nimmt einen Textstring
entgegen und einen zweiten String, der die im Textstring
zu ersetzenden Sonderzeichen
enthält. Der Backslash muss in Perl bekanntlich auch in einfachen
Anführungszeichen maskiert werden.
Das Sonderzeichen <
wird in command()
nicht ersetzt,
da diese Aufgabe schon das darunterliegende interior_sequence()
erledigt hat. Mehr dazu später.
Die in Zeile 122 definierte output()
-Methode ist tatsächlich eine
Methode der Klasse Quark
und nicht etwa eine ursprünglich
von Pod::Parser
stammende.
Sie ist ab Zeile 122
definiert. Ihre Aufgabe besteht lediglich darin, den ihr übergebenen
Text mit print()
auf das
Ausgabe-File-Handle des Parsers schreiben, das die Methode
output_handle()
liefert.
Für die in POD eingerückten Abschnitte (meist bereits fertig formatierte
Kurzlistings im Fließtext),
ruft der Parser jeweils die verbatim()
-Methode mit
dem Parser-Objekt $parser
und dem Listingstext auf, der in
$text
vorliegt. Besteht letzterer nur aus Leerzeilen, kehrt Zeile
55 rasch wieder zurück. Da der Spaltensatz des Linux-Magazins die
Zeilenlänge von Kurzlistings auf ca. 35 Zeichen beschränkt, gibt
Zeile 60 für jede längere Zeile eine kurze Warnung aus, so dass
der Autor diese gemäß den Empfehlungen in [1] im Unix-Format
umbrechen kann. In den Text eingebettete Listings sollten sich
bekanntlich nie über mehr als ein paar Zeilen hinziehen, für längere
und benamte Listings steht das =include
-Tag bereit.
Letzteres veranlasst pod2quark
, im fertigen
Manuskript lediglich den
Namen des Listings mit den notwendigen Formatierungsangaben
zu erwähnen und das ganze in einen Kasten zu stecken.
Der Autor formatiert dann die Listings und packt sie extern bei,
worauf der Setzer sie kunstvoll um die Textspalten gruppiert.
Zeile 64 maskiert daraufhin die Sonderzeichen
@
, \
und <
im Listing gemäß den Quark-Richtlinien
und Zeile 75 gibt den Verbatim-Absatz mit dem vorangestellten
Tag @LI:
aus. Zeile 65 entfernt die zum Einrücken verwendeten
Leerzeichen.
Leerzeilen in eingerückten Verbatim-Absätzen veranlassen
den POD-Parser leider, jedesmal von neuem die verbatim
-Methode
aufzurufen. Damit dies nicht jedesmal ein @LI:
-Tag zur Folge hat,
merkt sich der Parser die Tatsache, dass er sich in einem Verbatim-Absatz
befindet in der Instanzvariablen InVerbatim
. Schlägt die Variable
an, hängt Zeile 69 den gegenwärten Verbatim-Absatz einfach an die
Ausgabe an, ohne ein weiteres @LI:
-Tag auszuschreiben. Im nächsten
Text- oder Verbatimblock,
den die weiter unten beschriebenen
textblock()
/verbatim()
-Methoden erfassen,
wird InVerbatim
wieder auf 0
zurückgesetzt.
Die ab Zeile 79 definierte Methode interior_sequence()
ruft der
Parser jedesmal auf, wenn er auf eine von PODs Inline-Text-Auszeichnungen
wie E<gt>
oder I<...>
stößt. In $cmd
liegt
dann der Kommandoname (z.B. E
oder I
) und in $arg
der eingeschlossene
Text vor. Zeile 84 sucht nach den Kommandos B
, I
oder C
, die
pod2quark
allesamt mit <I>...<I>
in kursiven
Text transformiert.
Ab Zeile 89 wird's haarig: An einem mit E<gt>
in PND kodierten
Sonderzeichen >
nimmt Quark keinen Anstoß, also
kann aus E<gt>
gleich >
werden, aber
E<lt>
ist ein Sonderzeichen, das in Quark als
<\<>
notiert, was Zeile 90 erledigt, indem es
quark_esc()
auffordert, im String '<'
nur das Zeichen
<
quark-sicher zu maskieren.
Zeile 95 definiert die Methode textblock()
die der Parser für jeden
normalen Textabsatz ohne besondere Auszeichnung
aufruft. Der Text liegt in $para
.
Wenn von einem vorausgehenden =item
-Kommando her die Instanzvariable
Item
des Parsers gesetzt ist, verzweigt Zeile 101 in den if
-Block,
der den gespeicherten =item
-Text vor den Text des Absatzes einfügt,
damit die Literaturverweise ordnungsgemäß aufgelistet werden, das
@L:
-Tag entfällt in diesem Fall.
Die in Zeile 108 aufgerufene interpolate()
-Methode ruft für die
zu ersetzenden
Inline-Tags wieder die interior_sequence()
-Methode auf und nimmt
die notwendigen Transformationen vor. Das anschließend aufgerufene
quark_esc()
ersetzt nur die Sonderzeichen
@
und \
, da etwa auftretende <
-Zeichen schon von der
interior_sequence()
-Methode erschlagen wurden.
quark_esc()
ersetzt, wie schon erwähnt, die ihm im zweiten
String übergebenen gefährlichen Sonderzeichen durch Quark-sichere
-- es bedient sich jedoch eines kleinen Kniffs:
Es ersetzt Backslashes nur dann, falls diese nicht als
<\
vorkommen und für bereits maskierte <
-Zeichen stehen.
Der Grund dafür: Zeile 109 muss aus einem
bereits interpolierten Textabsatz unter anderem die wörtlich
vorkommenden Backslashes maskieren. Da die Interpolationsfunktion
dort aber schon <\<>
-Konstrukte abgelegt hat,
wäre es falsch, deren Backslashes nochmals zu maskieren.
Deswegen entfernt Zeile 136 in quark_esc()
eventuell vorkommende Backslashes
aus dem Skalar $chars
, der die zu ersetzenden Zeichen erhält.
Die Variable $backslash
fängt das Ergebnis ab, und erhält
einen wahren Wert, falls sich in $chars
vorher ein Backslash befand
und einen falschen Wert, falls nicht. Statt des sonst üblichen
/
-Zeichens, um das Suchmuster im Substitutionsbefehl vom
Ersetzungsmuster zu trennen, nutzen die regulären Ausdrücke
in quark_esc()
das Zeichen -
, um Backslashitis-Schüben
bei empfindlichen Perl-Programmierern vorzubeugen.
Zeile 139 ersetzt dann alle in $chars
angegebenen Zeichen
außer dem Backslash durch quark-sicherne Maskierungen.
Zeile 143 ersetzt Backslashes anschließend nur,
falls $chars
dies vorher verlangte und dann auch nur dann, falls
es kein Backslash in <\
ist, was das Suchmuster
/(?<!<)(\\)/
einfordert. (\\)
passt dabei auf einen
einfachen Backslash, der negative Look-Behind (?<!)
(neu mit Perl 5.6.0) davor stellt sicher, dass kein <
davorsteht.
Puh!
Das neue
Quark.pm
muss in einen Pfad, in dem perl
es auch findet. Gerne
darf pod2quark
auch, wie vorgestellt, mit use lib
auf einen
nicht standardisierten Pfad verweisen. pod2quark
sollte irgendwo
stehen, wo die Shell es findet, also irgendwo im $PATH
.
Aus einem mit PND geschriebenen Artikel wird also mit
pnd2pod text.pnd pod2quark text.pod >text.qex
einer für's Linux-Magazin.
Zu beachten ist, dass die mit =include
und
=figure
eingebundenen Listings und Abbildungen nicht direkt
im Text enthalten sind, sondern dem Artikel in einem Tar-
oder Zip-Archiv beiliegen sollten.
Schreibt ein, zwei, viele Artikel für's Linux-Magazin im PND-Format
und konvertiert sie zur Freude aller Redakteure gleich ins
richtige Layoutformat!
perldoc perlpod
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |