XML::Writer

(Auszug aus "Perl & XML" von Erik T. Ray & Jason McIntosh)

Verglichen mit all den Dingen, mit denen wir uns in diesem Abschnitt bereits beschäftigt haben, ist die Erzeugung eines XML-Dokuments ein Kinderspiel. Es ist einfacher, weil der Schuh jetzt am anderen Fuß sitzt: Diesesmal hat unser Programm bereits eine Datenstruktur, die wir nach Belieben kontrollieren. Wir müssen also nicht auf alle möglichen Unwägbarkeiten vorbereitet sein.

XML zu generieren ist nicht besonders kompliziert. Wir kennen die Elemente mit ihren Start- und End-Tags, die Attribute usw. In gewisser Weise müssen wir lediglich daran denken, die Buchstaben t mit einem Querstrich und die Buchstaben i mit einem Punkt zu versehen. Steht zwischen den Attributen ein Leerzeichen? Werden geöffnete Elemente auch geschlossen? Sind leere Elemente mit einem schließenden Slash versehen? Das sind alles Dinge, mit denen wir uns eigentlich nicht wirklich beschäftigen wollen. Andere haben bereits Module geschrieben, die diese Serialisierungsaufgaben für uns erledigen.

XML::Writer von David Megginson ist ein hervorragendes Beispiel für eine abstrakte Schnittstelle zur Generierung von XML. Das Modul bietet eine Reihe von einfach anwendbaren Methoden zur Erzeugung von XML-Dokumenten. Man erzeugt ein Writer-Objekt und ruft seine Methoden auf, um einen XML-Strom auszugeben. In der folgenden Tabelle finden Sie eine Liste dieser Methoden.

Tabelle: Methoden von XML::Writer

Name Funktion
end( ) Schließt das Dokument und führt eine Wohlgeformtheitsprüfung durch. Zum Beispiel wird geprüft, ob es nur ein einzelnes Wurzelelement gibt und ob alle Start-Tags ein zugehöriges End-Tag besitzen. Falls die Option UNSAFE gesetzt ist, werden diese Prüfungen aber übersprungen.
xmlDecl([$encoding, $standalone]) Ausgabe einer XML-Deklaration zu Beginn des Dokuments. Die Version »1.0« ist hartcodiert.
doctype($name, [$publicId, $systemId]) Ausgabe einer Dokumenttypdeklaration zu Beginn des Dokuments.
comment($text) Ausgabe eines XML-Kommentars.
pi($target [, $data]) Ausgabe einer PI.
startTag($name [, $aname1 => $value1, ...]) Ausgabe eines Start-Tags; das erste Argument ist der Elementname, gefolgt von einer Liste mit Name/Wert-Paaren, die die Attribute angeben.
emptyTag($name [, $aname1 => $value1, ...]) Ausgabe eines leeren Element-Tags; die Attribute werden wie bei der Methode startTag( ) angegeben.
endTag([$name]) Ausgabe eines End-Tags. Das Argument kann weggelassen werden, um das zuletzt geöffnete Element automatisch schließen zu lassen.
dataElement($name, $data [, $aname1 => $value1, ...]) Gibt ein Element mit Textdaten aus. Sowohl das Start-Tag als auch das End-Tag werden erzeugt, mitsamt der dazwischen stehenden Textdaten.
characters($data) Ausgabe von Textdaten.

Mit Hilfe dieser Methoden können Sie sehr einfach ein komplettes XML-Dokument erzeugen. Das Programm im folgenden Beispiel verwendet das Modul zur Ausgabe einer HTML-Datei.

Beispiel: HTML-Generator

use IO;
my $output = new IO::File(">output.xml");

use XML::Writer;
my $writer = new XML::Writer( OUTPUT => $output );

$writer->xmlDecl( 'UTF-8' );
$writer->doctype( 'html' );
$writer->comment( 'Meine niedliche kleine HTML-Seite' );
$writer->pi( 'foo', 'bar' );
$writer->startTag( 'html' );
$writer->startTag( 'body' );
$writer->startTag( 'h1' );
$writer->startTag( 'font', 'color' => 'green' );
$writer->characters( "<Hallo, Welt!>" );
$writer->endTag( );
$writer->endTag( );
$writer->dataElement( "p", "Freut mich, Dich zu sehen." );
$writer->endTag( );
$writer->endTag( );
$writer->end( );

Die Ausgabe dieses Programms sieht wie folgt aus:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<!-- Meine niedliche kleine HTML-Seite -->
<?foo bar?>
<html><body><h1><font color="green">&lt;Hallo, Welt!&gt;</font></h1><p>Freut mich, Dich
zu sehen.</p></body></html>

Das Modul bietet einige nette Annehmlichkeiten. Zum Beispiel werden Sonderzeichen wie etwa das Ampersand (&) in Textdaten automatisch in die entsprechenden Entityreferenzen umgewandelt. Attributwerte werden automatisch mit Anführungszeichen versehen. Während das Dokument aufgebaut wird, kann man den Kontext mit Hilfe von Methoden wie within_element('body') überprüfen. Im obigen Beispiel würde diese Methode true liefern, während wir uns innerhalb des HTML-Body befinden, und andernfalls false.

