XML::Parser

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

Einen Parser zu schreiben bedeutet eine Menge Arbeit. Ohne einen erheblichen Aufwand an Tests weiß man nicht, ob man wirklich alles berücksichtigt hat. Vielleicht sind Sie eine Art Mutant und haben Freude daran, auf unterster Ebene den Code eines Parsers zu entwickeln und zu optimieren? Nein? Dann wird Ihr Programm unter Umständen langsam und frißt Ressourcen. Aber zum Glück gibt es ja bereits eine große Menge von freien und einfach zu verwendenden XML-Parsern in hoher Qualität (die Autoren sind vermutlich solche Mutanten). Seit Jahren wird daran gearbeitet, Perl und XML zusammenzubringen, und das Ergebnis ist eine große Menge bereits erfundener Räder, die zu Ihrer Verfügung stehen.

Wohin wenden sich Perl-Programmierer, die fertige Module zum Einsatz in ihren Programmen suchen? Natürlich an das Comprehensive Perl Archive Network (CPAN), ein vielfach gespiegeltes Archiv voller freier Open Source Software in und für Perl. Wenn Sie sich nicht bereits mit dem CPAN auskennen, dann sollten Sie Ihren Elfenbeinturm schleunigst verlassen und ein auf dem Boden der Tatsachen stehender Programmierer werden. Sie werden eine Unmenge von Modulen finden, die von Leuten verfaßt sind, die sich bereits seit langem mit Perl und XML beschäftigen und die ihre Werkzeuge bereitwillig mit dem Rest der Welt teilen.

Anmerkung: Bitte stellen Sie sich CPAN nicht als einen Katalog fertiger Lösungen für jede denkbare XML-Anwendung vor. Denken Sie eher an einen Werkzeugkasten oder eine Sammlung verschiedener Bausteine, die Sie nach Ihrem Gutdünken zusammenstellen und konfigurieren, um daraus die fertige Anwendung zu entwickeln. Es gibt Module, die sich mit bestimmten XML-Anwendungen wie SOAP oder RSS beschäftigen, aber die meisten sind eher allgemeiner Natur. Vermutlich werden Sie eher zu einem der generischen Module greifen und es an Ihre eigenen Bedürfnisse anpassen. Wir werden Ihnen zeigen, wie einfach das ist, zumal es verschiedene Wege gibt, wie man ein Modul in die eigene Anwendung einbinden kann.

XML-Parser unterscheiden sich vor allem in zwei Punkten voneinander. Zum einen ist dies der Parserstil, also die Art, wie der Parser mit XML umgeht. Dazu gibt es verschiedene Strategien, zum Beispiel den Aufbau einer Datenstruktur oder die Bildung eines Eventstroms. Eine andere wesentliche Eigenschaft eines Parsers ist der Grad der Einhaltung von Standards, die Bandbreite reicht hier von extremen Nischenlösungen bis zur peinlich genauen Anlehnung an Standards. Die Achse verschiebt sich allmählich weg von den Individuallösungen hin zu Standards wie SAX oder DOM, da innerhalb der Perl-Gemeinde allmählich Einigkeit darüber herrscht, wie diese Standards zu implementieren sind.

Das Modul XML::Parser ist gewissermaßen der Ururgroßvater aller auf Perl basierenden XML-Prozessoren. Das Erscheinungsbild dieses Parsers ist vielseitig, da er eine ganze Reihe von verschiedenen Parserstilen bietet, allerdings ist er eher eine Individuallösung. Da er der erste XML-Parser war, der am Perl-Horizont erschien und effizient war, hat er sich auf breiter Ebene durchgesetzt, und wir haben ihm einen Ehrenplatz in unseren Herzen reserviert. Auch aus heutiger Sicht ist er immer noch recht nützlich. Okay, die API von XML::Parser ist nicht standardisiert, und in einigen Dingen ist er etwas übergenau, aber er funktioniert ganz einfach. Er liest und analysiert Dokumente mit einer vernünftigen Geschwindigkeit und Flexibilität. Der gemeine Programmierer tendiert dazu, die erste funktionierende Lösung zu wählen, auf die er stößt; ein auch unter Perl-Hackern nicht ganz unbekanntes Phänomen. Ein paar Schönheitsfehler stören dabei nicht. Aus diesem Grund basieren fast alle Perl-Module zum Thema XML, die schon ein paar Jahre auf dem Buckel haben, auf XML::Parser.

Ungefähr seit dem Jahre 2001 gibt es allerdings einige andere XML-Parser, die intern auf schnelleren und mehr an den Standards angelehnten Kernen basieren. Wir werden uns in Kürze noch mit diesen Modulen beschäftigen. Zunächst wollen wir uns aber noch etwas dem guten, alten XML::Parser zuwenden.

