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 =fors 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. |