Standardmäßig werden alle Tags ohne Trennzeichen ausgegeben. Aus Gründen der Lesbarkeit werden aber oft Leerzeichen und Zeilenenden als Trennzeichen gewünscht. Setzt man die Option NEWLINES auf true, wird jedes End-Tag mit einem Zeilenende abgeschlossen. Setzt man die Option DATA_MODE, bewirkt das einen ähnlichen Effekt. Kombiniert man DATA_MODE mit DATA_INDENT, werden die einzelnen Zeilen automatisch eingerückt, um ein hübsches, gut lesbares Dokument zu erzeugen.

Das Schöne an XML ist, daß man damit im Prinzip beliebige textuelle Daten organisieren kann. Mit XML::Writer kann man einen Haufen von Informationen in ein klar strukturiertes Dokument umwandeln. Zum Beispiel kann man den Inhalt eines Verzeichnisses in eine hierarchische Datenbank umwandeln. Das leistet das Programm im Beispiel unten.

Beispiel: Abbildung eines Verzeichnisses in ein XML-Dokument

use XML::Writer;
my $wr = new XML::Writer( DATA_MODE => 'true', DATA_INDENT => 2 );
&as_xml( shift @ARGV );    
$wr->end;

# Rekursive Umwandlung eines einzelnen Verzeichnisses in XML
#
sub as_xml {
    my $path = shift;
    return unless( -e $path );
              
    # Falls dies ein Verzeichnis ist, gib ein Start-Tag aus, dann den
    # Inhalt und dann das End-Tag.
    if( -d $path ) {
        $wr->startTag( 'directory', name => $path );
          
        # Das Inhaltsverzeichnis von $path wird in ein
        # Array geladen.
        my @contents = ( );
        opendir( DIR, $path );
        while( my $item = readdir( DIR )) {
        next if( $item eq '.' or $item eq '..' );
        push( @contents, $item );
        }
        closedir( DIR );
          
        # Rekursiver Aufruf für alle Dateien oder Unterverzeichnisse
        foreach my $item ( @contents ) {
        &as_xml( "$path/$item" );
        }
        $wr->endTag( 'directory' );
          
    # Alles, was kein Verzeichnis ist, behandeln wir als Datei.
    } else {
    $wr->emptyTag( 'file', name => $path );
    }
}

So sieht eine Ausgabe des Programms aus. Beachten Sie die Verwendung von DATA_MODE und DATA_INDENT zur Verbesserung der Lesbarkeit:

$ ~/bin/dir /home/eray/xtools/XML-DOM-1.25

<directory name="/home/eray/xtools/XML-DOM-1.25">
    <directory name="/home/eray/xtools/XML-DOM-1.25/t">
        <file name="/home/eray/xtools/XML-DOM-1.25/t/attr.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/minus.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/example.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/print.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/cdata.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/astress.t" />
        <file name="/home/eray/xtools/XML-DOM-1.25/t/modify.t" />
    </directory>
    <file name="/home/eray/xtools/XML-DOM-1.25/DOM.gif" />
    <directory name="/home/eray/xtools/XML-DOM-1.25/samples">
        <file name="/home/eray/xtools/XML-DOM-1.25/samples/REC-xml-19980210.xml" />
    </directory>
    <file name="/home/eray/xtools/XML-DOM-1.25/Manifest" />
    <file name="/home/eray/xtools/XML-DOM-1.25/Makefile.PL" />
    <file name="/home/eray/xtools/XML-DOM-1.25/Changes" />
    <file name="/home/eray/xtools/XML-DOM-1.25/CheckAncestors.pm" />
    <file name="/home/eray/xtools/XML-DOM-1.25/CmpDOM.pm" />

Das Modul XML::Writer wird dabei Schritt für Schritt und rekursiv eingesetzt. Man kann das Modul auch gut für eine Baumstruktur von Objekten verwenden, indem zum Beispiel jeder Objekttyp seine eigene »to-string«-Methode bekommt, die entsprechende Aufrufe eines XML::Writer-Objekts bewirkt. Das Modul ist extrem flexibel und nützlich.

Andere Methoden der Ausgabe

Denken Sie daran, daß die meisten Parsermodule ihre eigenen Möglichkeiten zur Umwandlung des jeweiligen Inhalts in einfache, hübsche XML-Strings anbieten. Zum Beispiel kennen die Dokument- oder Elementobjekte von XML::LibXML eine Methode toString( ). Entsprechend wird diese Methode von spezifischeren XML-Anwendungen, die dieses Modul einsetzen, gerne verwendet. Leitet die Anwendung eine Subklasse ab, werden meist entsprechende Methoden angeboten und mittels derselben Methoden der Superklasse implementiert. Details sollten Sie der Dokumentation Ihres bevorzugten XML-Parsers entnehmen.

Letzten Endes genügt einem oft auch die ganz einfache Perl-Funktion print. Natürlich kennt diese Funktion keine XML-spezifischen Regeln oder Anforderungen, aber dafür bietet sie eine ungleich feinere Kontrolle über jedes einzelne auszugebende Zeichen. Je trickreicher die Programmierung wird, desto mehr kann ein Rückgriff auf print zu einer Erleichterung führen. In Strategien des Programmierers werden wir darauf noch näher eingehen. Allerdings sollten Sie niemals die korrekte Behandlung der Sonderzeichen < und & vergessen, indem Sie sie durch die entsprechenden Entityreferenzen ersetzen, wie wir sie aus der Tabelle Entityreferenzen, die in XML ohne Deklaration gültig sind kennen. Auch der großzügige Einsatz von CDATA-Abschnitten ist eine Möglichkeit.

  

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Perl & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2003 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Perl & XML" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de