In den frühen Tagen von XML schrieb ein sehr fähiger Programmierer namens James Clark einen XML-Parser in C. Er nannte ihn Expat. (Anmerkung: Der Name James Clark besitzt einen besonderen Ruf in der XML-Gemeinschaft. Er hat durch seine frei kopierbaren Werkzeuge unermüdlich zur Standardisierung und Weiterentwicklung von XML beigetragen. Sie finden die meisten seiner Arbeiten unter www.jclark.com. Clark ist unter anderem auch der Herausgeber der W3C-Empfehlungen zu XSLT und XPath, die Sie unter www.w3.org finden.) Durch seine Schnelligkeit, Effizienz und Verläßlichkeit wurde er zum Werkzeug der Wahl unter den frühen Anwendern von XML. Kein anderer als Larry Wall schrieb eine Low-Level-API für diesen Parser und baute damit eine Brücke zwischen XML und Perl, die er XML::Parser::Expat nannte. Davon leitete er dann als nächstes XML::Parser als einen eher komfortablen und universellen Parser für jedermann ab. XML::Parser wird in der Zwischenzeit von Clark Cooper betreut und ist die Grundlage einer großen Anzahl von XML-Modulen.

Das Erfolgsgeheimnis von XML::Parser liegt zweifellos in der Anbindung einer vorhandenen, in C geschriebenen Lösung. Wir haben gesehen, welchen Aufwand selbst das Schreiben eines einfachen Parsers in Perl bedeutet. Versuchen Sie einmal, unser Beispielprogramm auf ein wirklich großes XML-Dokument anzuwenden. Sie werden ganz schön lange warten müssen, bevor unser einfacher Parser die Wohlgeformtheit des Dokuments bestätigt. Es gibt auch komplett in Perl geschriebene Lösungen, die den Vorzug der universellen Einsetzbarkeit bieten. Was die Performance angeht, sind sie aber nicht mit einem in C geschriebenen Rennwagen wie Expat vergleichbar. Glücklicherweise bemerkt man als Anwender eines Moduls nicht, ob es intern in C oder Perl geschrieben wurde. In der Tat gibt es eine ganze Menge solcher Perl-Module, da sie dank der XS-Schnittstelle von Perl nicht allzu kompliziert zu erstellen sind. (Anmerkung: Detaillierte Informationen zu XS finden Sie in der Manpage perlxs und in Kapitel 25 von Programmieren mit Perl.)

Beispiel: Prüfung auf Wohlgeformtheit, überarbeitete Fassung

Um die Anwendung von XML::Parser zu demonstrieren, kehren wir zu unserem alten Problem zurück, die Wohlgeformtheit eines Dokuments zu überprüfen. Wenn wir uns auf XML::Parser stützen, wird das sehr einfach, wie wir im folgenden Beispiel sehen.

Beispiel: Prüfung auf Wohlgeformtheit mit XML::Parse

use XML::Parser;

my $xmlfile = shift @ARGV; # Die Datei, die wir prüfen wollen

# Initialisierung eines Parserobjekts und Lesen der Datei
my $parser = XML::Parser->new( ErrorContext => 2 );
eval { $parser->parsefile( $xmlfile ); };

# Falls ein Fehler gemeldet wurde, gib ihn aus; andernfalls verkünde den Erfolg
if( $@ ) {
    $@ =~ s/at \/.*?$//s; # Zeilennummer des Moduls entfernen
    print STDERR "\nFEHLER in '$xmlfile':\n$@\n";
} else {
    print STDERR "'$xmlfile' enthält ein wohlgeformtes XML-Dokument\n";
}

Wie arbeitet dieses Programm? Wir erzeugen zunächst ein Objekt vom Typ XML::Parser, das die eigentliche Arbeit für uns erledigen soll. Die Verwendung eines Objekts statt einer statischen Methode bietet die Annehmlichkeit, daß man den Parser einmal konfigurieren und dann beliebig oft mit denselben Einstellungen verwenden kann. Dadurch wird der für die Initialisierung erforderliche Aufwand reduziert. Das Objekt konserviert Ihre Einstellungen und bietet Zugriff auf die Expat-Routinen, solange Sie Dateien parsen wollen. Anschließend ist das Objekt dafür verantwortlich, alles sauber aufzuräumen.

