XML-Workshop, Teil 3: Programmieren mit DOM und SAX
Artikel erschienen in Swiss IT Magazine 2001/20
Das Beschreiben von Daten mit XML nützt wenig, wenn XML-Dokumente von einer Anwendung nicht ausgewertet und weiterbearbeitet werden können. In den beiden letzten Teilen unserer XML-Workshop-Serie (InfoWeek 12/01 und 13/01) haben wir gesehen, wie man Informationen mit XML beschreiben und wie man sich eigene XML-Vokabulare anlegen kann. Die Basis für die Anwendung von XML ist somit gelegt. Eine wichtige Voraussetzung fehlt uns allerdings noch: Die Applikation, welche die Daten weiterverarbeiten soll, muss in der Lage sein, über eine einheitliche Schnittstelle auf die XML-Dokumente zuzugreifen, diese zu manipulieren und je nach dem auch selber zu erzeugen.
Solche Schnittstellen können in allen möglichen Applikationen - Editoren, Datenbanken, Browser, Konvertierungstools etc. - für die unterschiedlichsten Einsatzgebiete zur Anwendung kommen. Hier einige Beispiele:
XML-Dokumente in einen Editor laden, dem Benutzer zur Bearbeitung auf dem Bildschirm präsentieren und Änderungen wieder im Dokument speichern.
XML-Daten für die Verarbeitung in einem fremden System erzeugen und/oder in ein anderes Format konvertieren.
XML-Informationen einlesen, die Beziehung zwischen XML-Elementen auswerten und die Daten in den richtigen Tabellen und Felder der Datenbank ablegen.
Von einem Browser dargestellte Daten entsprechend der User-Eingaben on-the-fly anpassen und am Bildschirm anzeigen.
XML-Dokumente auf ihre Gültigkeit überprüfen und einer anderen Anwendung für die Weiterverarbeitung weitergeben oder im Fehlerfall zurückweisen.
Damit man mit einer Anwendung überhaupt auf XML-Daten zugreifen kann, wird ein API (Application Programming Interface) benötigt. Ein API stellt dem Entwickler die notwendigen Schnittstellen in Form von Objekten und Methoden zur Verfügung, mit der er direkt mit seiner Programmiersprache auf XML-Dokumente zugreifen und diese manipulieren kann. Mit DOM (Document Object Model) und SAX (Simple API for XML) existieren zwei von Grund auf verschiedene XML-APIs:
DOM: Die vom W3C gepflegte Programmierschnittstelle lädt ein XML-Dokument vollständig in den Speicher und stellt dieses der Anwendung als baumartige Struktur zur Bearbeitung bereit.
SAX: Das Simple API for XML ist ein ereignisorientiertes API, das ein XML-Dokument durcharbeitet und die auftretenden Ereignisse an die Applikation weitermeldet.
Die API-Schnittstelle wird in der Regel von einem Parser zur Verfügung gestellt. Der Parser arbeitet das XML-Dokument durch und liefert der Anwendung je nach API die Baumstruktur oder die Ereignisse des XML-Dokuments. Zu den verbreitetsten Parsern gehören MSXML (Microsoft), XML Parser for Java (IBM), XML Parser for Java (Oracle) und Java ProjectX (Sun). Alle unterstützen sowohl DOM Level 1 und SAX Version 1.0. Die Entwicklung bei den Parsern geht derzeit derart schnell voran, dass DOM Level 2 und SAX 2.0 wohl bereits in Kürze bei allen zum Standard gehören dürften.
In diesem Workshop legen wir den Fokus auf das Document Object Model. DOM ist für den Einstieg einfacher zu begreifen und kann bereits mit wenigen Hilfsmitteln (Browser und Editor) genutzt werden. SAX erfordert deutlich mehr Einarbeitungszeit in die Materie und würde die tadellose Beherrschung einer Programmiersprache wie Visual Basic, Java oder C++ erfordern.
DOM ist im Gegensatz zu SAX ein vom World-Wide-Web-Consortium entwickeltes und empfohlenes API. Allerdings legt das W3C hierbei nur die Schnittstelle fest, die eigentliche API-Implementation bleibt den Parser-Herstellern überlassen, was zur Folge hat, dass die verschiedenen DOM-Parser nicht zu hundert Prozent miteinander kompatibel sind. Ähnlich wie bei den Browsern, haben die Entwickler ihre Parser mit eigenen proprietären Erweiterungen für das DOM ausgestattet. Teilweise hat das auch seinen guten Grund: Das W3C hat einige elementare Funktionen wie etwa das Laden von XML-Dokumenten oder das Error-Handling in ihrer Empfehlung nicht berücksichtigt. Zudem umfasst die W3C-Spezifikation des Document Object Model mittlerweile drei verschiedene Ausbaustufen, was der Kompatibilität zwischen den Parsern auch nicht gerade zuträglich ist.
• Level 1: regelt den Zugriff auf XML-Dokumente und umfasst auch die Spezifikationen für den DOM-Zugriff auf HTML-Seiten.
• Level 2: bietet zusätzlich Support für Namensräume und Style Sheets. Weitere neue Features sind Filtering und Ranges. Letztere erleichtern die Bearbeitung von grossen Textblöcken.
• Level 3: Diese Ausbaustufe ist noch nicht verabschiedet und befindet sich derzeit in der Recommendation-Phase. Sie wird Mechanismen zur Anzeige und Formatierung sowie zur Unterstützung von Benutzerinteraktionen bereitstellen.
Ein DOM-basierender XML-Parser lädt ein XML-Dokument in den Speicher und stellt dieses in Form einer Baumstruktur (siehe Diagramm "Der DOM-Dokumentenbaum") einer Anwendung zur Bearbeitung zur Verfügung. Die Baumstruktur besteht aus Knoten (Nodes), die die Bausteine des XML-Dokuments (Elemente, Attribute, Daten) in hierarchischer Form repräsentieren.
Anhand dieses Baums kann sich eine Anwendung zum gewünschten Knoten hinunterhangeln und dort die nötigen Manipulationen wie etwa Löschen oder Zufügen von Knoten oder Ändern von Daten durchführen. Für all diese Operationen stellt DOM die notwendigen Objekte, Methoden und Properties bereit, die sich mit der eigenen Programmiersprache nutzen lassen.
Wie bereits erwähnt, besteht der grosse Vorteil von DOM darin, dass es sehr einfach zu nutzen und auch für Programmierneulinge schnell begreifbar ist. Das Document Object Model bringt aber auch einen gewaltigen Nachteil mit sich: Die Abbildung der Baumstruktur im Speicher bläht ein XML-Dokument etwa um den Faktor 10 auf. Ein 100 KB grosses XML-Dokument verspeist im Memory in etwa 1 MB Speicher. Für die Bearbeitung von grösseren XML-Datenmengen ist DOM also gänzlich ungeeignet. Hier muss dann auf SAX zurückgegriffen werden, das auch für riesige XML-Dateien kaum Ressourcen benötigt.
Für das Aufzeigen unserer Beispiele haben wir uns wegen der breiten Verfügbarkeit für den Microsoft-Parser entschieden, der Bestandteil des Internet Explorer ist. Wir empfehlen die Installation der neuesten MSXML Version 3.0, die via http://msdn.microsoft.com heruntergeladen werden kann. Microsofts XML-Parser kann mit einer ganzen Reihe von Programmiersprachen genutzt werden, darunter Javascript, VBScript, Perl, Visual Basic, Java oder C++. Da Javascript unter Web-Publishern sehr weit verbreitet ist, verwenden wir für unsere Beispiele diese Scriptsprache. Das Gezeigte ist mit etwas Anpassungsaufwand aber auch mit einer anderen Sprache nachvollziehbar.
Um überhaupt mit einem XML-Dokument arbeiten zu können, müssen wir zunächst eine Instanz des MSXML-DOMDocument-Objekts erzeugen und das XML-File in den Speicher laden. Das Bereitstellen des Document-Objekts, das wir auf den Namen xmlDoc taufen, nehmen wir mit dem Kommando new ActiveXObject in der Zeile 6 vor. In Zeile 9 laden wir unser XML-Dokument (test.xml) in den Speicher und weisen es gleichzeitig dem soeben geschaffenen xmDoc-Objekt zu. Mit der Zeile 8 xmlDoc.async=false; wird sichergestellt, dass das Laden und Parsen des XML-Dokuments nicht parallel durchgeführt wird. Der Parser lädt also das gesamte XML-File zunächst in den Speicher und startet erst dann mit der Weiterverarbeitung des Dokuments.
Bevor wir das XML-Dokument bearbeiten können, müssen wir sicherstellen, dass es korrekt geladen wurde und in einem wohlgeformten Format vorliegt. Um allfällige Fehler abzufangen, verfügt der MSXML-Parser über das parseError-Property - eine proprietäre aber nützliche Erweiterung des Microsoft-Parsers -, mit dem festgestellt werden kann, ob der Ladeprozess erfolgreich war. Zudem meldet der Parser über dieses Property auch weiter, wenn das Dokument gegen die Wohlgeformtheit und gegen die Validität verstösst. Ist das der Fall, können über weitere parseError-Properties genauere Informationen wie den Fehlercode (errorCode), Grund (reason) oder die fehlbare Zeile (line) abgerufen werden.
Unser Beispiel zeigt, wie man das parseError-Property abfragen kann (Zeilen 10 bis 22). Ist parseError ungleich 0 (bei korrektem Ladevorgang wird das Property auf 0 gesetzt), wird anschliessend eine Fehlermeldung ausgegeben. Angenommen, unser XML-Dokument wäre wegen einer falschen Verschachtelung nicht wohlgeformt, würde folgende Fehlermeldung ausgespuckt:
Das DOMDocument-Objekt bietet Dutzende von Objekten, Properties und Methoden, um sich durch die Nodes und Elemente eines XML-Dokuments durchnavigieren zu können. Anhand unserer Beispiele wollen wir Ihnen einige der wichtigsten demonstrieren. Eine komplette Referenz des DOMDocument-Objekts finden Sie in der MSDN-Library in der Sektion "Plattform SDK Documentation" im Bereich "XML Developer's Guide".
Das documentElement-Property repräsentiert das Root Element (Wurzelelement) des XML-Dokuments. Dieses verfügt wiederum über das childNodes-Property, das die Liste aller Nodes innerhalb des Wurzelelements wiedergibt. In Zeile 23 übergeben wir somit der Variablen listOfNodes alle Elemente, die sich in unserem XML-Dokument befinden.
nextNode() ist eine Methode von xmlDOC.documentElement.childNodes, die jeweils auf den nächsten Knoten der Node-Liste verweist. Ideal also, um sich durch den XML-Baum durchnavigieren zu können. In Zeile 24 weisen wir mit Hilfe von nextNode() das erste Element des Baums der Variablen currentNode zu. Anschliessend folgt eine while-Schlaufe, mit der die Elementliste abgearbeitet wird. Mit dem Property text des XMLDOMNode-Objekts, greifen wir auf Zeile 31 jeweils auf den Inhalt des gerade selektierten Elements zu und geben diesen auf den Bildschirm aus:
Natürlich will man XML-Dokumente nicht nur am Bildschirm ausgeben, sondern vor allem auch manipulieren können. Folgendes Beispiel zeigt, wie man Elemente einfügen kann. Um dieses selber ausprobieren zu können, ersetzen Sie im oben gezeigten Codebeispiel 1 die Zeilen 21 bis 33 mit dem Codeschnipsel des Codebeispiels 2.
Mit der createElement-Methode in Zeile 21 erzeugen wir ein Element mit der Bezeichnung data_processed. Auf Zeile 22 fügen wir dieses (newElement) mit der appendChild-Methode nach dem dritten Element in unserem XML-Dokument ein. Alternativ können auch Methoden wie insertBefore() und replaceChild() verwendet werden, die das Element vor dem aktuellen Knoten einfügen respektive das aktuelle Element ersetzen. Die Methode item() gibt den angegebenen Knoten zurück; item(0) liefert den ersten, item(2) dementsprechend den dritten Knoten in der Liste. In den Zeilen 24 und 25 fügen wir anschliessend dem neuen Element noch das aktuelle Datum als Inhalt hinzu. Zeile 27 gibt eine Dialogbox mit dem veränderten XML-Baum aus. Das bearbeitete XML-Dokument würde nun wie folgt aussehen:
Neben dem Zufügen von Daten, kann man die Knoten des XML-Dokuments löschen und dessen Daten manipulieren. Auch die Zeilen in Beispiel 3 sind als Ersatz für die Zeilen 21 bis 33 im Beispiel 1 gedacht.
Die Zeilen 21 bis 25 zeigen eine Löschoperation. Zunächst wird das erste Item-Element der Liste der Variablen firstElement zugewiesen. Anschliessend wird das erste Element des Item-Elements mit der Methode removeChild() gelöscht.
Der zweite Teil unseres Beispiels ab Zeile 27 selektiert Elemente anhand ihres Namens (preis) und setzt alle Preise mit dem Wert 5.00 auf 5.50. Für die Elementauswahl verwenden wir die Methode getElementsByTagName, die der Variablen listOfPrices eine Liste aller Elemente mit der Bezeichnung price_per_unit zuweist. Mit der folgenden for-Schlaufe durchsuchen wir alle Elemente nach Preisen mit dem Wert "5.00" und ändern diese im gegebenen Fall auf "5.50". Nach der Abarbeitung würde unserem XML-Dokument also Zeile 4 fehlen, und das Preis-Element des zweiten Items würde neu über den Wert "5.50" verfügen.
Die Änderungen an den oben gezeigten Beispielen werden nur am XML-Dokument vorgenommen, das sich im Speicher befindet. Das Originaldokument auf der Festplatte bleibt dabei unberührt. Damit die Änderungen Bestand haben, muss das erzeugte Dokument zum Schluss auch wieder gesichert werden.
Dafür steht die save()-Methode zur Verfügung, die den geänderten XML-Baum in ein neues XML-Dokument auf der Platte (Zeile 1) oder alternativ in ein neues XML-Objekt im Memory (Zeilen 3 bis 5) speichern kann.