Document Object Model


XML Hauptseminar

Philipp von Bassewitz

Martin Obermeier

Technische Universität München, 30.11.00

Inhalt

1. Einleitung

Was ist DOM?

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.

Was ist DOM nicht?

Vorteile von DOM

Diverses

Beispiel XML-Datei

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

Java-Programm "PrintTitle"

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);
    }
  }
}

2. Module und Konzepte

Module

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.

Speicherverwaltung

Vererbung versa "flache Sichtweise"

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.

Typdefinition DOMString

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.

XML Namensräume

3. Modul Core

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.

Strukturmodell

Schnittstelle Document

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.

Schnittstelle Node

Node ist der Basistyp für alle anderen Schnittstellen. Die hier definierten Felder und Methoden werden also von allen anderen Objekttypen unterstützt.

Sonstige Schnittstellen

NodeList

NodeList enthält eine ungeordnete List von Knoten, die einzeln angesprochen werden können. NodeList wird beispielsweise von der Methode getElementsByName() zurückgegeben.

Element

Element repräsentiert einen XML-Knoten (z.B. Tabelle, Tabellenzelle) und kann Attribute haben.

Attr

Ein Attribut besteht einfach aus Name und Wert:

Java-Programm "PrintBooks"

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'

Java-Programm "AddBook"

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.

4. Modul HTML

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

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

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

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

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

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.

Java-Programm "ChangeBGColor"

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.

5. Modul Traversal

Das Modul Traversal erlaubt eine spezialisierte Sicht eines Dokuments. Es umfasst nur vier Schnittstellen.

DocumentTraversal

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

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

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

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.

Java-Programm "WithTreeWalker"

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.

6. Weitere Module

Views

Stylesheets

CSS, CSS2

Events

User Interface Events

Mouse Events

Mutuation Events

Range

7. Schlußbemerkungen

Implementierte Parser

Quellen: www.w3c.org/DOM

Aussicht auf nächstes Thema: SAX

Ende