Als nächstes wird die Methode parsefile( ) aufgerufen, und zwar innerhalb eines eval-Blocks, da XML::Parser eine etwas drastische Art hat, mit Fehlern umzugehen. Ohne diesen eval-Block würde unser Programm bei einem Fehler beendet, ohne daß wir eine Chance hätten, für ein geordnetes Ende zu sorgen. Anschließend prüfen wir den Inhalt der Variable $@ darauf, ob sie eine Fehlermeldung enthält. Wenn das der Fall war, entfernen wir die Zeilennummer des Moduls, die anzeigt, an welcher Stelle der Fehler erkannt wurde. Anschließend geben wir eine Meldung aus.

Bei der Initialisierung des Parserobjekts setzen wir die Option ErrorContext => 2. XML::Parser hat eine Reihe von Optionen, die den Vorgang des Parsens kontrollieren. In diesem Fall wird die Option direkt an den Expat-Parser durchgereicht. Der Parser interpretiert dies als Anweisung, die jeweils letzten zwei Zeilen vor einem Fehler zu konservieren und gegebenenfalls als Kontext auszugeben. Wenn wir die Fehlermeldung ausgeben, beschreibt sie nicht nur die Art des Fehlers, sondern enthält auch einen Ausschnitt des Dokuments, der die Meldung illustrieren soll.

Hier ist ein Beispiel, wie die Fehlermeldungen unseres Prüfprogramms aussehen. (Wir haben uns entschieden, das Programm xwf – für »XML well-formedness checker« – zu nennen.)

$ xwf ch01.xml

FEHLER in 'ch01.xml':

not well-formed (invalid token) at line 66, column 22, byte 2354:

<chapter id="dorothy-in-oz">
<title>Lions, Tigers & Bears</title>
=====================^

Beachten Sie, wie einfach es ist, den Parser zu initialisieren, und was für schöne Ergebnisse man damit bekommt. Was Sie hier nicht sehen können, aber beim Start des Programms bemerken werden: Es ist schnell! Die Antwort erscheint im Bruchteil einer Sekunde.

Man kann den Parser durch Konfiguration auf verschiedene Art und Weise verwenden. Zum Beispiel muß man keineswegs eine Datei lesen. Die Methode parse( ) erlaubt zum Beispiel die Analyse eines Textstrings. Oder man kann mit Hilfe der Option NoExpand => 1 die Auflösung externer Entities unterdrücken und eventuell eine eigene Routine dafür einbinden. Im Falle unseres Prüfprogramms könnte das bedeuten, daß man die Prüfung nicht auf externe Dateien ausdehnen möchte.

Der Wohlgeformtheitsprüfer ist ein sehr nützliches Tool, das Sie in Ihrem Werkzeugkasten gut gebrauchen können, wenn Sie regelmäßig mit XML-Dateien arbeiten. Aber was die Arbeit mit XML::Parser und dessen Möglichkeiten angeht, streift er gerade einmal die Oberfläche. Wir werden im nächsten Abschnitt sehen, daß die wichtigste Aufgabe eines Parsers darin besteht, Daten in ein Programm hinein zu schaufeln. Wie das geschieht, hängt davon ab, welchen Stil Sie wählen.

Parserstile

XML::Parser unterstützt verschiedene Stile des Parsens, die je nach Entwicklungsstrategie mal besser und mal schlechter passen. Der Stil beeinflußt nicht, wie der Parser ein XML-Dokument liest. Allerdings verändert er auf drastische Weise, wie die gelesenen Daten an das Programm übergeben werden. Sie wünschen eine persistente Datenstruktur, in der das Dokument abgebildet ist? Bitteschön, können Sie haben. Sie möchten lieber, daß der Parser Funktionen aufruft, die Sie geschrieben haben? Auch das kann geschehen. Der Stil wird bei der Initialisierung des Objekts bestimmt, indem man dem Konstruktor ein Attribut Style übergibt. Zur Auswahl stehen dabei die folgenden Varianten:

Debug

Dieser Stil schreibt das Dokument auf STDOUT , wobei es durch Einrückung formatiert wird (innere Elemente werden tiefer eingerückt). Die Methode parse( ) liefert kein spezielles Ergebnis.

Tree

Dieser Stil erzeugt eine hierarchische Baumstruktur, mit der Ihr Programm arbeiten kann. Alle Elemente und ihre Daten werden in Form von verschachtelten Hashes und Arrays kristallisiert.

Object

Ähnlich wie bei Tree wird auch bei diesem Stil eine Referenz auf eine hierarchische Datenstruktur mit den Dokumentdaten geliefert. Allerdings besteht die Datenstruktur nicht aus simplen Hashes und Arrays, sondern aus Objekten, deren Klassen den verschiedenen Bestandteilen des XML-Markup entsprechen.

Subs

