DOM-Parser - Der nichtvalidierende DOM-Parser

Wie schon erwähnt, bildet der DOM-Parser das XML-Dokument auf einen DOM-Tree ab.

Dieser Baum befindet sich im Speicher, und kann dort auch manipuliert werden. Auf diese Weise, kann man ein XML-Dokument relativ leicht verändern.

Allerdings wird dieser Komfort mit einer entsprechend längeren "Parse-Zeit" und mehr Speicherbedarf erkauft. Man sollte DOM-Parser also nur dort einsetzen, wo sie auch wirklich Sinn machen.

Trotz dieser einleitenden Bemerkungen, wird mein erstes Beispiel den DOM-Parser verwenden (äh…hattet ihr in diesem Kapitel wohl nicht erwartet?), obwohl man besser den SAX-Parser benutzt hätte. Da ich jedoch einfache und überschaubare Beispiele bringen möchte, war das leider nötig.

Was soll nun das erste Beispiel tun? Es soll ein Dokument in einen DOM-Tree parsen, und dann das ganze Dokument auf dem Bildschirm ausgeben.

import org.w3c.dom.*;
import com.ibm.xml.parsers.*;

public class IBMDOMParser
{
    public static void main(String[] args)
    {
        IBMDOMParser MyParser = new IBMDOMParser();
        MyParser.parse(args[0]);
    }

    private void parse(String file)
    {
        NonValidatingDOMParser parser = new NonValidatingDOMParser();
        try
        {
           parser.parse(file);
           writeDoc(parser.getDocument().getDocumentElement());
        }
        catch (Exception e)
        {
            System.err.println("Fehler: " + e);
        }
    }

    private void writeDoc(Node node)
    {
        short type = node.getNodeType();
        switch (type)
        {
            case Node.ELEMENT_NODE:
            {
                String name = "<" + node.getNodeName();
                NamedNodeMap attrs = node.getAttributes();
                if (attrs != null)
                {
                    int length = attrs.getLength();
                    for (int i = 0; i < length; i++)
                    {
                        Node attr =  attrs.item(i);
                        name += " " + attr.getNodeName();
                        name += "=\"" + attr.getNodeValue() + "\"";
                    }
                }
                name += ">";
                System.out.println(name);

                NodeList children = node.getChildNodes();
                if (children != null)
                {
                    int length = children.getLength();
                    for (int i = 0; i < length; i++)
                        writeDoc(children.item(i));
                }
                break;
            }
            case Node.TEXT_NODE:
            {
                System.out.println(node.getNodeValue());
                break;
            }
        }
    }
}

Was passiert hier nun im Einzelnen? Zuersteinmal importieren wir das Paket org.w3c.dom. Dieses stammt wie man sieht nicht von IBM sondern vom W3C, und wird in allen mir bekannten Parsern verwendet. Das hat zur Folge, daß die Parser sehr ähnlich zu bedienen sind, und nur an wenigen Stellen Abweichungen auftreten.

In der Methode parse wird ein nichtvalidierender DOM-Parser instantiert, und zwar mit:

NonValidatingDOMParser parser = new NonValidatingDOMParser();

Danach wird aus dem Dokument, mittels der parse-Methode des Parsers, ein DOM-Tree erzeugt.

Um auf den DOM-Tree zugreifen zu können, kann man sich das gesamte geparste XML-Dokument mit parser.getDocument zurückgeben lassen. Man bekommt dann ein Objekt vom Typ Document. Wir brauchen in unserem Beispiel nun noch das Root-Element. Dieses erhält man mit Document.getDocumentElement. Das zurückgegebene Objekt ist folgerichtig vom Typ Element. Mit der Zeile

parser.getDocument().getDocumentElement()

erhalten wir also das Root-Element.

Ich möchte an dieser Stelle nocheinmal kurz darauf eingehen, was der Unterschied zwischen Document und Element ist. Ein Object vom Typ Document enhält das gesamte XML-Dokument, also auch die DTD und alle Kommentare etc, die AUßERHALB des Root-Elementes stehen. Im Gegensatz dazu, enthalten Objekte vom Typ Element, nur die Elemente, Texte, Attribute usw, die INNERHALB dieses Elementes stehen.

Das bedeutet, wenn wir mit unserem Beispielprogramm ein XML-Dokument parsen würden, würden alle Kommentare, die außerhalb des Root-Elements stehen, von vornherein ignoriert werden. Allerdings weden in diesem Beispiel Kommentare sowieso nicht beachtet, so daß das garnicht auffällt. ;-)

Kommen wir nun zur Methode writeDoc. Diese Methode übernimmt ein Node-Object. Ein Node ist eine Gabelung im DOM-Tree. Von Node sind viele andere Objekte abgeleitet, unter anderem auch Document und Element. Node stellt viele nützliche Methoden zur Verfügung, um auf Kinder diesen Knotens zuzugreifen, oder diese zu Manipulieren, bzw. zu löschen oder zu erzeugen. Am besten, ihr schaut mal in die API-Doks. Diese Methoden haben dann natürlich auch alle von Node abgeleiteten Klassen oder Interfaces.

