Seinem inoffiziellen Leitspruch bleibt Perl auch treu, wenn es um das Verarbeiten von XML-Dokumenten geht: There is more than one way to do it - es gibt mehr als einen Weg für diese Aufgabe. Die verschiedenen Module sollen anhand des Beispiels von Abbildung 1 illustriert werden. Das File enthält zwei Datensätze vom Typ »<cd>« in einem »<result>«-Tag. Jeder dieser Datensätze besteht seinerseits aus Tags für »<artists>« und »<title>« einer CD, wobei »<artists>« wiederum ein oder mehrere »<artist>«-Tags umschließen darf.
Schnell, einfach und auf einen Rutsch
Am einfachsten parst sich XML in Perl mit Hilfe des Moduls XML::Simple vom CPAN. Dieses Modul exportiert die Funktion »XMLin«, die eine komplette Datei oder einen String mit XML-Daten hereinholt und anschließend als Datenstruktur in Perl ablegt:
use XML::Simple;
my $ref = XMLin("data.xml");
Abbildung 2 zeigt einen Dump der resultierenden Datenstruktur in »$ref«. Zwei Dinge fallen auf: Abhängig davon, ob ein oder zwei Künstler in »<artists>« stehen, ist die resultierende Datenstruktur entweder ein Skalar oder ein Array. Das erschwert später die Arbeit.
Mit der Option »ForceArray« lässt sich jedoch festlegen, dass ein Feld immer als Array dargestellt wird. Der Aufruf »XMLin("data.xml", ForceArray => [\'artist\']);« stellt sicher, dass »$ref->{cd}->[0]->{artists}->{artist}« ebenfalls eine Arrayreferenz zurückgibt, obwohl dort nur ein einziger Interpret steht.
Außerdem ist »->{artists}->{artist}« etwas umständlich zu schreiben, da »->{artists}« außer »->{artist}« keine weiteren Unterelemente enthält. XML::Simple bietet mit der »GroupTags«-Option die Möglichkeit, Hierarchien kollabieren zu lassen. Der Aufruf
XMLin("data.xml",
ForceArray => ['artist'],
GroupTags =>
{'artists' => 'artist'});
erzeugt die Datenstruktur in Abbildung 3, die schon sehr einfach zu handhaben ist. Beispielsweise lassen sich alle Seriennummern nun mit einer einfachen For-Schleife finden:
for my $cd (@{$ref->{cd}}) {
print $cd->{serial}, "n";
}
XML::Simple liest die komplette XML-Datei in den Hauptspeicher ein, was oft praktisch ist, manchmal aber auch zum Problem wird: Bei riesigen XML-Dateien ist das nicht effizient oder schlichtweg unmöglich.
Verschlungene Pfade
Wer auf knapper und griffiger Notation besteht, um durch den XML-Dschungel zu navigieren, wird XPath lieben. Das Modul XML::LibXML vom CPAN hängt sich an die Libxml-2-Bibliothek des Gnome-Projekts an und bietet über die »findnodes«-Methode auch die Möglichkeit, per XPath-Notation auf XML-Elemente zuzugreifen.
Um zum Beispiel den Textinhalt aller »<title>«-Elemente zutage zu fördern, genügt die XPath-Notation »/result/cd/title/text()«, die mit »/« an der Dokumentenwurzel anfängt, in die »<results>«-, »<cd>«- und »<title>«-Elemente hinabsteigt und mit »text()« deren Textinhalt zurückgibt.
Abbildung 1: Der XML-Beispieldatensatz, der die hier vorgestellten XML-Module illustriert, zeigt ein CD-Archiv.
Alternativ geht es auch mit »//title/text()«, denn so stöbert XPath einfach alle »<title>«-Elemente in beliebiger Tiefe auf. Listing 1 zeigt, dass die Methode »findnodes()« eine Reihe von Textobjekten zurückgibt, deren Methode »toString()« den Titeltext liefert.
Aber mit XPath lassen sich auch wesentlich komplexere Aufgaben lösen: Listing 2 fieselt beispielsweise die Seriennummern aller CDs heraus, bei denen das Artist-Tag den String »Foo Fighters« enthält. Das leistet ein einzelnes, zunächst recht kompliziert wirkendes Konstrukt: »/result/cd/artists/artist[.="Foo Fighters"]/../../@serial«. Der Ausdruck wird verständlich, wenn man ihn abschnittweise betrachtet.
Zuerst steigt XPath bis zu den »<artist>«-Tags hinab und prüft dann jedes mit »[.="Foo Fighters"]«. Dieses in eckige Klammern eingeschlossene Prädikat referenziert mit ».« den aktuellen Knoten und kontrolliert, ob dessen Wert auch mit dem gesuchten String »Foo Fighters« übereinstimmt.
Trifft dies zu, fährt XPath mit »../..« anschließend zwei Etagen hoch. Auf dieser Ebene findet sich auch das CD-Tag. Dessen Parameter »serial« liest das Modul mittels »@serial« aus und gibt ihn zurück. Das Listing 2 »xpserial« muss dann nur noch die »value()«-Methode des zurückgelieferten Objekts bemühen, um den Wert des Parameters zu erhalten - in diesem Fall also die gesuchte Seriennummer der CD.
XPath erlaubt sehr prägnante und bündige Formulierungen. Doch sobald etwas nicht auf Anhieb funktioniert, kann die Fehlersuche viel Zeit verschlingen. Glücklicherweise bügelt Perl manchen Nachteil einer reinen XSLT-Umgebung wieder aus, weil es schnelle XPath-Hacks mit solider Programmlogik und seinen ausgezeichneten Debugging-Möglichkeiten kombiniert.
01 #!/usr/bin/perl -w
02 use strict;
03 use XML::LibXML;
04
05 my $x = XML::LibXML->new() or
06 die "new failed";
07
08 my $d = $x->parse_file("data.xml") or
09 die "parse failed";
10
11 my $titles =
12 "/result/cd/title/text()";
13
14 for my $title ($d->findnodes($titles)) {
15 print $title->toString(), "n";
16 }
|
01 #!/usr/bin/perl -w
02 use strict;
03 use XML::LibXML;
04
05 my $x = XML::LibXML->new() or
06 die "new failed";
07
08 my $d = $x->parse_file("data.xml") or
09 die "parse failed";
10
11 my $serials = q{
12 /result/cd/artists/
13 artist[.="Foo Fighters"]/
14 ../../@serial
15 };
16
17 for my $serial ($d->findnodes($serials)) {
18 print $serial->value(), "n";
19 }
|