Gängige Modelle für die XML-Verarbeitung

(Auszug aus "XML in a Nutshell" von Elliotte Rusty Harold & W. Scott Means)

Der in XML strukturierte und ausgezeichnete Text kann von Entwicklern auf unterschiedliche Weise verarbeitet werden. Programme können das XML-Dokument als reinen Text, als Folge von Ereignissen, als Baum oder als Serialisierung einer anderen Struktur auffassen. Tools für jede diese Behandlungsweisen sind weit verbreitet.

Textbasierte XML-Verarbeitung

Grundsätzlich sind XML-Dokumente zunächst einmal Texte. Sowohl der Inhalt als auch die Markup-Zeichen werden als Text dargestellt. Daher können Text-Editoren sehr hilfreich bei der Analyse, Erzeugung und Veränderung von XML-Dokumenten sein. Die Tatsache, dass XML nichts anderes als Text ist, gibt den Entwicklern die Möglichkeit, direkt mit XML zu arbeiten und nur bei Bedarf auf Tools zurückzugreifen, die speziell für die Bearbeitung von XML-Dokumenten gedacht sind.

Es war eines der ursprünglichen Entwurfsziele von XML, dass Dokumente leicht zu parsen sein sollen. Bei sehr einfachen Dokumenten, die Features wie Standardwerte von Attributen oder Validierung nicht einsetzen, ist es möglich, Tags, Attribute und Textdaten mit Standard-Programmierwerkzeugen wie regulären Ausdrücken und Tokenizern zu parsen. Aber die Komplexität der Verarbeitung wächst schnell, wenn Dokumente mehr Features einsetzen. Wenn eine Anwendung den Inhalt einkommender Dokumente nicht vollständig kontrollieren kann, ist es fast immer vorzuziehen, einen der vielen hochwertigen XML-Parser zu verwenden, die für die meisten Programmiersprachen frei verfügbar sind.

Bei der XML-Bearbeitung spielen Text-Werkzeuge dennoch eine große Rolle. Sie sind ein wichtiger Bestandteil der verschiedenen Zusammenstellungen von Tools, die zur XML-Verarbeitung eingesetzt werden. Viele Entwickler benutzen einfache Text-Editoren wie vi, Emacs, NotePad, WordPad, BBEdit oder UltraEdit, um XML-Dokumente zu erstellen oder zu modifizieren. Reguläre Ausdrücke werden in unterschiedlichen Umgebungen, zum Beispiel in sed, grep, Perl und Python, für Vorgänge wie das Suchen und Ersetzen eingesetzt. Außerdem werden sie zum Optimieren der XML-Dokumente vor dem Parsen oder der Weiterverarbeitung mit XSLT gebraucht. Verschiedene Standards beginnen die Vorteile von Mustervergleichen mit regulären Ausdrücken zu nutzen, nachdem das Dokument geparst wurde. Die XML Schema-Empfehlung des W3C schließt beispielsweise Mustervergleiche mit regulären Ausdrücken als einen Mechanismus für die Validierung von Datentypen ein.

Die textbasierte Verarbeitung kann auch mit anderen Möglichkeiten zur XML-Verarbeitung kombiniert werden. Das Parsen und die nach der Verarbeitung erfolgende Serialisierung eines XML-Dokuments liefern nicht immer das gewünschte Ergebnis. XSLT zum Beispiel löst Entity-Referenzen auf. Um die Entities nicht zu verlieren, muss man sie im Ausgangsdokument durch eindeutige Platzhalter ersetzen. Im Ergebnisdokument kann man dann die Platzhalter wieder gegen die Entity-Referenzen eintauschen. Mit regulären Ausdrücken lässt sich das ganz einfach bewerkstelligen.

