XML Hauptseminar
Philipp von Bassewitz
Martin Obermeier
Technische Universität München, 30.11.00
Das Document Object Model (DOM) ist eine vom World Wide Web Consortium (www.w3c.org) definierte Programmierschnittstelle (Application Programming Interface, API) für XML- und HTML-Dokumente. XML bedeutet hier nicht nur Text-, sondern allgemeine Dateiformate, die auch beispielsweise Vektorgrafiken beinhalten können.
Das DOM definiert Schnittstellen und Methoden zum erzeugen, durchsuchen, zugreifen, ändern und löschen von Dokumentinhalten. Das serialisieren der Dokumentstruktur (z.B. zum speichern in eine Datei) ist im DOM nicht definiert. Das DOM ist eine abstrakte Schnittstellenbeschreibung in OMG IDL (Object Management Group Interface Definition Language), die zur Anwendung in einer konkreten Sprache implementiert sein muß. Dokumente werden unabhängig von der internen Datenstruktur der jeweiligen Implementation nach außen objektorientiert repräsentiert. Alle Objekte des Dokuments sind in eine hierarchische Baumstruktur eingegliedert und werden im DOM Knoten genannt.
Das DOM garantiert strukturellen Isomorphismus, d.h. jedes XML-Dokument hat eine eindeutige Struktur, die in jeder DOM-Implementation genau gleich aussieht. Das DOM definiert Objekte, Schnittstellen und Semantiken (das Verhalten der Objekte beim Aufruf von Methoden), nicht aber interne Datenstrukturen oder Funktionen der Implementationen. Insbesondere kann DOM auf bestehende APIs aufgesetzt werden, um diese zu standadisieren.
Folgende XML-Datei wird in den meisten Beispielen verwendet. Sie enthält eine Bibliothek mit mehreren Büchern, zu denen jeweils Titel, Autor und Beschreibung gespeichert sind.
<?xml version="1.0"?>
<!DOCTYPE xmllib SYSTEM "libary.dtd">
<xmllib><libary name="Meine Bibliothek">
<book>
<title>Herr der Ringe</title>
<author>J. R. Tolkien</author>
<description>Fantasyklassiker</description>
</book>
<book>
<title>Es</title>
<author>Stephen King</author>
<description>Horrorbestseller</description>
</book>
<book>
<title>Mein Leben</title>
<author>Marcel Reich-Ranicki</author>
<description>Autobiographie</description>
</book>
<book>
<title>Die Firma</title>
<author>John Grisham</author>
<description>Anwaltsthriller</description>
</book>
</libary></xmllib>
Die zugeörige Dokumenttyp-Definition (DTD) lautet:
<!ELEMENT xml (libary)+ >
<!ELEMENT library (book)+ >
<!ELEMENT book (title, author, description?)+ >
<!ATTLIST element name CDATA #IMPLIED>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT description (#PCDATA)>
Dieses Java-Programm gibt die Buchtitel aller Bücher der XML-Datei aus. Auch wenn die einzelnen Objekte noch nicht besprochen sind, erkennt man die einfach Verwendung des DOM-Parsers.
package seminar.xml.dom;
import java.io.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xerces.parsers.DOMParser;
public class PrintTitle {
public PrintTitle() {
}
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(args[0]));
Document doc = parser.getDocument();
// Liste aller <title>-Elemente
NodeList titles = doc.getElementsByTagName("title");
System.out.println("Folgende Bücher sind in ihren Bibliotheken:");
for(int i=0; i<titles.getLength(); i++) {
Node node = titles.item(i);
CharacterData text = (CharacterData)node.getFirstChild();
System.out.println(text.getData());
}
}
catch(Exception e) {
System.out.println("Fehler beim Parsen!");
e.printStackTrace(System.out);
}
}
}
Das DOM ist unterteilt in einzelne Module. Eine DOM-Implementation muß in jedem Fall das Modul XML (=Core) oder HTML und kann beliebige weitere Module implementieren. Einige Module werden später genauer besprochen.
Table t = new Table().Table t = createTable().malloc() und free()), da dies je nach
Implementation anders ist. Die Schnittstelle node ist die Basis-Schnittstelle, von der
alle anderen abgeleitet sind. Sie enthät die wichtigsten
Eigenschaften und Methoden, die auch alle anderen Knoten benötigen
(z.B. cloneNode). Bei vielen Methodenaufrufen
(z.B. getNodesByName()) bekommt man Knoten des Basistyps
node zurück, die dann in den entsprechenden Typ
(z.B. Table) umgewandelt werden können. Da dieses
Umwandeln
(cast) aber je nach Implementierung ein teurer (langsamer) Aufruf sein
kann, gibt es in DOM auch die Möglichkeit, die wichtigsten
Operationen direkt auf dem node-Objekt auszuführen.
Man hat praktisch parallel zur hierarchischen Ansicht auf die
Objekttypen auch eine flache Ansicht auf die API
-- alle Knoten sind einfach node-Objekte. Das bedeutet
natürlich nicht, daß gleichzeitig die Dokumentstruktur flach
ist; hier bleiben die einzelnen Knoten in einer hierarchischen Struktur.
Wegen der beiden Möglichkeiten, eine bestimmte Operation sowohl
auf node als auch auf dem konkreten Typ ausführen zu
können, enthält DOM eine gewisse Redundaz. Je nach
Performance-Anforderungen und eigenem Stil kann der Entwickler
eine der beiden Ansichten bevorzugen.
In DOM ist der Datentyp DOMString definiert als eine
Folge von UTF-16-Zeichen (Unicode-Zeichensatz mit 16 bit pro Zeichen).
Dieser Datentyp kann in Java direkt auf den dortigen Typ
String abgebildet werden, da Java auch Unicode verwendet.
Bei einer DOM-Implementation in C beispielsweise können
Zeichen dagegen nicht direkt auf char abgebildet werden,
da dort nur 8 bit je Zeichen verwendet werden.
DOM unterscheidet, wie XML überhaupt, immer zwischen Groß- und Kleinschreibung (case-sensitive). In Einzelfällen, wie beim verarbeiten von HTML-Tags, ist das anders.
nodeName und
wird mit setAttribute() gesetzt.namespaceURI) sowie localName und wird mit
setAttributeNS() gesetzt.setAttributeNS() gesetzt wird, dann mit
setAttribute() und anschließend mit
getAttributeNS() ausgelesen, kann je nach Implementation
der erste oder zweite gesetzte Wert zurückgeliefert werden.Das Modul Core ist das wichtiste der Module. Hier wird die gesamte Basis-Funktionalität für den Umgang mit XML-Dokumenten definiert (daher auch XML-Modul genannt), die in den anderen Modulen teilweise erweitert (Modul Events, Stylesheets), teilweise durch Spezialisierung (Modul HTML) leichter bedienbar wird. Eine DOM-Implementation muß das Modul Core oder HTML unterstützen.
Document hat 0 oder 1 DocumentType,
der in einer Dokument-Typ-Definition (DTD) festgelegt ist. Das einzige
Element ist das root-Element, welches dann das gesamte
XML-Dokument enthält. Ferner kann es beliebig viele
ProcessingInstruction und Comment Objekte
enthalten.DocumentFragment dient als Container, um beliebige Teile
eines Dokuments zwischenzuspeichern. Es kann also sätliche
Knoten, die in einem XML-Dokument vorkommen können, speichern, bis
auf DocumentType, da es kein vollständiges Dokument ist.
Das erstellen eines Document wäre je nach Implementation
teurer als ein DocumentFragment.
Da Document der oberste Knoten in einem Dokument ist,
muß dieser zuerst neu erstellt oder (bei schon existierender
XML-Datei) vom Parser zurückgegeben werden. Dann können in
diesem Dokument Knoten hinzugefügt oder modifiziert werden.
Dafür enthält diese Schnittstelle Factory-Methoden zum
Erzeugen aller Knotenypen.
Node ist der Basistyp für alle anderen Schnittstellen.
Die hier definierten Felder und Methoden werden also von allen anderen
Objekttypen unterstützt.
NodeList enthält eine ungeordnete List von Knoten, die
einzeln angesprochen werden können. NodeList wird
beispielsweise von der Methode getElementsByName()
zurückgegeben.
Element repräsentiert einen XML-Knoten (z.B. Tabelle,
Tabellenzelle) und kann Attribute haben.
Ein Attribut besteht einfach aus Name und Wert:
package seminar.xml.dom;
import java.io.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xerces.parsers.DOMParser;
public class PrintBooks {
public PrintBooks() {
}
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false);
parser.parse(new InputSource(args[0]));
Document doc = parser.getDocument();
Element root = doc.getDocumentElement();
Element lib = (Element)root.getFirstChild();
System.out.println("Bibliothek '"+lib.getAttribute("name")+"' enthält folgende Bücher:");
Node book = lib.getFirstChild();
// Schleife durch alle -Elemente
while(book != null) {
Node title = book.getFirstChild();
Node author = title.getNextSibling();
CharacterData text1 = (CharacterData)author.getFirstChild();
CharacterData text2 = (CharacterData)title.getFirstChild();
System.out.println(text1.getData()+": '"+text2.getData()+"'");
book = book.getNextSibling();
}
}
catch(Exception e) {
System.out.println("Fehler beim Parsen!");
e.printStackTrace(System.out);
}
}
}
Das Programm hält sich starr an die Struktur der
XML-Datei, die es einliest. Während beim ersten Beispiel
egal war, wo sich die <title>-Tags im Dokument befinden,
läuft dieses Programm nur fehlerfrei, falls die Struktur der
einzulesenden Datei exakt mit der erwarteten Struktur übereinstimmt.
Führt man das Programm mit dem Beispiel-XML-Dokument aus, so
erhält man folgende Ausgabe:
Bibliothek 'Meine Bibliothek' enthält folgende Bücher: J. R. Tolkien: 'Herr der Ringe' Stephen King: 'Es' Marcel Reich-Ranicki: 'Mein Leben' John Grisham: 'Die Firma'
package seminar.xml.dom;
import java.io.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.apache.xml.serialize.*;
import org.apache.xerces.parsers.DOMParser;
public class AddBook {
public AddBook() {
}
public static void main(String[] args) {
try {
// Document einlesen
String filename = args[0];
DOMParser parser = new DOMParser();
parser.parse(new InputSource(filename));
Document doc = parser.getDocument();
Element root = doc.getDocumentElement();
Element lib = (Element)root.getFirstChild();
Node firstbook = lib.getFirstChild();
// neue Elemente erstellen und ins Document einfügen
Element newbook = doc.createElement("book");
Element title = doc.createElement("title");
CharacterData text1 = doc.createTextNode(args[1]);
title.appendChild(text1);
Element author = doc.createElement("author");
CharacterData text2 = doc.createTextNode(args[2]);
author.appendChild(text2);
newbook.appendChild(title);
newbook.appendChild(author);
lib.insertBefore(newbook, firstbook);
// Document drucken
OutputFormat format = new OutputFormat(doc);
format.setLineSeparator(LineSeparator.Windows);
FileOutputStream out = new FileOutputStream(filename);
XMLSerializer xml = new XMLSerializer(out, format);
xml.serialize(doc);
}
catch(Exception e) {
System.out.println("Fehler beim Parsen!");
e.printStackTrace(System.out);
}
}
}
Hier fügt das Programm der Bibliothek ein weiteres Buch hinzu.
Das erste Eingabeargument gibt wieder den Dateinamen des XML-Dokumentes
an, danach folgen der Titel und der Autor des neuen Buchs. Die neuen
Knoten werden mittels der createX(...)-Methoden der Document
Schnittstelle erstellt und an den jeweils entsprechenden Vaterknoten
angehängt. Das neue Buch wird in der Bibliothek als erstes Buch
eingefügt. Zum Schluß wird das erweiterte XML-Dokument unter
dem gleichen Dateinamen wieder abgespeichert.
Das Modul HTML stellt für HTML spezialisierte Schnittstellen zur Verfügung, die die allgemeine Funktionalität von den entsprechenden Schnittstellen des Moduls Core durch Vererbung erhalten.
Für eine große Anzahl von HTML-Tags wurde eine eigene
Schnittstelle definiert. Wir betrachten hier allerdings nur einen kleinen
Auszug davon. All diese Spezialschnittstellen erben ihre
Hauptfunktionalität von HTMLElement und erweitern es um
weitere Attribute und/oder Methoden.
HTMLDocument ist eine Spezialisierung von
Document aus dem Modul Core und repräsentiert das
gesamte HTML-Dokument (geklammert durch die
<html>-Tags). Wichtige Tags sind bereits zu
HTMLCollections zusammengefaßt, einer Art von Listen.
So kann auf eine Liste zugegriffen werden, die auf sämtliche
<img>-Tags verweist. Andere Listen speichern alle
Formulare, Links oder Applets des HTML-Dokumentes.
HTMLElement ist eine Spezialisierung von Element
aus dem Modul Core.
Es speichert bestimmte Attribute wie class, title
und id direkt als DOMString ab. Damit
entfällt der Weg über die getAttribute()-Methode
von Element auf die entsprechenden Werte zuzugreifen.
HTMLCollection entspricht der NodeList des Moduls Core;
es ist eine Menge von Node-Objekten. Auf einen bestimmten
Knoten greift man entweder mittles Index zu oder man gibt einen
Namen an, der im gesuchten Knoten als id –Attributwert
gesetzt ist.
HTMLBodyElement ist eine Spezialisierung von
HTMLElement und repräsentiert den Körper
der HTML-Datei (geklammert durch die <body>-Tags).
Es definiert neue Attribute wie background, bgcolor und
link.
HTMLFormElement ist eine Spezialisierung von
HTMLElement und repräsentiert ein HTML-Formular
(geklammert durch die <form>-Tags).
Neben Attributen wie action, target und method
definiert es noch die beiden Methoden submit() und
reset(), die keinen Rückgabewert haben und die
gleiche Funktion ausführen wie ein Knopfdruck auf den Abschicken- oder
Abbrechen-Knopf eines Formulars.
package seminar.xml.dom;
import java.io.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.w3c.dom.html.*;
import org.apache.xml.serialize.*;
import org.apache.html.dom.HTMLDocumentImpl;
import org.apache.xerces.parsers.DOMParser;
public class ChangeBGColor {
public ChangeBGColor() {
}
public static void main(String[] args) {
try {
String filename = args[0];
DOMParser parser = new DOMParser();
parser.parse(new InputSource(filename));
HTMLDocumentImpl doc = (HTMLDocumentImpl)parser.getDocument();
HTMLBodyElement body = (HTMLBodyElement)doc.getBody();
String color = body.getBgColor();
System.out.println("Aktuelle Hintergrundfarbe ist "+color+".");
color = args[1];
body.setBgColor(color);
System.out.println("Farbe geändert zu "+color+".");
// Document drucken
OutputFormat format = new OutputFormat(doc);
format.setLineSeparator(LineSeparator.Windows);
FileOutputStream out = new FileOutputStream(filename);
HTMLSerializer html = new HTMLSerializer(out, format);
html.serialize(doc);
}
catch(Exception e) {
System.out.println("Fehler beim Parsen!");
e.printStackTrace(System.out);
}
}
}
Dieses Programm setzt bei einem beliebigen HTML- Dokument das bgcolor-Attribut des <body>-Tags auf den eingegeben Wert und speichert die Dateien wieder ab. Eine kleine Textausgabe erscheint zur Information:
Aktuelle Hintergrundfarbe ist red. Farbe geändert zu blue.
Das Modul Traversal erlaubt eine spezialisierte Sicht eines Dokuments. Es umfasst nur vier Schnittstellen.
DocumentTraversal muß von denselben Objekten wie
Document implementiert werden. Es enthält nur zwei Methoden:
eine zum erstellen eines TreeWalkers und eine zum erstellen
eines NodeIterators.
NodeFilter definiert nur eine einzige Methode
acceptNode, die für einen übergebenen Knoten
zurückgibt, ob dieser angezeigt wird oder aussortiert wird. Der
Rückgabewert ist eine vordefinierte Konstante: FILTER_ACCEPT,
FILTER_REJECT oder FILTER_SKIP. Außerdem
definiert dieses Interface Konstanten für eine grobe Auswahl.
SHOW_ALL akzeptiert zuerst einmal alle Knoten,
SHOW_ELEMENT beachtet nur Element-Knoten.
NodeIterator liefert eine flache Sichtweise des Dokuments.
Die einzige Ordnung der Knoten ist die Reihenfolge, in der sie
auch im Quelltext stehen. Welche Knoten betrachtet werden geben ein
NodeFilter und eine SHOW_XXX Konstante an. Es
gibt zwei Methoden, um auf den nächsten bzw. vorangegangen
Knoten zuzugreifen.
TreeWalker behält im Gegensatz zu
NodeIterator die hierarchische Sicht eines XML- Dokumentes
als Baum. Es wird wieder eine Teilmenge aller Knoten mittels
NodeFilter und Konstante ausgewählt, die per
TreeWalker besucht werden kann. Zusätzlich zum
Nachfolger- und Vorgänger-Knoten kann man aber auch auf den
Vater-Knoten, das erste Kind oder den nächsten Geschwister-Knoten
zugreifen.
package seminar.xml.dom;
import java.io.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.w3c.dom.traversal.*;
import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.parsers.DOMParser;
public class WithTreeWalker {
public WithTreeWalker() {
}
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
parser.parse(new InputSource(args[0]));
DocumentImpl doc = (DocumentImpl)parser.getDocument();
TreeWalker jonny = doc.createTreeWalker(doc.getDocumentElement(),
NodeFilter.SHOW_ELEMENT, null, false);
Element lib = (Element)jonny.firstChild(); // erstes -Element
System.out.println("Bibliothek '"+lib.getAttribute("name")+"' enthält folgende Bücher:");
jonny.firstChild(); // erstes -Element
while(true) {
Node title = jonny.firstChild();
Node author = jonny.nextSibling();
CharacterData text1 = (CharacterData)title.getFirstChild();
CharacterData text2 = (CharacterData)author.getFirstChild();
System.out.println(text2.getData()+": '"+text1.getData()+"'");
jonny.parentNode();
if(jonny.nextSibling() == null) return;
}
}
catch(Exception e) {
System.out.println("Fehler beim Parsen!");
e.printStackTrace(System.out);
}
}
}
Das Programm erzeugt die gleiche Ausgabe wie "PrintBooks.java", einem früherem Beispiel:
Bibliothek 'Meine Bibliothek' enthält folgende Bücher: J. R. Tolkien: 'Herr der Ringe' Stephen King: 'Es' Marcel Reich-Ranicki: 'Mein Leben' John Grisham: 'Die Firma'
Der Unterschied besteht darin, daß dieses Programm einen
TreeWalker benutzt, der nur Element-Knoten
betrachtet. Somit werden alle Comments, ProcessingInstructions
usw. übergangen. Das ursprüngliche Programm bekommt durch einen
Kommentar an der falschen Stelle in der XML-Datei schon erhebliche
Probleme.
Views implementieren.DocumentView für verschiedene Ansichten
eines Dokuments.AbstractView für die Verarbeitung von
Benutzerschnittstellen-Ereignissen.