Bei diesem Stil geben Sie selbst sogenannte Callback-Funktionen vor, die bestimmte Elemente behandeln sollen. Die Idee ist, daß die Funktionen nach den Elementen benannt sind, die sie behandeln sollen. Die verschiedenen Funktionen liegen alle in einem Package, das Sie selbst mit Hilfe der Option pkg vorgeben. Jedesmal wenn der Parser das Start-Tag eines Elements <fooby> findet, wird er in dem angegebenen Pakkage nach einer Funktion fooby( ) suchen. Gleichermaßen wird er beim End-Tag nach einer Funktion _fooby( ) suchen. Der Parser übergibt gegebenenfalls kritische Informationen wie Inhalt und Attribute an die Funktion, so daß sie für die Verarbeitung zur Verfügung stehen.

Stream

Wie beim Stil Subs definiert man auch hier Callback-Funktionen für verschiedene XML-Komponenten. Allerdings ist die Unterteilung wesentlich allgemeiner als die durch Elementnamen. Die Funktionen werden hier als Handler für bestimmte » Events« bezeichnet, wie zum Beispiel den Start eines Elements (eines beliebigen Elements, nicht eines bestimmten), einer Menge von Zeichendaten oder einer PI. Das Package der Handler wird entweder mit der Option Handlers oder durch einen Aufruf der Methode setHandlers( ) gesetzt.

Subklassenname

Man kann auch eine Subklasse von XML::Parser erzeugen, als Stil gibt man dann den vollständigen Klassennamen an (der mindestens ein :: enthalten muß.) Diese Variante ist gut geeignet für Situationen, in denen man eine Parser-artige API für etwas spezifischere Situationen bieten möchte. Das Modul XML::Parser::PerlSAX benutzt das, um den Parserstandard SAX zu implementieren.

Das folgende Beispiel ist ein Programm, das XML::Parser mit dem Stil Tree benutzt. Der Parser liest das gesamte XML-Dokument und bildet es in eine Datenstruktur ab. Am Ende wird dem Programm die aufgebaute Struktur übergeben, um sie nach Belieben zu verarbeiten.

Beispiel: Aufbau eines XML-Baums

use XML::Parser;

# Initialisierung des Parsers und Lesen einer Datei
$parser = new XML::Parser( Style => 'Tree' );
my $tree = $parser->parsefile( shift @ARGV );

# Serialisierung der Struktur
use Data::Dumper;
print Dumper( $tree );

Im Modus Tree liefert die Methode parsefile( ) eine Referenz auf eine Datenstruktur, die das Dokument mit Hilfe von Arrays und Hashes abbildet. Wir verwenden das Modul Data::Dumper, ein praktisches Hilfsmittel zur Serialisierung von Datenstrukturen, um das Ergebnis auszugeben. Im nächsten Beispiel sehen Sie eine mögliche Eingabedatei:

Beispiel: Eine XML-Beispieldatei

<preferences>
  <font role="console">
    <fname>Courier</name>
      <size>9</size>
  </font>
  <font role="default">
    <fname>Times New Roman</name>
      <size>14</size>
  </font>
  <font role="titles">
    <fname>Helvetica</name>
      <size>10</size>
  </font>
</preferences>

Mit dieser Beispieldatei produziert das Programm die folgende Ausgabe (die wir aus Gründen der Lesbarkeit eingerückt und etwas umformatiert haben):

$tree = [       
     'preferences', [
       {}, 0, '\n',
       'font', [
       { 'role' => 'console' }, 0, '\n',
       'size', [ {}, 0, '9' ], 0, '\n',
       'fname', [ {}, 0, 'Courier' ], 0, '\n'
     ], 0, '\n',
     'font', [
       { 'role' => 'default' }, 0, '\n',
       'fname', [ {}, 0, 'Times New Roman' ], 0, '\n',
       'size', [ {}, 0, '14' ], 0, '\n'
     ], 0, '\n',
     'font', [
       { 'role' => 'titles' }, 0, '\n',
       'size', [ {}, 0, '10' ], 0, '\n',
       'fname', [ {}, 0, 'Helvetica' ], 0, '\n',
     ], 0, '\n',
  ]
];

Es ist erheblich einfacher, Code zu schreiben, der die obige Struktur seziert, als einen eigenen Parser zu implementieren. Wir können uns darauf verlassen, daß das Dokument zu 100 Prozent wohlgeformt war, denn der Parser hat eine Datenstruktur als Ergebnis geliefert, statt das Programm mit »die« abzubrechen. In Eventströme werden wir uns mit dem Stream-Stil von XML::Parser beschäftigen, und in Baumorientierte Verarbeitung werden wir mehr über Bäume und Objekte erzählen.

  

  

<< 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