Achtung: Entwickler sollten bei der Auswahl des Textverarbeitungs-Tools vorsichtig sein, denn XML ist von Unicode abhängig. Viele Entwicklungsumgebungen sind mittlerweile aufgerüstet worden und unterstützen jetzt Unicode, diverse aber auch noch nicht. Bevor man die Ausgabedokumente eines Parsers mit einem Text-Werkzeug bearbeitet, sollte man sicherstellen, dass dieses Werkzeug Unicode unterstützt. Die ursprünglichen XML-Dokumente muss man dagegen mit Textverarbeitungs-Tools bearbeiten, die den Zeichensatz des Dokuments unterstützen. Die meisten modernen Sprachen (einschließlich Java, C#, Perl 5.6 und Python 2.2) und Tools unterstützen Unicode. Problemfälle stellen sich vorwiegend in C und C++ ein, wo Sie sich darüber Gedanken machen müssen, ob Sie wchar oder char verwenden sollen, und verstehen müssen, was ein wchar auf einer bestimmten Plattform überhaupt ist.

Ereignisorientierte XML-Verarbeitung

Beim Lesen eines XML-Dokuments bewegt sich der XML-Parser vom Anfang an das Ende des Dokuments. Er hält unter Umständen an, um externe Datenquellen zum Beispiel nach einer DTD oder einer externen Entity abzufragen, aber auf jeden Fall erhält er Kenntnisse über das Dokument, während er sich fortbewegt. Wenn das Parsen vollständig erfolgreich war, kombinieren baumbasierte XML-Technologien (wie DOM) diese inkrementellen Parse-Ereignisse zu einem monolithischen Bild eines XML-Dokuments.

Ereignisbasierte Parser andererseits melden diese während der Verarbeitung auftretenden Ereignisse an die Client-Anwendung, wenn sie auftreten. Zu diesen während der Verarbeitung auftretenden Ereignissen zählt das Lesen von Start-Tags, Elementinhalten und Start-Tags. Sehen Sie sich beispielsweise das Dokument im folgenden Beispiel an.

<name>
  <vorname>Keith</vorname>
  <nachname>Johnson</nachname>
</name>

Code-Beispiel: Einfaches XML-Dokument

Ein ereignisorientierter Parser würde Ereignisse wie die folgenden melden:

startElement:name
startElement:vorname
content:Keith
endElement:vorname
startElement:nachname
content:Johnson
endElement:nachname
endElement:name

Die Liste und die Struktur der Ereignisse können weitaus komplexer werden, wenn zum Beispiel Funktionalitäten wie Namensräume, Attribute, Whitespace zwischen Elementen, Kommentare, Verarbeitungsanweisungen oder Entities im Ausgangsdokument dazukommen. Der grundsätzliche Mechanismus ist aber recht einfach und im Allgemeinen sehr effizient.

Ereignisorientierte Anwendungen sind in der Regel komplexer als baumbasierte Anwendungen. Zur Verarbeitung von Ereignissen muss in der Regel eine Zustandsmaschine entwickelt werden, also ein Programm, das den aktuellen Kontext versteht und die in Ereignissen abgelegten Informationen richtig einordnen kann. Da Ereignisse auch während des Lesens auftreten, müssen solche Anwendungen bereits vorhandene Ergebnisse verwerfen können, falls ein schwerwiegender Fehler auf dem Weg durch das Dokument auftreten sollte. Außerdem ist es aufwändiger, auf eine große Vielzahl von Daten zuzugreifen, die über ein ganzes Dokument verteilt sind, als es wäre, wenn das ganze Dokument in eine Baumstruktur geparst wäre.

Die Vorteile einer ereignisbasierten API sind Geschwindigkeit und Effizienz. Weil eine ereignisbasierte API das Dokument an die Client-Anwendung weiterfließen lässt, kann Ihr Programm unmittelbar mit der Verarbeitung der Daten beginnen, wenn der Anfang gelesen worden ist – also bevor das Ende des Dokuments erreicht wird. Es muss nicht warten, dass das ganze Dokument gelesen worden ist, bevor es anfangen kann. Beispielsweise kann ein Programm für den Aktienhandel, das eine lange Liste mit Kaufforderungen für verschiedene Aktien erhält, das erste Geschäft ausführen, bevor der Parser das zweite liest, und das zweite ausführen, bevor er das dritte liest, und so weiter. Das kann bei den ersten Geschäften entscheidende Sekunden sparen, wenn das Dokument viele separate Kaufaufträge enthält.

Sogar noch wichtiger als Geschwindigkeit ist die Größe. XML-Dokumente können ziemlich umfangreich sein, manchmal sogar Gigabyte an Daten enthalten. Eine ereignisbasierte API muss nicht all diese Daten auf einmal im Speicher halten. Sie kann das Dokument in kleinen, leicht zu handhabenden Happen verarbeiten und den Speicher anschließend wiederverwenden. In der Praxis ist es so, dass XML-Dokumente, die größer als ein paar hundert Megabyte sind, selbst mit den größten, fettesten Servern mit mehreren Gigabyte RAM nicht mehr mit einer baumbasierten API verarbeitet werden können. In einer Embedded-Umgebung (wie einem Handy) machen Speicherbeschränkungen ereignisbasierte APIs zwingend erforderlich.

Für bestimmte Aufgaben wie das Filtern von Inhalten sind ereignisbasierte Parser auch von Natur aus besser geeignet. Filter können Ereignisse verarbeiten und verändern, bevor diese an einen anderen Prozess weitergegeben werden. So können große Mengen von Transformationen effizient durchgeführt werden. Filter können hintereinander geschaltet werden und bieten damit eine relativ einfache Möglichkeit zum Aufbau einer Kette von XML-Verarbeitungsschritten, in der das Ergebnis eines Prozesses direkt an einen anderen übergeben wird. Wenn Informationen aus einem XML-Dokument direkt in die interne Struktur einer Anwendung eingespeist werden sollen, sind Ereignisse wahrscheinlich dafür am besten geeignet. Sogar Parser, die XML-Dokumente als komplette Bäume ausgeben, verwenden zum Aufbau dieser Bäume typischerweise eine Ereignisfolge. Derartige Parser werden im nächsten Abschnitt näher beschrieben.

Anmerkung: Die Simple API für XML (SAX) ist die meistgenutzte ereignisorientierte API. SAX 2.0.1 ist die aktuelle Version. Expat, ein häufig eingesetzter, in C geschriebener Parser, verwendet ebenfalls eine ereignisorientierte API.

Baumbasierte XML-Verarbeitung

XML-Dokumente können auf Grund der Forderungen an die Wohlgeformtheit sehr leicht mit Baumstrukturen beschrieben werden. Elemente sind inhärent hierarchisch, da sie andere Elemente, Textinhalte, Kommentare und so weiter enthalten.

Es gibt viele verschiedene Baummodelle für XML-Dokumente. XPath wird in XSLT-Transformationen benutzt. Es hat etwas andere Rahmenbedingungen als die DOM-API. Wieder andere Voraussetzungen schafft das XML Information Set (Infoset), ein anderes Projekt des W3C. XML Schema definiert ein Post-Schema Validation Infoset (PSVI), das vom XML-Schema abgeleitet ist. Dieses enthält mehr Informationen als alle anderen Baummodelle.

Die Entwickler, die Dokumente aus ihren Programmen heraus manipulieren möchten, verwenden typischerweise APIs, die Zugriff auf eine Repräsentation des XML-Dokuments im Objektmodell bieten. Baumorientierte APIs stellen anderen Anwendungen normalerweise ein Modell des gesamten Dokuments zur Verfügung, wenn das Parsen erfolgreich abgeschlossen ist. Anwendungen müssen sich damit befassen, den Parse-Kontext manuell festzuhalten, oder mit teilweise verarbeiteten Dokumenten klarkommen, weil der Parser auf einen Fehler gestoßen ist, da baumbasierte Parser sich im Allgemeinen selbst um Fehler kümmern. Statt einem Strom von Ereignissen zu folgen, können sie einfach durch den Baum navigieren, um die gewünschten Teile eines Dokuments zu finden.

Die Arbeit mit dem Baummodell hat grundlegende Vorteile. Das komplette Dokument steht jederzeit zur Verfügung, und es ist recht einfach, balancierte Teile des Dokuments zu verändern oder von einem Ort zu einem anderen zu verschieben. Die vollständige Kontextinformation zu jedem beliebigen Teil eines Dokuments ist jederzeit verfügbar. Wenn sie eine API einsetzen, die XPath-Ausdrücke unterstützt, haben Entwickler die Möglichkeit, sie einzusetzen, um Inhalte zu lokalisieren und inhaltliche Entscheidungen an beliebiger Stelle im Dokument zu treffen. (DOM Level 3 enthält zusätzlich formale Unterstützung für XPath. Verschiedene andere Implementierungen bieten bereits eine eigene, nicht standardbasierte Unterstützung.)

Baummodelle von Dokumenten haben auch einige Nachteile. Sie können große Menge Speicher belegen, in der Regel drei- bis zehnmal die Dateigröße des Originaldokuments. Entwickler haben mehr Möglichkeiten, wenn sie durch Dokumente navigieren können. Dazu ist aber unter Umständen die weitere Verarbeitung der Dokumente nach dem Parsen erforderlich. (Baummodelle verlangen von den Dokumenten nicht die gleiche Disziplin wie die ereignisorientierte Verarbeitung.) Diese Punkte können die Skalierung und den Austausch von Anwendungen, die sich auf Baummodelle stützen, sehr erschweren. Nichtsdestotrotz können Baummodelle die geeignete Wahl sein, wenn es um eine geringe Anzahl von Dokumenten oder um kleinere Dokumente geht.

Anmerkung: Das Document Object Model (DOM) ist die am weitesten verbreitete baumbasierte API. JDOM, DOM4J und XOM sind Alternativen, die nur für Java verfügbar sind. (XOM ist ein Objektmodell, das von Elliotte Rusty Harold, einem der Autoren, gefördert wird.)

Pull-basierte XML-Verarbeitung

Der neueste Kandidat in der XML-Verarbeitungsarena ist das so genannte Pull-Verarbeitungsmodell. Einer der am weitesten verbreiteten Pull-Prozessoren ist Microsofts .NET-Klasse XMLReader. Das Pull-Modell weist insofern die größte Ähnlichkeit mit dem ereignisbasierten Modell auf, als es den Inhalt des XML-Dokuments nach und nach während des Parsens verfügbar macht.

Im Unterschied zum Ereignis-Modell beruht das Pull-Verfahren jedoch darauf, dass die Client-Anwendung ihren eigenen Anforderungen folgend Inhalte vom Parser fordert. Beispielsweise könnte ein Pull-Client den folgenden Code enthalten, um das einfache Dokument aus dem Code-Beispiel Einfaches XML-Dokument zu parsen:

reader.ReadStartElement("name")
reader.ReadStartElement("given")
givenName = reader.ReadString( )
reader.ReadEndElement( )
reader.ReadStartElement("family")
familyName = reader.ReadString( )
reader.ReadEndElement( )
reader.ReadEndElement( )

Der Pull-Client fordert den XML-Inhalt an, den er vom Pull-Parser sehen möchte. In der Praxis führt das dazu, dass der Code für Pull-Clients leichter zu lesen und verstehen ist, als es der entsprechende ereignisbasierte Code wäre. In der Regel ist es auch nicht in dem Maße erforderlich, Stacks und Strukturen anzulegen, die die Dokumentinformationen aufnehmen, da der Code selbst so geschrieben werden kann, dass er das Recursive Descent-Modell des Parsens widerspiegelt.

In der Java-Welt haben BEA, Sun und einige einzelne Entwickler zusammengearbeitet, um die Streaming API for XML (StAX) zu schaffen. StAX und andere Pull-Parser teilen mit SAX die Vorteile strombasierter Verfahren wie Geschwindigkeit, Parallelität und Speichereffizienz, bieten andererseits aber eine API, mit der viele Entwickler besser klarkommen. Dem Wesen nach basieren SAX und andere Push-Parser auf dem Observer-Entwurfsmuster. StAX, XMLReader und andere Pull-Parser hingegen basieren auf dem Iterator-Entwurfsmuster.

Transformationen

Eine weitere Möglichkeit, die dem XML-Programmierer zur Verfügung steht, ist die Transformation von XML-Dokumenten. Die Extensible Stylesheet Language Transformation (XSLT) wird zum Beispiel sehr gern benutzt, um XML in HTML, XML oder andere Präsentationsformate umzuwandeln. Gelegentlich kann die Komplexität einer DOM- oder SAX-Anwendung reduziert werden, indem eine Transformation zur Vor- oder Nachbearbeitung eines XML-Dokuments eingesetzt wird. Beispielsweise könnte XSLT als Präprozesser für eine Screen Scraping-Anwendung dienen, die mit XHMTL-Dokumenten arbeitet. Das komplexe XHTML-Dokument könnte in ein kleineres, zugänglicheres anwendungsspezifisches XML-Format umgewandelt werden, das dann von einem Skript bearbeitet wird.

Transformationen können aus sich selbst heraus, in Browsern oder auf der Kommandozeile verwendet werden. Viele Implementierungen von XSLT und andere Transformations-Tools bieten Schnittstellen zu SAX oder DOM und vereinfachen dadurch die Aufgabe, Transformations-Pipelines zur Verarbeitung von Dokumenten aufzubauen.

XML ausblenden

Entwickler, die von den plattformübergreifenden Vorteilen von XML profitieren möchten, aber keine Geduld dazu haben, sich mit den Markup-Zeichen auseinander zu setzen, können auf verschiedene Tools zurückgreifen. Diese Tools ermöglichen die Bearbeitung von XML, ohne dass man sich direkt mit den XML-Strukturen beschäftigen muss. Web Services wie REST und SOAP können als ein Schritt in diese Richtung aufgefasst werden. Wenn man mit solchen Tools arbeitet, kann man die XML-Dateien immer noch direkt anfassen, aber mit Hilfe der Toolkits kann man das auch vermeiden.

Anwendungen dieser Art sind im Allgemeinen als weitere Schicht konzipiert, die auf der ereignis- oder baumorientierten Verarbeitung basiert und den darunter liegenden Informationen ihre eigene API anbietet. Unserer Meinung nach sind die zugrunde liegenden XML-Daten in den meisten Fällen so klar und zugreifbar, wie es nur geht. Zusätzliche Abstraktionsschichten oberhalb von XML führen im Ganzen nur zu einer Erhöhung der Komplexität und Strenge der Anwendung.

Standards und Erweiterungen

Die SAX- und DOM-Spezifikationen und auch verschiedene andere Spezifikationen, die den XML-Kern betreffen, bieten die Grundlage für die Verarbeitung von XML. Konkrete Implementierungen dieser Standards, insbesondere von DTDs, weichen bisweilen von der Spezifikation ab. Für einige Erweiterungen gibt es formale Definitionen. SVG zum Beispiel definiert bestimmte Erweiterungen für DOM, die speziell für die Arbeit mit SVG gedacht sind. Andere Erweiterungen kann man als Anhänge zu einer ursprünglichen Spezifikation auffassen, die dadurch zustande gekommen sind, dass ein Programmierer oder ein Anbieter zusätzliche Funktionalität für wichtig hielt, die nicht in der ursprünglichen Spezifikation enthalten war. Die verschiedenen DOM-Level und -Module haben dazu geführt, dass es Entwickler gibt, die behaupten, Support für DOM anzubieten. In Wirklichkeit bieten sie allerdings nur Unterstützung für eine bestimmte Teilmenge oder Erweiterung der vorhandenen Spezifikation an.

Das Portieren der Standards führt ebenfalls zu verschiedenen Varianten. SAX wurde ursprünglich für Java entwickelt, und das Kernstück des SAX-Projekts definiert auch nur eine Java-API. DOM verwendet IDL zur Beschreibung seiner API, allerdings haben verschiedene Implementierungen die API auf etwas unterschiedliche Weise interpretiert. SAX2 und DOM sind in der Regel portierbar, allerdings kann die jeweilige Realisierung in unterschiedlichen Umgebungen variieren.

Es gibt in einigen Umgebungen auch Bibliotheken, die völlig unabhängig von den SAX- und DOM-Interfaces sind. Sowohl Perl als auch Python enthalten Bibliotheken, die die ereignisorientierte und baumorientierte Verarbeitung kombinieren. Auf diese Weise ermöglichen sie es Anwendungen, auf Teilbäumen zu arbeiten statt mit SAX-Ereignissen oder kompletten DOM-Bäumen. Diese nicht-standardisierten Verfahren erschweren den Wechsel zwischen Umgebungen, sie können aber dabei nützlich sein.

Kombination von Modellen

Auch wenn Text, Ereignisse, Bäume und Transformationen auf den ersten Blick sehr unterschiedlich aussehen, so ist es doch möglich, sie zu kombinieren. Die meisten Parser, die DOM-Bäume erzeugen können, unterstützen auch SAX-Ereignisse. Außerdem gibt es eine Reihe von Tools, die DOM-Bäume aus SAX-Ereignissen generieren können und umgekehrt. Einige Tools, die SAX-Ereignisse verarbeiten und erzeugen können, arbeiten intern sogar mit Bäumen: Viele XSLT-Prozessoren arbeiten auf diese Weise. Dabei verwenden sie ein optimiertes, internes Baummodell und nicht das ursprüngliche DOM-Modell. Auch akzeptieren die XSLT-Prozessoren selbst häufig sowohl SAX-Ereignisse als auch DOM-Bäume als Input und können daraus diese Modelle (oder Text) als Output generieren.

Die meisten Programmierer, die direkt auf XML-Dokumente zugreifen möchten, fangen mit DOM-Bäumen an, weil diese anfangs leichter zu verstehen sind. Stoßen sie dann auf Probleme, die man besser in ereignisorientierten Umgebungen lösen kann, können sie entweder ihren Code entsprechend umschreiben – eine umfangreiche Aufgabe – oder die ereignisorientierte und die baumorientierte Verarbeitung kombinieren und aufeinander abstimmen.

  

<< zurück vor >>

 

 

 

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

  


Copyright © 2005 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 "XML in a Nutshell" 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