Um nur schnell aufgesetzte Papierbriefe sauber formatiert auszudrucken, lohnt sich der Start von OpenOffice kaum. Hartgesottene Programmierer erledigen das lieber mit ihrem Lieblingseditor und fernsteuern Formatierung, Empfängerauswahl und den Druckvorgang mit Perl.
OpenOffice bietet mittlerweile eine echte Alternative zu tradionell Windows-dominierten Office-Produkten. Es ist erstaunlich leicht zu installieren und lässt sich zudem (anders als seine proprietären Kollegen) auch noch einfach von außen manipulieren, da es seine internen Datenstrukten, wie das ``Open'' im Namen verrät, vollständig veröffentlicht.
Das heute vorgestellte Skript mailit arbeitet mit einem einmal
angefertigten OpenOffice-Dokument nach Abbildung 1. Diese Vorlage gibt
stets gleichbleibende Angaben wie den Absender, die Grußformel, und die
gewünschte Formatierung des Briefes vor und setzt für alle
dynamisch eingesetzten Textpassagen folgende Platzhalter ein:
[% date %] # Datum
[% recipient %] # Empfänger-Adresse
[% subject %] # Betreff
[% text %] # Brieftext
|
|
Abbildung 1: Das vorgefertigte Office-Dokument C |
Aufgabe von mailit ist es nun, aus einer reinen Textdatei wie in
Abbildung 2 unter Verwendung der Vorlage einen sauber gesetzten Brief
wie in Abbildung 3 zu generieren. Die Textdatei folgt einem einfachen
absatzorientierten Format: Der erste Absatz enthält die Betreffzeile
des späteren Briefes, alle folgenden geben die Absätze
des Brieftextes an. Das Datum rechts oben im Brief wird dabei
entsprechend dem aktuellen Tagesdatum generiert und entsprechend
der eingestellten Landessprache formatiert.
|
| Abbildung 2: Die Textversion des Briefs im Editor vi. Der erste Absatz gibt den Betreff an, der Rest den Brieftext. |
|
| Abbildung 3: Der fertige Brief in OpenOffice, nachdem Perl die Textpassagen eingearbeitet hat. |
Bei der Implementierung
von mailit kamen sage und schreibe fünf CPAN-Module zum Zug,
deren Einsatzmöglichkeiten üblicherweise viel weiter reichen als nur
für kurze Skripteskapaden.
Zum einen ist da OpenOffice::OODoc, das eine objektorientierte
Schnittstelle auf den Inhalt und die Struktur von OpenOffice-Dokumenten
bereitstellt. Für die Zwecke von mailit, das reine Textersetzung vornimmt,
genügt die Unterklasse OpenOffice::OODoc::Text.
Der Konstruktor new öffnet in Zeile 24 zuerst die angegebene
OpenOffice-Datei, die wie in Abbildung 1 gezeigt ein Template-Dokument
im gewünschten Format mit den Platzhaltern enthält.
Die Methode
getTextElementList() extrahiert daraufhin eine Liste aller
Text-Elemente im Text. Die zurückgegebenen Werte sind allesamt
Referenzen auf Objekte vom Typ XML::XPath::Node::Element, da
OpenOffice::OODoc unter der Haube heftig XML::XPath für die
interne Darstellung der ihrerseits XML-basierten OpenOffice-Dokumente nutzt.
Diese Tatsache muss aber nicht weiter interessieren, denn um zum
Beispiel den Text eines von der Listfunktion gelieferten
Elements $element zuextrahieren, ruft man einfach die
getText()-Methode des OpenOffice::OODoc::Text-Objekts auf
und übergibt die Elementreferenz:
$doc->getText($element);
Den so erhaltenen Text, der typischerweise einen Absatz im
OpenOffice-Dokument repräsentiert, untersucht mailit dann
auf vorkommende Platzhalter im Format [% xxx %] und ersetzt
deren Werte entsprechend der Vorgaben. Das Ergebnis schreibt mailit
anschließend mittels der setText()-Methode
wieder zurück ins Dokument, ebenfalls unter Angabe der Elementreferenz:
$doc->setText($element, $text);
Die Textersetzung nimmt das mächtige Template Toolkit vor, das eigentlich viel mehr kann als nur Platzhaltern Leben einzuhauchen: Es ist das neue IN-Modul für Web-Applikationen, die von Designern gestaltete HTML-Templates mit dynamischen Daten füllen.
Hierzu erzeugt Zeile 50 ein Objekt der Klasse Template. Der ab
Zeile 52 definierte Hash %vars ordnet den Platzhaltern im Dokument
ihre dynamisch zugewiesenen Werte zu.
Die anschließend aufgerufene
process()-Methode des Template-Moduls nimmt in mailit
drei Parameter entgegen:
%vars
Eine Referenz auf eine Funktion, der process() nach erfolgreicher
Textersetzung den Ergebnisstring übergibt.
Letztere ist optional, eignet sich aber in mailit gut dafür, den
bearbeiteten Text gleich per setText() an das OpenOffice-Dokument
weiter zu reichen.
Das so modifizierte OpenOffice-Dokument landet in später in
Zeile 76 per save in einer neuen temporären Datei, die
das Modul File::Temp elegant neu anlegt.
File::Temp ist der Cadillac unter den Tempfile-Modulen, denen es nur
darum geht, temporäre Dateien zu erzeugen, ohne mit bereits bestehenden zu
kollidieren. Der Benutzer darf wählen, in welchem Verzeichnis
die Datei landen soll (/tmp),
welche Endung sie aufweist (.sxw im vorliegenden Fall) und nach
welcher Vorlage der Name generiert wird. TEMPLATE => 'ooXXXXX'
gibt im vorliegenden Fall an, dass nach einem einleitenden
oo (für OpenOffice) fünf zufällige Zeichen stehen, der
komplette Name der Temp-Datei also beispielsweise etwa wie
"/tmp/oo2hkss.sxw" aussieht.
Außerdem bestimmt der UNLINK-Parameter, ob das Modul die
Datei wegputzt, wenn das zugehörige Objekt erlischt. Das zurückgelieferte
Handle lässt sich einfach als File-Handle verwenden und verwandelt
sich innerhalb eines Strings ("$temp") flugs in den Namen der
temporären Datei.
Das bewährte Modul Date::Calc zur Datumsberechnung hilft
mailit, das heutige Datum zu bestimmen und es landestypisch
ins Format XX. Monat, Jahr umzuwandeln. Hierzu setzt es zunächst
das Locale mit
Language(Decode_Language("Deutsch"));
und ruft weiter unten Month_to_Text(), auf um die von der Funktion
Today() zurückgegebenen Monatsnummer in den deutschen Monatsnamen
umzuwandeln.
Zur Bestimmung der mehrzeiligen Empfängeradresse, die den Platzhalter
[% recipient %] im Dokument ersetzt, zieht mailit eine lokal
erstellte Adressdatenbank heran.
Welches Format eignet sich für eine Datei mit bekannten Adressen,
an die mailit Briefe adressieren kann? Computerlesbare Datenformate
gibt es viele, an vorderster Stelle steht XML. Allerdings führen dessen
exzessive Triangel beim menschlichen Leser schnell zu dreieckigen
Augen und Kopfschmerzen.
Das von Brian Ingerson entwickelte YAML (YAML Ain't Markup Language) lässt sich hingegen nicht nur leicht parsen, sondern schmeichelt auch dem Auge des Betrachters. Eine Adressdatenbank, die ihre Datensätze per Kürzel indiziert und ihnen Werte für Name, Straße und Wohnort zuweist, schreibt sich in YAML einfach so:
otto:
- Otto Ollenhauer
- Olle Straße 123
- D-82922 Ottobrunn
bea:
- Beate Ballermann
- Brezenweg 7
- D-93312 Bremen
...
Reicht man den Namen der Datei an YAMLs LoadFile()-Funktion weiter,
gibt diese eine Referenz auf einen Hash zurück, der die Kürzel
als Schlüssel und die Einträge als Referenzen auf Arrays enthält:
{
'bea' => [
'Beate Ballermann',
'Brezenweg 7',
'D-93312 Bremen'
],
'otto' => [
'Otto Ollenhauer',
'Olle Straße 123',
'D-82922 Ottobrunn'
]. ...
}
YAML kann noch viel mehr. Es handelt sich tatsächlich nicht um eine Markup-Sprache, sondern um einen vielseitigen Daten-Serialisierer, der Perls beliebig tief verschachtelte Core-Datenstrukturen samt und sonders in leicht lesbaren ASCII-Text verwandelt und anschließend wieder zurück nach Perl importiert.
Es eignet sich ideal für Konfigurationsdateien, die von Programmen gelesen, aber auch von menschlichen Benutzern studiert und gewartet werden.
|
| Abbildung 4: Das Adressbuch als Textdatei im YAML-Format. |
mailit nimmt die Textversion des Briefs entweder als Dateiname
entgegen oder erwaret ihn auf STDIN: mailit brief.txt und
cat brief.txt | mailit funktionieren gleichermaßen, da
Zeile 29 mit Perls magischer Eingaberaute arbeitet. Der
reguläre Ausdruck in Zeile 33 trennt den ersten Absatz vom Rest des
Briefs und legt die getrennten Bereiche in $subject und $body ab.
Die ab Zeile 82 definierte pick()-Funktion nimmt eine Reihe von
Adressaten als Kürzel entgegen, präsentiert sie dem Benutzer
als durchnumerierte Liste und lässt ihn eine per Nummer auswählen.
Ein typischer Ablauf von mailit sieht folgendermaßen aus:
mailit letter.txt
[1] bea
[2] otto
[3] zephy
Recipient [1]> 1
Preparing letter for Beate Ballermann
Printing /tmp/ooGd8H3.sxw
Um das temporäre Dokument zu drucken, wird in Zeile 71 einfach das
OpenOffice-Binary mit der Option -p aufgerufen. Letztere verhindert
einen langwierigen Start der Oberfläche und schickt statt dessen
die überreichte *.sxw-Datei an den Standard-Drucker. Fertig!
001 #!/usr/bin/perl
002 ###########################################
003 # mailit -- Print letters with OpenOffice
004 # Mike Schilli, 2004 (m@perlmeister.com)
005 ###########################################
006 use warnings;
007 use strict;
008
009 my $CFG_DIR = "$ENV{HOME}/.mailit";
010 my $OO_TEMPLATE = "$CFG_DIR/usa.sxw";
011 my $ADDR_YML_FILE = "$CFG_DIR/addr.yml";
012 my $OO_EXE = "$ENV{HOME}/ooffice/soffice";
013
014 use OpenOffice::OODoc;
015 use Template;
016 use YAML qw(LoadFile);
017 use File::Temp;
018 use Date::Calc qw(Language Decode_Language Date_to_Text
019 Today Month_to_Text);
020
021 Language(Decode_Language("English"));
022 my ($year,$month,$day) = Today();
023
024 my $doc = OpenOffice::OODoc::Text->new(
025 file => $OO_TEMPLATE,
026 );
027
028 # Read from STDIN or file given
029 my $data = join '', <>;
030
031 # Split subject and body
032 my($subject, $body) =
033 ($data =~ /(.*?)\n\n(.*)/s);
034
035 # Remove superfluous blanks
036 my $text;
037 for my $paragraph (split /\n\n/, $body) {
038 $paragraph =~ s/\n/ /g;
039 $text .= "$paragraph\n\n";
040 }
041
042 my $yml = LoadFile($ADDR_YML_FILE);
043 my $nick = pick("Recipient", [keys %$yml]);
044
045 my $recipient = $yml->{$nick};
046
047 print "Preparing letter for ",
048 $recipient->[0], "\n";
049
050 my $template = Template->new();
051
052 my %vars = (
053 recipient => join("\n", @$recipient),
054 subject => $subject,
055 text => $text,
056 #date => sprintf("%d. %s %d",
057 # $day, Month_to_Text($month), $year),
058 date => Date_to_Text($year, $month, $day),
059 );
060
061 for my $e ($doc->getTextElementList()) {
062
063 my $text_element = $doc->getText($e);
064
065 $template->process(\$text_element,
066 \%vars,
067 sub { $doc->setText($e, $_[0]); });
068 }
069
070 my $oo_output = File::Temp->new(
071 TEMPLATE => 'ooXXXXX',
072 DIR => '/tmp',
073 SUFFIX => '.sxw',
074 UNLINK => 1,
075 );
076
077 $doc->save($oo_output->filename);
078
079 print "Printing $oo_output\n";
080 system("$OO_EXE $oo_output");
081
082 ###########################################
083 sub pick {
084 ###########################################
085 my ($prompt, $options) = @_;
086
087 my $count = 0;
088 my %files = ();
089
090 foreach (@$options) {
091 print STDERR "[",
092 ++$count, "] $_\n";
093 $files{$count} = $_;
094 }
095
096 print STDERR "$prompt [1]> ";
097 my $input = <STDIN>;
098 chomp($input);
099
100 $input = 1 unless length($input);
101 return "$files{$input}";
102 }
![]() |
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. |