Für unser Beispiel interessant, ist die Methode getNodeType. Damit läßt sich der Typ des Knotens bestimmen (habt ihr euch schon gedacht, gelle?). Folgende Typen gibt es:

  • ATTRIBUTE_NODE
  • CDATA_SECTION_NODE
  • COMMENT_NODE
  • DOCUMENT_FRAGMENT_NODE
  • DOCUMENT_NODE
  • DOCUMENT_TYPE_NODE
  • ELEMENT_NODE
  • ENTITY_NODE
  • ENTITY_REFERENCE_NODE
  • NOTATION_NODE
  • PROCESSING_INSTRUCTION_NODE
  • TEXT_NODE

In meinem Beispiel habe ich allerdings nur die Elemente, deren Attribute, und Text-Elemente berücksichtigt.

Die Aufgabe in meinem Beispiel war nun, den DOM-Tree zu durchlaufen, und alle Elemente auszugeben. Die Traversierung des Baums läßt sich am einfachsten mit einer rekursiven Methode bewerkstelligen. In dieser Methode, in unserem Fall writeDoc, wird ein übergebener Knoten auf seinen Typ geprüft, und entsprechend ausgegeben. Danach wird die Methode für jedes seiner Kinder aufgerufen. In unserem Fall kann allerdings nur ein Element-Knoten Kinder haben.

Ich denke, der Rest des Beispiels sollte leicht verständlich sein, und wenn nicht, lassen sich eventuelle Unklarheiten mit Hilfe der API-Dok sicherlich leicht beseitigen.

Ihr kennt das ja schon, aber ich möchte trotzdem nochmal darauf hinweisen, daß die Formatierung des Outputs, gelinde gesagt, chaotisch ist. Es fehlen sogar die schließenden Tags der Elemente. Ich wollte jedoch das Beispiel nicht durch entsprechenden Code unnötig komplizieren.

Natürlich kann man das Beispiel auch mit dem Sun-Parser verwirklichen. Dabei ändert sich am Code relativ wenig. Zum einen die Importstatements:

import org.w3c.dom.*;
import com.sun.xml.parser.*;
import com.sun.xml.tree.XmlDocumentBuilder;
import java.io.File;

Zum anderen ändert sich die Methode parse:

private void parse(String file)
{
    XmlDocumentBuilder builder = new XmlDocumentBuilder();
    Parser parser = new Parser();
    builder.setParser(parser);
    try
    {
        parser.parse(Resolver.createInputSource(new File(file)));
        writeDoc(builder.getDocument().getDocumentElement());
    }
    catch (Exception e)
    {
        System.err.println("Fehler: " + e);
    }
}

An dieser Stelle wird sehr deutlich, daß der DOM-Parser auf dem SAX-Parser aufsetzt, und aus dessen Events den DOM-Tree erzeugt. Man instantiiert nämlich zuerst einen nichtvalidierenden SAX-Parser. Mit der Methode setParser wird dann nichts anderes gemacht, als den XmlDocumentBuilder als DocumentHandler beim Parser zu registrieren. Ihr könntet stattdessen genausogut schreiben:

parser.setDocumentHandler(builder);

Der XmlDocumentBuilder baut dann den DOM-Tree auf.

Der Vollstänigkeit halber, hier nochmal das vollständige Listing:

import org.w3c.dom.*;
import com.sun.xml.parser.*;
import com.sun.xml.tree.XmlDocumentBuilder;
import java.io.File;

public class SunDOMParser
{
    public static void main(String[] args)
    {
        SunDOMParser MyParser = new SunDOMParser();
        MyParser.parse(args[0]);
    }

    private void parse(String file)
    {
        XmlDocumentBuilder builder = new XmlDocumentBuilder();
        Parser parser = new Parser();
        builder.setParser(parser);
        try
        {
            parser.parse(Resolver.createInputSource(new File(file)));
            writeDoc(builder.getDocument().getDocumentElement());
        }
        catch (Exception e)
        {
            System.err.println("Fehler: " + e);
        }
    }

    private void writeDoc(Node node)
    {
        short type = node.getNodeType();
        switch (type)
        {
            case Node.ELEMENT_NODE:
            {
                String name = "<" + node.getNodeName();
                NamedNodeMap attrs = node.getAttributes();
                if (attrs != null)
                {
                    int length = attrs.getLength();
                    for (int i = 0; i < length; i++)
                    {
                        Node attr =  attrs.item(i);
                        name += " " + attr.getNodeName();
                        name += "=\"" + attr.getNodeValue() + "\"";
                    }
                }
                name += ">";
                System.out.println(name);

                NodeList children = node.getChildNodes();
                if (children != null)
                {
                    int length = children.getLength();
                    for (int i = 0; i < length; i++)
                        writeDoc(children.item(i));
                }
                break;
            }
            case Node.TEXT_NODE:
            {
                System.out.println(node.getNodeValue());
                break;
            }
        }
    }
}

zurück                weiter

nach oben