• +49-(0)721-402485-12
Ihre Experten für XML, XQuery und XML-Datenbanken

JDOM

JDOM stellt ein Verfahren zur Verfügung, um innerhalb von Java durch eine Baumstruktur auf ein XML-Dokument zuzugreifen, und ist DOM in dieser Hinsicht etwas ähnlich. Allerdings wurde es speziell für Java entwickelt (erinnern Sie sich noch an die Besprechung der Sprachenbindungen für DOM?), erscheint einem Java-Entwickler also in vielfacher Weise intuitiver als DOM. Ich werde diese Aspekte von JDOM im Verlauf dieses Kapitels beschreiben, außerdem werde ich über spezielle Fälle sprechen, in denen die Verwendung von SAX, DOM oder JDOM angebracht ist. Und für eine Zusammenstellung sämtlicher Details zu JDOM sollten Sie auf der Website http://www.jdom.org nachschauen.

Darüber hinaus – ein wichtiger Punkt – ist JDOM eine Open Source-API. Und weil es von der API noch keine endgültige 1.0-Version gibt, bleibt sie auch flexibel.1 Sie haben die Möglichkeit, selbst Änderungen vorzuschlagen und zu implementieren. Wenn Sie merken sollten, daß Ihnen JDOM bis auf ein ärgerliches kleines Detail gefällt, können Sie uns helfen, Lösungen für Ihr Problem zu finden. In diesem Kapitel behandle ich den aktuellen Status von JDOM, besonders im Hinblick auf die Standardisierung, und die Grundlagen der Anwendung dieser API, und ich gebe Ihnen einige funktionierende Beispiele.

Die Grundlagen

Die Kapitel DOM und DOM für Fortgeschrittene sollten Ihnen ein ziemlich gutes Verständnis für den Umgang mit Baum-Darstellungen von XML vermittelt haben. Wenn ich also sage, daß JDOM ebenfalls eine Baum-basierte Darstellung eines XML-Dokuments bietet, ist das für Sie ein guter Startpunkt, um zu verstehen, wie sich JDOM verhält. Als Hilfe für Sie, damit Sie verstehen, wie die Klassen in JDOM mit den XML-Strukturen zusammenhängen, sollten Sie sich Abbildung 7-1 ansehen, in der ein UML-Modell der Kernklassen von JDOM gezeigt wird.

Abbildung 7-1: UML-Modell der JDOM-Kernklassen
UML-Modell der JDOM-Kernklassen

Wie Sie sehen, sprechen die Namen der Klassen für sich selbst. Den Kern der JDOM-Struktur bildet das Document-Objekt; es ist sowohl die Darstellung eines XML-Dokuments als auch ein Container für alle anderen JDOM-Strukturen. Element repräsentiert ein XML-Element, Attribute ein Attribut und so weiter. Wenn Sie sich allerdings in DOM vertieft haben, könnten Sie denken, daß hier in JDOM einiges fehlt.

Wo ist zum Beispiel die Text-Klasse? Wie Sie sich wohl erinnern, folgt DOM einem sehr strengen Baummodell, und die Inhalte von Elementen werden tatsächlich als Kindknoten eines Elementknotens angesehen. In JDOM wurde dies in vielen Fällen als unbequem erachtet, deshalb bietet die API in der Klasse Element getText( )-Methoden an. Dies ermöglicht es, die Inhalte eines Elements aus diesem Element selbst zu erhalten, und deshalb gibt es keine Text-Klasse. Dies wurde als intuitiverer Ansatz für diejenigen Java-Entwickler erachtet, die mit XML, DOM oder einigen Extravaganzen von Bäumen weniger vertraut sind.

Ein weiterer wichtiger Punkt, auf den Sie achten sollten, ist, daß Sie keine List-Klassen wie die SAX-Klasse Attributes oder die DOM-Klassen NodeList und NamedNodeMap finden. Dies ist eine Erleichterung für Java-Entwickler; es schien so, daß die Verwendung von Java-Collection-Klassen (java.util.List, java.util.Map usw.) eine vertraut erscheinende und einfache API für die XML-Nutzung bieten würde. DOM muß über Sprachgrenzen hinweg seine Dienste tun (erinnern Sie sich an die Java-Sprachbindungen im Kapitel DOM?) und kann sich nicht sprachspezifischer Vorteile wie Java-Collections bedienen. Wenn Sie zum Beispiel die Methode getAttributes( ) der Klasse Element aufrufen, erhalten Sie ein List-Objekt zurück; Sie können diese List natürlich bearbeiten wie jede andere Java-List, ohne neue Methoden oder eine andere Syntax nachschlagen zu müssen.

Ein weiterer Grundsatz von JDOM, der es von DOM unterscheidet, aber nicht ganz so offensichtlich ist, besteht darin, daß JDOM eine API aus konkreten Klassen ist. Mit anderen Worten: Element, Attribute, ProcessingInstruction, Comment und der Rest sind allesamt Klassen, die direkt mit dem Schlüsselwort new instantiiert werden können. Der Vorteil ist hier, daß keine Factories benötigt werden, da Factories im Code oft lästig sein können. Die Erzeugung eines neuen JDOM-Dokuments würde zum Beispiel so funktionieren:

Element rootElement = new Element("root");
Document document = new Document(rootElement);

Ganz einfach. Auf der anderen Seite kann die Nichtverwendung von Factories auch als Nachteil angesehen werden. Auch wenn Sie Unterklassen der JDOM-Klassen bilden können, müssen Sie diese in Ihrem Code stets explizit verwenden:

element.addContent(new FooterElement("Copyright 2001"));

Hier ist FooterElement eine Unterklasse von org.jdom.Element und führt einiges an grundlegender Verarbeitung durch (es könnte zum Beispiel einige Elemente hinzufügen, die eine Fußzeile ausgeben). Da es eine Unterklasse von Element ist, kann es durch das übliche Mittel zur Elementvariable hinzugefügt werden, nämlich durch die Methode add-Content( ). Allerdings gibt es keine Möglichkeit, eine Elementunterklasse zu definieren und festzulegen, daß sie immer für die Elementinstantiierung verwendet werden soll, etwa folgendermaßen:

// Dieser Code funktioniert nicht!!
JDOMFactory factory = new JDOMFactory(  );
factory.setDocumentClass("javaxml2.BrettsDocumentClass");
factory.setElementClass("javaxml2.BrettsElementClass");

Element rootElement = JDOMFactory.createElement("root");
Document document = JDOMFactory.createDocument(rootElement);

Die Idee besteht darin, daß nach der Erzeugung der Factory spezielle Unterklassen von JDOM-Strukturen als Klassen zur Verwendung für solche Strukturen festgelegt werden können. Dann wird (zum Beispiel) jedesmal, wenn ein Element durch die Factory erzeugt wird, die Klasse javaxml2.BrettsElementClass statt der Standardklasse org.jdom.Element verwendet.

Die Unterstützung dieser Funktionalität als Option ist in Entwicklung, wenn auch nicht als Standardmittel der Arbeit mit JDOM. Das bedeutet, daß es in der Open Source-Welt möglich wäre, daß diese Funktionalität zu der Zeit, da Sie dies lesen, zur Verfügung steht oder vielleicht dann, wenn JDOM in seiner endgültigen 1.0-Form existiert. Unter http://www.jdom.org können Sie sich über die neuesten Entwicklungen informieren.

Ein letzter wichtiger Aspekt von JDOM ist sein Ein- und Ausgabemodell. Als erstes sollten Sie sich merken, daß JDOM kein Parser ist; es ist eine XML-Dokumentendarstellung in Java. Mit anderen Worten ist es, wie DOM und SAX, ein Satz von Klassen, die verwendet werden können, um die Daten zu manipulieren, die ein Parser zur Verfügung stellt. Infolgedessen muß sich JDOM für das Lesen von XML im Rohzustand auf einen Parser verlassen.2 Es kann auch SAX-Ereignisse oder einen DOM-Baum als Eingabe entgegennehmen, genau wie Instanzen von JDBC-ResultSet und anderes. Um dies zu vereinfachen, bietet JDOM ein spezielles Package nur für die Eingabe an: org.jdom.input.

Dieses Package stellt builder-Klassen zur Verfügung; die beiden, die Sie wahrscheinlich am häufigsten verwenden werden, sind SAXBuilder und DOMBuilder. Diese bauen die JDOM-Kernstruktur, nämlich ein JDOM-Dokument, aus einem Satz von SAX-Ereignissen oder aus einem DOM-Baum auf. Da JDOM allmählich zum Standard wird (näheres im Abschnitt »Ist JDOM ein Standard?« am Ende dieses Kapitels), steht auch zu erwarten, daß in Parser-Projekten wie Apache Xerces oder Crimson von Sun eine direkte JDOM-Unterstützung hinzukommen wird.

Für den Umgang mit Eingabestreams, Dateien oder Dokumenten auf Datenträgern oder für den Aufbau aus existierendem XML, das sich nicht in einem DOM-Baum befindet, ist der SAXBuilder die beste Lösung. Er ist schnell und effizient, genau wie SAX. Die Verwendung des Builders ist ein Kinderspiel:

SAXBuilder builder = new SAXBuilder(  );
Document doc = builder.build(new FileInputStream("contents.xml"));

Ich werde im Code in diesem Kapitel detaillierter darauf eingehen, aber Sie können schon sehen, daß es nicht viel Anstrengung kostet, Zugriff auf XML zu erhalten. Wenn sich Ihr Dokument bereits in einer DOM-Struktur befindet, sollten Sie den DOMBuilder verwenden, der eine schnelle Konvertierung von der einen API zur anderen durchführt:

DOMBuilder builder = new DOMBuilder(  );
Document doc = builder.build(myDomDocumentObject);

Das erklärt sich beinahe von selbst. Es konvertiert im wesentlichen ein org.w3c.dom.Document in ein org.jdom.Document. Der Vorgang der Konvertierung eines JDOM-Dokuments zurück in eine dieser Strukturen funktioniert im Prinzip genauso, eben einfach umgekehrt; das Package org.jdom.output wird für diese Aufgaben verwendet. Um JDOM-Strukturen wieder in DOM zu überführen, wird DOMOutputter verwendet:

DOMOutputter outputter = new DOMOutputter(  );
org.w3c.dom.Document domDoc = outputter.output(myJDOMDocumentObject);

Die Übernahme eines JDOM-Documents und das Erzeugen von SAX-Ereignissen daraus funktioniert auf ähnliche Art und Weise:

SAXOutputter outputter = new SAXOutputter(  );
outputter.setContentHandler(myContentHandler);
outputter.setErrorHandler(myErrorHandler);
outputter.output(myJDOMDocumentObject);

Das funktioniert genau wie der Umgang mit normalen SAX-Ereignissen, indem Sie Content-Handler, Fehlerhandler und den Rest registrieren und dann aus dem JDOM-Document-Objekt heraus, das der Methode output( ) übergeben wurde, Ereignisse für diese Handler abschicken.

Die letzte Ausgabeklasse, und die, mit der Sie wahrscheinlich häufiger arbeiten werden als mit allen anderen, ist org.jdom.output.XMLOutputter. Sie gibt XML in einen Stream oder Writer aus, der eine Netzwerkverbindung, eine Datei oder eine andere Struktur umhüllt, an die Sie XML senden möchten. Tatsächlich stellt diese Klasse eine praxistaugliche Version der Klasse DOMSerializer aus dem Kapitel DOM dar, bis auf die Tatsache natürlich, daß sie mit JDOM statt mit DOM arbeitet. Die Anwendung von XMLOutputter funktioniert folgendermaßen:

XMLOutputter outputter = new XMLOutputter(  );
outputter.output(jdomDocumentObject, new FileOutputStream("results.xml"));

Das war es also schon; die Ein- und Ausgabe von JDOM komplett in einigen wenigen Abschnitten. Eine letzte wichtige Anmerkung, die in Abbildung 7-2 veranschaulicht wird: Es ist sehr einfach, eine »Schleife« einzurichten, weil die gesamte Ein- und Ausgabe in JDOM ein Teil der eigentlichen API ist. Mit anderen Worten: Sie können eine Datei als Eingabe verwenden, sie in JDOM bearbeiten, sie als SAX, DOM oder in eine Datei ausgeben und dieses Ergebnis wieder als Eingabe verwenden, was die Schleife neu startet. Dies ist besonders hilfreich in nachrichtenbasierten Anwendungen oder in Fällen, in denen JDOM als Bindeglied zwischen anderen Komponenten verwendet wird, die XML ausgeben und aufnehmen.

Abbildung 7-2: Ein- und Ausgabeschleifen in JDOM
Ein- und Ausgabeschleifen in JDOM

Das war noch keine vollständige Betrachtung von JDOM, aber es vermittelt Ihnen genügend Informationen, um loszulegen, und ich zeige Ihnen die Sachen sowieso lieber im Kontext von funktionierendem Code! Deshalb wollen wir uns ein Hilfsprogramm anschauen, das Java-Eigenschaftsdateien nach XML konvertiert.

PropsToXML

Um die Aufgabe, JDOM zu erlernen, mit echtem Code zu illustrieren, möchte ich die Klasse PropsToXML einführen. Diese Klasse ist ein Hilfsprogramm, das eine Standard-Java-Eigenschaftsdatei aufnimmt und in ein XML-Äquivalent konvertiert. Viele Entwickler da draußen haben sich ein Werkzeug gewünscht, um genau diese Aufgabe zu erledigen; es ermöglicht die einfache Umstellung herkömmlicher Anwendungen, die noch Eigenschaftsdateien verwenden, auf die Verwendung von XML, ohne die zusätzliche Belastung, die Konfigurationsdateien von Hand konvertieren zu müssen.

Falls Sie noch nie mit Java-Eigenschaftsdateien gearbeitet haben: Es handelt sich im wesentlichen um Dateien mit Name/Wert-Paaren, die auf einfache Weise durch einige Java-Klassen gelesen werden können (zum Beispiel durch die Klasse java.util.Properties). Diese Dateien sehen oft so ähnlich aus wie Beispiel 7-1, und tatsächlich werde ich dieses Beispiel einer Eigenschaftsdatei im gesamten Rest des Kapitels verwenden. Es stammt übrigens von dem Application-Server Enhydra.

Beispiel 7-1: Eine typische Java-Eigenschaftsdatei

#
# Properties added to System properties
#

# sax parser implementing class
org.xml.sax.parser="org.apache.xerces.parsers.SAXParser"

#
# Properties used to start the server
#

# Class used to start the server
org.enhydra.initialclass=org.enhydra.multiServer.bootstrap.Bootstrap

# initial arguments passed to the server (replace command line args)
org.enhydra.initialargs="./bootstrap.conf"

# Classpath for the parent top enhydra classloader
org.enhydra.classpath="."

# separator for the classpath above
org.enhydra.classpath.separator=":"

Keine große Sache, oder? Nun, unter Verwendung einer Instanz der Java-Klasse Properties können Sie diese Eigenschaften in das Objekt hineinladen (mit Hilfe der Methode load(InputStream inputStream)) und dann damit arbeiten wie mit einer Hashtable. Tatsächlich erweitert die Klasse Properties in Java die Klasse Hashtable; nett, oder? Das Problem ist, daß viele Leute diese Dateien wie das Beispiel schreiben, also mit Namen, deren Bestandteile durch einen Punkt (.) getrennt sind, um eine Art hierarchische Struktur zu bilden. In dem Beispiel gäbe es eine oberste Ebene (die Eigenschaftsdatei selbst), dann den Knoten org, unter diesem dann die Knoten xml und enhydra sowie einige Knoten unterhalb des enhydra-Knotens mit einigen Werten. Sie werden also meist eine Struktur wie in Abbildung 7-3 haben.

Abbildung 7-3: Erwartete Struktur der Eigenschaften aus Beispiel 7-1
Erwartete Struktur der Eigenschaften aus Beispiel 7-1

Auch wenn sich das gut anhört, stellt Java kein Mittel zur Verfügung, auf diese Weise auf die Name/Wert-Paare zuzugreifen; dem Punkt wird kein besonderer Wert beigemessen, sondern er wird einfach als normales Zeichen behandelt. Während Sie also folgendes tun können

String classpathValue = Properties.getProperty("org.enhydra.classpath");

können Sie das hier nicht tun:

List enhydraProperties = Properties.getProperties("org.enhydra");

Sie erwarten vielleicht (zumindest tue ich das!), daß letzteres funktioniert und Ihnen sämtliche Untereigenschaften der Struktur org.enhydra liefert (org.enhydra.classpath, org.enhydra.initialargs usw.). Leider ist dies aber kein Bestandteil der Klasse Properties. Aus diesem Grund mußten schon viele Entwickler ihre eigenen kleinen Wrapper-Methoden um dieses Objekt herum schreiben, was natürlich kein Standard ist und ein wenig Kopfschmerzen bereitet. Wäre es nicht angenehm, wenn diese Informationen in XML aufgebaut werden könnten, in dem Operationen wie das zweite Beispiel einfach sind? Genau dafür möchte ich Code schreiben, und ich werde JDOM verwenden, um diese API zu demonstrieren.

Wie schon in früheren Kapiteln ist es auch hier das Einfachste, mit dem Grundgerüst der Klasse zu beginnen und dieses dann auszugestalten. Ich möche die Klasse PropsToXML so einrichten, daß sie eine Eigenschaftsdatei entgegennimmt, aus der die Eingabe erfolgt, sowie den Namen einer weiteren Datei, in die die Ausgabe von XML vorgenommen wird. Die Klasse liest die Eigenschaftsdatei ein, konvertiert sie mit Hilfe von JDOM in ein XML-Dokument und gibt dies in die angegebene Datei aus. Beispiel 7-2 bringt den Stein ins Rollen.

Beispiel 7-2: Das Grundgerüst der Klasse PropsToXML

package javaxml2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

public class PropsToXML {
    
    /**
     * <p> Dies nimmt die übergebene Eigenschaftsdatei entgegen und
     *   konvertiert diese Datei in eine XML-Darstellung, die
     *   dann als XML-Dokument mit dem angegebenen Namen ausgegeben wird.</p>
     *
     * @param propertiesFilename Java-Eigenschaftsdatei, die eingelesen werden soll.
     * @param xmlFilename Datei, in die die XML-Darstellung ausgegeben werden soll.
     * @throws <code>IOException</code> - wenn Fehler auftreten.
     */
    public void convert(String propertiesFilename, String xmlFilename)
        throws IOException {

        // Java-Eigenschaftsobjekt holen
        FileInputStream input = new FileInputStream(propertiesFilename);
        Properties props = new Properties(  );
        props.load(input);
        
        // In XML konvertieren
        convertToXML(props, xmlFilename);
    }

    /**
     * <p> Dies regelt die Details der Konvertierung eines
     *  <code>Properties</code>-Objekts in ein XML-Dokument. </p>
     *
     * @param props <code>Properties</code>-Objekt, das als Eingabe verwendet wird.
     * @param xmlFilename Datei, in die XML ausgegeben wird.
     * @throws <code>IOException</code> - wenn Fehler auftreten.
     */
    private void convertToXML(Properties props, String xmlFilename)
        throws IOException {

        // Der JDOM-Konvertierungscode wird hier eingefügt
    }

    /**
     * <p> Liefert einen statischen Einstiegspunkt für den Start. </p>
     */
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Anwendung: java javaxml2.PropsToXML " +
                "[Eigenschaftsdatei] [XML-Datei für die Ausgabe]");
            System.exit(0);
        }
        
        try {
            PropsToXML propsToXML = new PropsToXML(  );
            propsToXML.convert(args[0], args[1]);
        } catch (Exception e) {
            e.printStackTrace(  );
        }        
    }
}

Das einzig Neue an diesem Code ist das Java-Properties-Objekt, das ich kurz erwähnt habe. Der übergebene Eigenschaftsdateiname wird in der Methode load( ) verwendet, und dieses Objekt wird an eine Methode weitergegeben, die JDOM verwendet und auf die ich mich als nächstes konzentrieren werde.

Nachdem dem Code die Eigenschaften in einer (eher) verwertbaren Form zur Verfügung stehen, wird es Zeit, mit der Anwendung von JDOM zu beginnen. Die erste Aufgabe besteht darin, ein JDOM-Document zu erzeugen. Damit dies passiert, müssen Sie ein Wurzelelement für das Dokument erzeugen, indem Sie die Element-Klasse von JDOM verwenden. Da ein XML-Dokument nicht ohne Wurzelelement existieren kann, wird eine Instanz der Klasse Element als Eingabe in den Konstruktor der Klasse Document benötigt.

Die Erzeugung eines Element-Objekts erfordert lediglich die Übergabe des Elementnamens. Es gibt alternative Versionen, die auch Namensraum-Informationen entgegennehmen, und ich werde diese ein wenig später besprechen. Für den Moment ist es das Einfachste, den Namen des Wurzelelements zu benutzen, und da dieser ein beliebiger Name der obersten Ebene sein muß (um sämtliche verschachtelten Eigenschaften enthalten zu können), verwende ich »properties« im Code. Nachdem dieses Element angelegt wurde, dient es dazu, ein neues JDOM-Document zu erzeugen.

Anschließend ist die Behandlung der Eigenschaften in der übergegebenen Datei an der Reihe. Die Liste der Eigenschaftsnamen wird durch die Methode propertyNames( ) des Properties-Objekts als Java-Enumeration zurückgeliefert. Nachdem ein Name verfügbar ist, kann er benutzt werden, um mit Hilfe der Methode getProperty( ) den Eigenschaftswert zu erhalten.

Zu diesem Zeitpunkt haben Sie bereits das Wurzelelement des neuen XML-Dokuments, den Eigenschaftsnamen, der hinzugefügt werden soll, und den Wert dieser Eigenschaft. Und dann, wie in jedem guten Programm, gehen Sie alle anderen Eigenschaften schrittweise bis zum Ende durch. Bei jedem Schritt wird diese Information an eine neue Methode weitergegeben, nämlich an createXMLRepresentation( ). Diese Methode bildet den technischen Rahmen für die Durchführung der Konvertierung einer einzelnen Eigenschaft in einen Satz von XML-Elementen. Fügen Sie den hier dargestellten Code zu Ihrer Quelldatei hinzu:

    private void convertToXML(Properties props, String xmlFilename)
        throws IOException {
    
        // Ein neues JDOM-Dokument mit dem Wurzelelement "properties" erzeugen
        Element root = new Element("properties");
        Document doc = new Document(root);
        
        // Die Eigenschaftsnamen holen
        Enumeration propertyNames = props.propertyNames(  );
        while (propertyNames.hasMoreElements(  )) {
            String propertyName = (String)propertyNames.nextElement(  );
            String propertyValue = props.getProperty(propertyName);
            createXMLRepresentation(root, propertyName, propertyValue);
        }        
        
        // Das Dokument in die angegebene Datei ausgeben
        XMLOutputter outputter = new XMLOutputter("  ", true);
        FileOutputStream output = new FileOutputStream(xmlFilename);
        outputter.output(doc, output);
    }

Machen Sie sich fürs Erste keine Sorgen über die letzten paar Zeilen, die das JDOM-Document ausgeben. Ich werde dies im nächsten Abschnitt besprechen, aber zuerst möchte ich die Methode createXMLRepresentation( ) behandeln, die die konkreten Arbeitsschritte für den Umgang mit einer einzelnen Eigenschaft enthält sowie für die Erzeugung von XML aus dieser.

Der einfachste (und logischerweise der erste) Schritt bei der Umwandlung einer Eigenschaft in XML besteht darin, daß der Name der Eigenschaft geholt und ein Element dieses Namens erzeugt wird. Sie haben bereits gesehen, wie das funktioniert; übergeben Sie einfach den Namen des Elements an dessen Konstruktor. Nachdem das Element erzeugt wurde, legen Sie den Wert der Eigenschaft als Textwert des Elements fest. Dies kann auf einfache Art und Weise mit Hilfe der Methode setText( ) erreicht werden, die natürlich einen String entgegennimmt. Nachdem das Element einsatzbereit ist, kann es durch die Methode addContent( ) als Kind des Wurzelelements hinzugefügt werden.

Tatsächlich kann der Methode addContent( ) eines Elements jedes erlaubte JDOM-Konstrukt übergeben werden, da sie durch Überladung all diese unterschiedlichen Typen akzeptiert. Dazu zählen Instanzen der JDOM-Klassen Entity, Comment, ProcessingInstruction und andere. Aber dazu komme ich später; fügen wir vorerst einfach die folgende Methode zu Ihrer Quelldatei hinzu:

    /**
     * <p> Dies konvertiert eine einfache Eigenschaft und deren Wert
     *  in ein XML-Dokument mit Textwert. </p>
     *
     * @param root JDOM-Wurzel-<code>Element</code>, das Kindelemente erhält.
     * @param propertyName Name, auf dem die Elementerzeugung beruht.
     * @param propertyValue Wert, der für die Eigenschaft genutzt wird.
     */
    private void createXMLRepresentation(Element root, 
                                         String propertyName,
                                         String propertyValue) {
        
        Element element = new Element(propertyName);
        element.setText(propertyValue);
        root.addContent(element);
    }

An dieser Stelle können Sie die Quelldatei tatsächlich kompilieren, und anschließend die daraus resultierende PropsToXML-Klasse benutzen. Übergeben Sie eine Eigenschaftsdatei (Sie können sie eintippen oder die Datei enhydra.properties herunterladen, die weiter oben in diesem Kapitel gezeigt wurde) sowie einen Dateinamen für die Ausgabedatei, wie hier gezeigt:3

/javaxml2/build $ java javaxml2.PropsToXML \
                  /javaxml2/ch07/properties/enhydra.properties \
                  enhydraProps.xml

Dies schwirrt einen Sekundenbruchteil lang herum und erzeugt dann die Datei4 enhydraProps.xml. Öffnen Sie diese, sie sollte so aussehen wie Beispiel 7-3.

Beispiel 7-3: Erste Version des Dokuments enhydraProps.xml

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <org.enhydra.classpath.separator>":"</org.enhydra.classpath.separator>
  <org.enhydra.initialargs>"./bootstrap.conf"</org.enhydra.initialargs>
  <org.enhydra.initialclass>org.enhydra.multiServer.bootstrap.Bootstrap
</org.enhydra.initialclass>
  <org.enhydra.classpath>"."</org.enhydra.classpath>
  <org.xml.sax.parser>"org.apache.xerces.parsers.SAXParser"
</org.xml.sax.parser>
</properties>

In etwa 50 Zeilen Code sind Sie den Weg von Java-Eigenschaften zu XML gegangen. Allerdings ist dieses XML-Dokument noch nicht viel besser als die Eigenschaftsdatei; es gibt noch immer keine Möglichkeit, die Eigenschaft org.enhydra.initialArgs zu der Eigenschaft org.enhydra.classpath in Beziehung zu setzen. Unsere Aufgabe ist noch nicht erledigt.

Anstatt den Eigenschaftsnamen als den Elementnamen zu verwenden, soll der Code den Eigenschaftsnamen jeweils an den Punkten aufteilen. Für jeden dieser »Unternamen« muß ein Element erzeugt und zum Elementstapel hinzugefügt werden. Danach kann der Prozeß wiederholt werden. Für den Eigenschaftsnamen org.xml.sax sollte sich die folgende XML-Struktur ergeben:

<org>
  <xml>
    <sax>[Eigenschaftswert]</sax>
  </xml>
</org>

Bei jedem Schritt besteht der Trick in der Verwendung des Element-Konstruktors und der Methode addContent( ); und nachdem der Name vollständig auseinandergenommen wurde, kann die Methode setText( ) benutzt werden, um den Textwert des letzten Elements einzustellen. Die einfachste Möglichkeit ist es, ein neues Element namens current zu erzeugen und es als »Zeiger« zu verwenden (es gibt keine Zeiger in Java – das ist bloß ein Begriff); er wird stets auf dasjenige Element zeigen, zu dem Inhalt hinzugefügt werden soll. Bei jedem Schritt muß der Code auch überprüfen, ob das Element, das hinzugefügt werden soll, bereits existiert. Zum Beispiel erzeugt die erste Eigenschaft, org.xml.sax, ein org-Element. Wenn die nächste Eigenschaft (org.enhydra.classpath) hinzugefügt wird, braucht das Element org nicht noch einmal erzeugt zu werden.

Um dies zu vereinfachen, wird die Methode getChild( ) benutzt. Diese Methode nimmt den Namen des Kindelements entgegen, das ermittelt werden soll, und steht allen Instanzen der Klasse Element zur Verfügung. Wenn das angegebene Kindelement existiert, wird es zurückgegeben. Falls nicht, wird der Wert null zurückgegeben, und bei diesem kann unser Code ansetzen. Mit anderen Worten: Wenn der Rückgabewert ein Element ist, wird es zum Element current, und es muß kein neues Element erzeugt werden (es existiert bereits).

Ist der Rückgabewert des Aufrufs von getChild( ) jedoch null, dann muß ein neues Element mit dem aktuellen Unternamen erzeugt und als Inhalt zum Element current hinzugefügt werden; anschließend wird der Zeiger current im Baum abwärts bewegt. Schließlich, nachdem die Iteration vorbei ist, kann der Textinhalt der Eigenschaft zu dem Blattelement hinzugefügt werden, das sich (angenehmerweise) als das Element herausstellt, auf das der Zeiger current verweist. Fügen Sie diesen Code zu Ihrer Quelldatei hinzu:

    private void createXMLRepresentation(Element root, 
                                         String propertyName,
                                         String propertyValue) {
        
        /*           
        Element element = new Element(propertyName);
        element.setText(propertyValue);
        root.addContent(element);
        */
        
        int split;
        String name = propertyName;
        Element current = root;
        Element test = null;
              
        while ((split = name.indexOf(".")) != -1) {
            String subName = name.substring(0, split);
            name = name.substring(split+1);
            
            // Prüfen, ob Element vorhanden  
            if ((test = current.getChild(subName)) == null) {
                Element subElement = new Element(subName);
                current.addContent(subElement);
                current = subElement;
            } else {
                current = test;
            }
        }
        
        // Nach Verlassen der Schleife bleibt der Name des letzten Elements übrig
        Element last = new Element(name);                        
        last.setText(propertyValue); 
        current.addContent(last);
    }

Nach dem Einfügen dieser Ergänzung kompilieren Sie das Programm erneut und starten es nochmals. Dieses Mal sollte Ihre Ausgabe viel freundlicher aussehen, wie in Beispiel 7-4 gezeigt wird.

Und genau so schnell, wie Sie in JDOM eingestiegen sind, sind Sie auch schon damit vertraut. Allerdings könnte Ihnen auffallen, daß das XML-Dokument eine der Regeln aus dem Überblick über das Dokumentdesign verletzt, die im Kapitel Einstieg in XML eingeführt wurden (in dem Abschnitt, der die Details der Verwendung von Elementen gegen die Verwendung von Attributen abwägt). Wie Sie sehen, hat jeder Eigenschaftswert einen einzelnen Textwert. Eigentlich würde dies eher dafür sprechen, daß die Eigenschaftswerte als Attribute des letzten Elements auf dem Stapel geeigneter wären statt als Inhalt. Um zu zeigen, daß Ausnahmen die Regel bestätigen, verwende ich sie in diesem Fall lieber als Inhalt, aber das ist eine andere Geschichte.

Beispiel 7-4: Update der Ausgabe von PropsToXML

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <org>
    <enhydra>
      <classpath>
        <separator>":"</separator>
      </classpath>
      <initialargs>"./bootstrap.conf"</initialargs>
      <initialclass>org.enhydra.multiServer.bootstrap.Bootstrap</initialclass>
      <classpath>"."</classpath>
    </enhydra>
    <xml>
      <sax>
        <parser>"org.apache.xerces.parsers.SAXParser"</parser>
      </sax>
    </xml>
  </org>
</properties>

Aus keinem anderen Grund als zu Demonstrationszwecken wollen wir uns die Konvertierung der Eigenschaftswerte in Attribute statt in Textinhalte anschauen. Dies stellt sich als ziemlich leicht heraus und kann auf zwei verschiedene Arten geschehen. Die erste Möglichkeit besteht darin, eine Instanz der JDOM-Klasse Attribute zu erzeugen. Der Konstruktor dieser Klasse nimmt den Namen des Attributs sowie dessen Wert entgegen. Anschließend kann die daraus hervorgegangene Instanz mit Hilfe der Element-Methode setAttribute( ) zu dem Blattelement hinzugefügt werden. Dieser Ansatz wird hier gezeigt:

        // Nach dem Verlassen der Schleife bleibt der Name des letzten Elements
        // übrig
        Element last = new Element(name);                        
        /* last.setText(propertyValue);  */
        Attribute attribute = new Attribute("value", propertyValue);
        current.setAttribute(attribute);
        current.addContent(last);

Wenn Sie die Datei mit diesen Änderungen kompilieren möchten, stellen Sie sicher, daß folgende Import-Anweisung für die Klasse Attribute hinzugefügt wird:

Eine etwas leichtere Möglichkeit stellt die Verwendung der bequemen Methoden dar, die JDOM anbietet. Da das Hinzufügen von Attributen eine derart gängige Aufgabe ist, stellt die Klasse Element eine überladene Version von setAttribute( ) bereit, die einen Namen und Wert entgegennimmt und intern ein Attribute-Objekt erzeugt. In diesem Fall erscheint der Ansatz ein wenig klarer:

        // Nach dem Verlassen der Schleife bleibt der Name des letzten Elements
        // übrig
        Element last = new Element(name);                        
        /* last.setText(propertyValue); */
        last.setAttribute("value", propertyValue);
        current.addContent(last);

Das funktioniert genausogut, vermeidet es aber zusätzlich, daß eine weitere Import-Anweisung verwendet werden muß. Sie können diese Änderung hineinkompilieren und das Beispielprogramm starten. Die neue Ausgabe sollte Beispiel 7-5 entsprechen.

Beispiel 7-5: Ausgabe von PropsToXML unter Verwendung von Attributen

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <org>
    <enhydra>
      <classpath>
        <separator value="&quot;:&quot;" />
      </classpath>
      <initialargs value="&quot;./bootstrap.conf&quot;" />
      <initialclass value="org.enhydra.multiServer.bootstrap.Bootstrap" />
      <classpath value="&quot;.&quot;" />
    </enhydra>
    <xml>
      <sax>
        <parser value="&quot;org.apache.xerces.parsers.SAXParser&quot;" />
      </sax>
    </xml>
  </org>
</properties>

Jeder Eigenschaftswert stellt nun ein Attribut des innersten Elements dar. Beachten Sie, daß JDOM die unerlaubten Anführungszeichen innerhalb der Attributwerte in Entity-Referenzen umwandelt, so daß das Dokument als Ausgabe wohlgeformt ist. Allerdings macht dies die Ausgabe ein wenig unsauberer, so daß Sie Ihren Code am liebsten wieder auf die Verwendung von Textdaten innerhalb von Elementen statt von Attributen umstellen würden.

Bevor wir weitermachen, möchte ich ein wenig Zeit damit verbringen, über den Ausgabeteil des Codes zu sprechen, den ich schon weiter oben im Kapitel kurz angeschnitten habe.

    private void convertToXML(Properties props, String xmlFilename)
        throws IOException {
    
        // Ein neues JDOM-Dokument mit dem Wurzelelement "properties" erzeugen
        Element root = new Element("properties");
        Document doc = new Document(root);
        
        // Die Eigenschaftsnamen holen
        Enumeration propertyNames = props.propertyNames(  );
        while (propertyNames.hasMoreElements(  )) {
            String propertyName = (String)propertyNames.nextElement(  );
            String propertyValue = props.getProperty(propertyName);
            createXMLRepresentation(root, propertyName, propertyValue);
        }        
        
        // Das Dokument in die angegebene Datei ausgeben
        XMLOutputter outputter = new XMLOutputter("  ", true);
        FileOutputStream output = new FileOutputStream(xmlFilename);
        outputter.output(doc, output); 
    }

Wie Sie bereits wissen, ist XMLOutputter die Klasse, die verwendet wird, um die Ausgabe in eine Datei, einen Stream oder eine andere statische Darstellung durchzuführen. Allerdings habe ich an den Konstruktor im Codebeispiel einige Argumente übergeben; ohne Argumente würde der Outputter eine direkte Ausgabe vornehmen. Es würde keine Änderung in dem XML geben, das als Eingabe verwendet wird. Wenn XML gelesen wird, ergibt dies oft keine Zeilenumbrüche und keine Einrückung. Das Ergebnis würde das gesamte Dokument, bis auf die XML-Deklaration, in einer einzigen Zeile enthalten. Ich würde Ihnen das eigentlich gern zeigen, aber es paßt nicht auf die Seite und kann zu Verwirrung führen. Der Outputter hat jedoch mehrere Konstruktoren:

public XMLOutputter(  );

public XMLOutputter(String indent);

public XMLOutputter(String indent, boolean newlines);

public XMLOutputter(String indent, boolean newlines, String encoding);

public XMLOutputter(XMLOutputter that);

Die meisten davon sind selbsterklärend. Der Parameter indent ermöglicht es festzulegen, wie viele Leerzeichen für die Einrückung verwendet werden sollen; im Beispielcode habe ich zwei Leerzeichen (» «) verwendet. Der boolean-Wert für newlines bestimmt, ob Zeilenumbrüche benutzt werden (ist in diesem Beispiel eingeschaltet). Bei Bedarf kann ein Codierungsparameter angegeben werden, der in der XML-Deklaration als Wert für encoding eingesetzt wird:

<?xml version="1.0" encoding="UTF-8"?>

Darüber hinaus gibt es Veränderungsmethoden für alle diese Eigenschaften (setIndent( ), setEncoding( ) usw.) in der Klasse. Es gibt auch Versionen der Methode output( ) (die im Beispielcode verwendet wird), welche entweder einen OutputStream oder einen Writer entgegennehmen. Und es gibt Versionen, die die verschiedenen JDOM-Konstrukte als Eingabe annehmen, so daß Sie ein ganzes Document-Objekt ausgeben könnten, aber auch nur ein Element-, Comment-, ProcessingInstruction- oder sonstiges Objekt:

// Einen Outputter mit vier Leerzeichen Einzug und Zeilenumbrüchen erzeugen
XMLOutputter outputter = new XMLOutputter("    ", true);

// Verschiedene JDOM-Konstrukte ausgeben
outputter.output(myDocument, myOutputStream);
outputter.output(myElement, myWriter);
outputter.output(myComment, myOutputStream);
// usw...

Mit anderen Worten: Der XMLOutputter erfüllt alle Ihre XML-Ausgabebedürfnisse. Natürlich können Sie auch DOMOutputter und SAXOutputter verwenden, die ich im nächsten Kapitel detaillierter behandeln werde.

XMLProperties

Wir wollen mit dem nächsten logischen Schritt fortfahren und uns das Lesen von XML anschauen. Wir bleiben bei dem Beispiel der Konvertierung einer Eigenschaftsdatei in XML; Sie fragen sich nun wahrscheinlich, wie Sie auf die Informationen in Ihrer XML-Datei zugreifen können. Glücklicherweise gibt es auch dafür eine Lösung! In diesem Abschnitt möchte ich, um zu erläutern, wie JDOM XML liest, eine neue Hilfsklasse vorstellen: XMLProperties. Diese Klasse ist im wesentlichen eine XML-fähige Version der Java-Klasse Properties; tatsächlich erweitert sie auch genau diese Klasse. Die Klasse ermöglicht den Zugriff auf ein XML-Dokument mit Hilfe der typischen Methoden für den Eigenschaftszugriff wie getProperty( ) und properties( ); mit anderen Worten: Sie ermöglicht einen Java-artigen Zugriff (durch die Verwendung der Klasse Properties) auf Konfigurationsdateien, die im XML-Style gespeichert sind. Meiner Ansicht nach ist das die beste Kombination, die Sie bekommen können.

Um diese Aufgabe zu erfüllen, können Sie mit dem Erzeugen der Klasse XMLProperties beginnen, die die Klasse java.util.Properties erweitert. Durch diesen Ansatz ist es nur erforderlich, die Methoden load( ), save( ) und store( ) anzupassen, um die Dinge zum Laufen zu bringen. Die erste dieser Methoden, load( ), liest ein XML-Dokument ein und lädt die Eigenschaften dieses Dokuments in das Objekt der übergeordneten Klasse.

Mißverstehen Sie diese Klasse nicht als universellen XML-Eigenschaftskonverter; sie liest lediglich XML in dem Format ein, das weiter oben in diesem Kapitel detailliert beschrieben wurde. Mit anderen Worten: Eigenschaften sind Elemente, die entweder Text- oder Attributwerte haben, aber nicht beides; ich werde beide Ansätze behandeln, aber Sie müssen sich für einen von beiden entscheiden. Versuchen Sie nicht, alle Ihre XML-Dokumente zu nehmen und einzulesen, und erwarten Sie erst recht nicht, daß dann alles so funktioniert wie geplant!

Die zweite Methode, save( ), ist in Java 2 eigentlich veraltet, da sie keine Fehlermeldungen anzeigt; aber sie muß immer noch überschrieben werden, wenn Sie Java 1.1 verwenden. Um dies zu vereinfachen, ruft die Implementierung in XMLProperties einfach store( ) auf. Und store( ) übernimmt die Aufgabe, die Eigenschaftsinformationen in ein XML-Dokument auszugeben. Beispiel 7-6 ist ein guter Anfang dafür und bietet ein Grundgerüst, mit dem gearbeitet werden kann.

Beispiel 7-6: Das Grundgerüst der Klasse XMLProperties

package javaxml2;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.jdom.Attribute;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

public class XMLProperties extends Properties {
    
    public void load(Reader reader) 
        throws IOException {
         
        // Ein XML-Dokument in ein Eigenschaftsobjekt einlesen 
    }    

    public void load(InputStream inputStream) 
        throws IOException {
         
        load(new InputStreamReader(inputStream));    
    }
    
    public void load(File xmlDocument) 
        throws IOException {
        
        load(new FileReader(xmlDocument));    
    }  

    public void save(OutputStream out, String header) {
        try {            
            store(out, header);
        } catch (IOException ignored) {
            // Die veraltete Version meldet keine Fehler
        }        
    }   
     
    public void store(Writer writer, String header)
        throws IOException {
            
        // Eigenschaften in XML konvertieren und ausgeben
    }    
          
    public void store(OutputStream out, String header)
        throws IOException {
            
        store(new OutputStreamWriter(out), header);
    }
 
    public void store(File xmlDocument, String header)
        throws IOException {
            
        store(new FileWriter(xmlDocument), header);
    }    
}

Beachten Sie, daß ich die Methoden load( ) und store( )überladen habe; während die Klasse Properties nur Versionen beinhaltet, die einen InputStream beziehungsweise OutputStream entgegennehmen, bin ich eher der Ansicht, daß Benutzern mehr Optionen eingeräumt werden sollten. Die zusätzlichen Versionen, die Objekte vom Typ File und Reader/Writer annehmen, erleichtern den Anwendern die Interaktion, fügen aber nur eine geringfügige Menge Code zur Klasse hinzu. Darüber hinaus können all diese überladenen Methoden Aufgaben an existierende Methoden delegieren, was den Code für die Implementierung der Lade- und Speicherfunktionen vorbereitet.

Ich werde als erstes das Speichern von XML behandeln, vor allem, weil dieser Code bereits geschrieben ist. Der logische Ablauf, ein Properties-Objekt zu nehmen und als XML auszugeben, ist der Zweck der Klasse PropsToXML, und ich verwende hier lediglich einiges von diesem Code wieder, damit alles korrekt arbeitet:

    public void store(Writer writer, String header)
        throws IOException {
            
        // Ein neues JDOM-Dokument mit dem Wurzelelement "properties" erzeugen
        Element root = new Element("properties");
        Document doc = new Document(root);
        
        // Eigenschaftsnamen holen
        Enumeration propertyNames = propertyNames(  );
        while (propertyNames.hasMoreElements(  )) {
            String propertyName = (String)propertyNames.nextElement(  );
            String propertyValue = getProperty(propertyName);
            createXMLRepresentation(root, propertyName, propertyValue);
        }        
        
        // Dokument in die angegebene Datei ausgeben
        XMLOutputter outputter = new XMLOutputter("  ", true);
        outputter.output(doc, writer);
    }

    private void createXMLRepresentation(Element root, 
                                         String propertyName,
                                         String propertyValue) {
        
        int split;
        String name = propertyName;
        Element current = root;
        Element test = null;
              
        while ((split = name.indexOf(".")) != -1) {
            String subName = name.substring(0, split);
            name = name.substring(split+1);
            
            // Auf existierendes Element überprüfen 
            if ((test = current.getChild(subName)) == null) {
                Element subElement = new Element(subName);
                current.addContent(subElement);
                current = subElement;
            } else {
                current = test;
            }
        }
        
        // Nach dem Beenden der Schleife bleibt der endgültige Elementname übrig
        Element last = new Element(name);                        
        last.setText(propertyValue);
        /** Hier für die Verwendung mit Attributen den Kommentar entfernen */
        /*
        last.setAttribute("value", propertyValue);
        */
        current.addContent(last);
    }

Hier wird nicht viel Kommentar benötigt. Wenige Zeilen Code sind jedoch hervorgehoben, um einige Änderungen zu zeigen. Die beiden ersten Änderungen sorgen dafür, daß die übergeordnete Klasse verwendet wird, um die Eigenschaftsnamen und -werte anzunehmen, und nicht das Properties-Objekt, das an die Version dieser Methode in PropsToXML übergeben wurde. Die dritte Änderung ersetzt den ursprünglichen Dateinamens-String durch den angegebenen Writer für die Ausgabe. Mit diesen paar Änderungen ist alles bereit, um die XMLProperties-Quelldatei zu kompilieren.

Eins fehlt aber noch. Beachten Sie, daß die Methode store( ) die Spezifikation einer Header-Variable ermöglicht; in einer normalen Java-Eigenschaftsdatei wird diese als Kommentar zum Kopf der Datei hinzugefügt. Um das Ganze parallel zu halten, kann die Klasse XMLProperties so modifiziert werden, daß sie das Gleiche tut. Sie müssen die Klasse Comment verwenden, um dies zu tun. Die folgenden Ergänzungen des Codes führen diese Änderung durch:

    public void store(Writer writer, String header)
        throws IOException {
            
        // Ein neues JDOM-Dokument mit dem Wurzelelement "properties" erzeugen
        Element root = new Element("properties");
        Document doc = new Document(root);
        
        // Header-Information hinzufügen
        Comment comment = new Comment(header);
        doc.addContent(comment);
        
        // Die Eigenschaftsnamen holen
        Enumeration propertyNames = propertyNames(  );
        while (propertyNames.hasMoreElements(  )) {
            String propertyName = (String)propertyNames.nextElement(  );
            String propertyValue = getProperty(propertyName);
            createXMLRepresentation(root, propertyName, propertyValue);
        }        
        
        // Das Dokument in die angegebene Datei ausgeben
        XMLOutputter outputter = new XMLOutputter("  ", true);
        outputter.output(doc, writer);
    }

Die Methode addContent( ) des Document-Objekts wurde überladen, um sowohl Comment- als auch ProcessingInstruction-Objekte entgegenzunehmen, und fügt diese Inhalte zur Datei hinzu. Sie wird hier verwendet, um den Parameter header als Kommentar zu dem XML-Dokument hinzuzufügen, in das hineingeschrieben wird.

Es gibt hier nicht mehr viel zu tun; grundsätzlich gibt die Klasse XML aus, bietet Zugriff auf Eigenschaften (durch die Methoden, die bereits in der Klasse Properties existieren) und muß jetzt bloß noch XML lesen können. Das ist eine recht einfache Aufgabe; es führt lediglich zu weiterer Rekursion. Ich zeige Ihnen die erforderlichen Modifikationen des Codes und gehe sie dann einzeln durch. Geben Sie den hier gezeigten Code in Ihre XMLProperties.java-Quelldatei ein:

    public void load(Reader reader) 
        throws IOException {
        
        try { 
            // XML in ein JDOM-Dokument laden
            SAXBuilder builder = new SAXBuilder(  );
            Document doc = builder.build(reader);
            
            // In Eigenschaftsobjekte umwandeln
            loadFromElements(doc.getRootElement().getChildren(  ), 
                new StringBuffer(""));
            
        } catch (JDOMException e) {
            throw new IOException(e.getMessage(  ));
        }        
    }

    private void loadFromElements(List elements, StringBuffer baseName) {
        // Jedes Element einzeln durchgehen
        for (Iterator i = elements.iterator(); i.hasNext(  ); ) {
            Element current = (Element)i.next(  );
            String name = current.getName(  );
            String text = current.getTextTrim(  );
            
            // Keinen "." hinzufügen, wenn kein baseName da ist
            if (baseName.length(  ) > 0) {
                baseName.append(".");
            }            
            baseName.append(name);
            
            // Überprüfen, ob wir einen Elementwert haben
            if ((text == null) || (text.equals(""))) {
                // Wenn kein Text da ist, Rekursion über die Kindobjekte
                loadFromElements(current.getChildren(  ),
                                 baseName);
            } else {                
                // Wenn Text da ist, ist dies eine Eigenschaft
                setProperty(baseName.toString(  ), 
                            text);
            }            
            
            // Bei der Rückkehr aus der Rekursion den letzten Namen entfernen
            if (baseName.length() == name.length(  )) {
                baseName.setLength(0);
            } else {                
                baseName.setLength(baseName.length(  ) - 
                    (name.length(  ) + 1));
            }            
        }        
    }

Die Implementierung der Methode load( ) (an die alle überladenen Versionen die eigentliche Arbeit delegieren) verwendet SAXBuilder, um das übergebene XML-Dokument einzulesen. Ich habe das schon weiter oben im Kapitel besprochen und werde es im nächsten sogar noch detaillierter behandeln; im Moment genügt es zu bemerken, daß es einfach XML in ein JDOM-Document-Objekt einliest.

Der Name einer Eigenschaft besteht aus allen Elementen, die zum Eigenschaftswert führen, wobei ein Punkt jeden Namen voneinander trennt. Hier sehen Sie eine Beispieleigenschaft in XML:

<properties>
  <org>
    <enhydra>
      <classpath>"."</classpath>
    </enhydra>
  </org>
</properties>

Der Eigenschaftsname kann ermittelt werden, indem die Elementnamen genommen werden, die bis zum Wert führen (bis auf das Element properties, das als Container auf der Wurzelebene verwendet wird): org, enhydra und classpath. Wenn Sie jeweils einen Punkt dazwischensetzen, erhalten Sie org.enhydra.classpath, und das ist der fragliche Name. Um dies zu erledigen, habe ich die Methode loadFromElements( ) programmiert. Diese nimmt eine Liste von Elementen entgegen, geht sie iterativ durch und behandelt jedes Element individuell.

Wenn das Element einen Textwert besitzt, wird dieser Wert zu den Eigenschaften des Objekts der übergeordneten Klasse hinzugefügt. Hat es dagegen Kindelemente, dann werden diese entgegengenommen, und die Rekursion beginnt wieder mit der neuen Liste der Kindobjekte. Bei jedem Schritt der Rekursion wird der gerade bearbeitete Name zu der Variable baseName hinzugefügt, die jeweils den aktuellen Eigenschaftsnamen enthält. Im Verlauf der Rekursion würde baseName hier zunächst org, dann org.enhydra und anschließend org.enhydra.classpath werden. Und bei der Rückkehr aus der Rekursion wird die Variable baseName gekürzt, um den letzten Elementnamen zu entfernen. Schauen wir uns die JDOM-Methodenaufrufe an, die das möglich machen.

Als erstes werden Ihnen mehrere Aufrufe der Methode getChildren( ) bei Instanzen der Klasse Element auffallen. Es gibt Versionen dieser Methode, die auch den Namen eines Elements entgegennehmen, nach dem gesucht werden soll, und die dann entweder alle Elemente dieses Namens (getChildren(String name)) oder nur das erste Kindelement dieses Namens (getChild(String name)) zurückgeben. Es gibt auch Namensraum-fähige Versionen dieser Methode, die im nächsten Kapitel behandelt werden.

Um den Rekursionsprozeß zu starten, wird das Wurzelelement durch die Methode getRootElement( ) aus dem JDOM-Document-Objekt geholt, und anschließend werden deren Kinder verwendet, um die Rekursion einzuleiten. Nach dem Start der Methode loadFromElements( ) werden Standard-Java-Klassen verwendet, um die Liste der Elemente schrittweise durchzugehen (etwa java.util.Iterator). Um nach Textinhalt zu suchen, wird die Methode getTextTrim( ) verwendet. Diese Methode gibt den Textinhalt eines Elements zurück, und sie liefert das Element ohne umgebenden Whitespace.5 Auf diese Weise würde der Inhalt »reiner Text« (beachten Sie den umgebenden Whitespace) als »reiner Text« zurückgegeben. Auch wenn dies irgendwie trivial erscheint, betrachten Sie das folgende realistischere XML-Beispiel:

<kapitel>
  <titel>
    Fortgeschrittenes SAX
  </titel>
</kapitel>

Der eigentliche Textinhalt des Elements titel erweist sich als Abfolge mehrerer Leerzeichen, gefolgt von einem Zeilenumbruch und weiteren Leerzeichen. Mit anderen Worten: Es ist wahrscheinlich nicht das, was Sie erwartet haben. Die zurückgegebenen String-Daten aus einem Aufruf von getTextTrim( ) wären einfach »Fortgeschrittenes SAX« – in den meisten Fällen genau das, was Sie haben wollen.

Wenn Sie allerdings den vollständigen Inhalt benötigen (was oft verwendet wird, um das eingegebene Dokument genau so zu reproduzieren, wie es hereinkam), können Sie die Methode getText( ) verwenden, die den Inhalt des Elements unverändert zurückgibt. Wenn kein Inhalt existiert, ist der Rückgabewert dieser Methode ein leerer String (»«), der den Vergleich leicht macht, wie im Beispielcode zu sehen ist. Und das war es schon fast: Ein paar einfache Methodenaufrufe, und der Code liest XML mit Hilfe von JDOM. Schauen wir uns diese Klasse in Aktion an.

Nachdem Sie in der Klasse XMLProperties alles fertiggestellt haben, kompilieren Sie sie. Um sie zu testen, können Sie Beispiel 7-7 eingeben oder herunterladen, eine Klasse, die die Klasse XMLProperties verwendet, um ein XML-Dokument zu laden, einige Informationen über es auszugeben und die Eigenschaften dann wieder als XML auszugeben.

Beispiel 7-7: Die Klasse XMLProperties testen

package javaxml2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Enumeration;

public class TestXMLProperties {

    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Verwendung: java javaxml2.TestXMLProperties " +
                "[XML-Eingabedokument] [XML-Ausgabedokument]");
            System.exit(0);
        }
    
        try {
            // Eigenschaften erzeugen und laden
            System.out.println("Lese XML-Eigenschaften aus " + args[0]);
            XMLProperties props = new XMLProperties(  );
            props.load(new FileInputStream(args[0]));
            
            // Eigenschaften und Werte ausgeben
            System.out.println("\n\n---- Eigenschaftswerte ----");
            Enumeration names = props.propertyNames(  );
            while (names.hasMoreElements(  )) {
                String name = (String)names.nextElement(  );
                String value = props.getProperty(name);
                System.out.println("Eigenschaftsname: " + name + 
                                   " hat den Wert " + value);
            }            
            
            // Eigenschaften speichern
            System.out.println("\n\nSchreibe XML-Eigenschaften in " + args[1]);
            props.store(new FileOutputStream(args[1]),
                "Die Klasse XMLProperties testen");
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Dieses Programm tut nicht besonders viel; es liest Eigenschaften ein, verwendet sie, um alle Eigenschaftsnamen und -werte auszugeben, und schreibt sie dann wieder zurück – aber alles in XML. Sie können dieses Programm mit der XML-Datei starten, die von der Klasse PropsToXML erzeugt wurde, die ich Ihnen weiter oben in diesem Kapitel gezeigt habe.

C:\javaxml2\build>java javaxml2.TestXMLProperties enhydraProps.xml output.xml
Lese XML-Eigenschaften aus enhydraProps.xml


---- Eigenschaftswerte ----
Eigenschaftsname: org.enhydra.classpath.separator hat den Wert ":"
Eigenschaftsname: org.enhydra.initialargs hat den Wert "./bootstrap.conf"
Eigenschaftsname: org.enhydra.initialclass hat den Wert 
  org.enhydra.multiServer.bootstrap.Bootstrap
Eigenschaftsname: org.enhydra.classpath hat den Wert "."
Eigenschaftsname: org.xml.sax.parser hat den Wert 
  "org.apache.xerces.parsers.SAXParser"


Schreibe XML-Eigenschaften in output.xml

Und da haben Sie es: XML-Datenformatierung mit dem Verhalten von Eigenschaften.

Bevor wir diesen Code wegpacken, gibt es noch einige Dinge, die ich ansprechen möchte. Schauen Sie sich als erstes die XML-Datei an, die von TestXMLProperties erzeugt wurde – das Ergebnis der Anwendung von store( ) auf die Eigenschaften. Es sollte so ähnlich aussehen wie Beispiel 7-8, wenn Sie die XML-Version von enhydra.properties verwendet haben, die weiter oben in diesem Kapitel detailliert beschrieben wurde.

Beispiel 7-8: Ausgabe von TestXMLProperties

<?xml version="1.0" encoding="UTF-8"?>
<properties>
  <org>
    <enhydra>
      <classpath>
        <separator>":"</separator>
      </classpath>
      <initialargs>"./bootstrap.conf"</initialargs>
      <initialclass>org.enhydra.multiServer.bootstrap.Bootstrap</initialclass>
      <classpath>"."</classpath>
    </enhydra>
    <xml>
      <sax>
        <parser>"org.apache.xerces.parsers.SAXParser"</parser>
      </sax>
    </xml>
  </org>
</properties>
<!--Die Klasse XMLProperties testen-->

Fällt Ihnen irgendein Fehler auf? Der Header-Kommentar ist an der falschen Stelle. Schauen Sie sich noch einmal den Code aus der Methode store( ) an, der diesen Kommentar eingefügt hat:

        // Ein neues JDOM-Dokument mit dem Wurzelelement "properties" erzeugen
        Element root = new Element("properties");
        Document doc = new Document(root);
        
        // Header-Information hinzufügen
        Comment comment = new Comment(header);
        doc.addContent(comment);

Das Wurzelelement taucht vor dem Kommentar auf, weil es als erstes zum Document-Objekt hinzugefügt wird. Allerdings kann das Document-Objekt nicht ohne Angabe eines Wurzelelements erzeugt werden – eine Art Huhn-oder-Ei-Situation. Um damit umzugehen, müssen Sie eine neue Methode verwenden: getContent( ). Diese Methode gibt ein List-Objekt zurück, aber dieses enthält sämtlichen Inhalt des Documents mitsamt Kommentaren, dem Wurzelelement und den Steueranweisungen. Sie können nun den Kommentar dieser Liste voranstellen, wie hier gezeigt wird, indem Sie Methoden der Klasse List verwenden:

        // Header-Information hinzufügen
        Comment comment = new Comment(header);
        doc.getContent(  ).add(0, comment);

Nach dieser Änderung sieht Ihr Dokument so aus wie es sollte:

<?xml version="1.0" encoding="UTF-8"?>
<!--Die Klasse XMLProperties testen-->
<properties>
  <org>
    <enhydra>
      <classpath>
        <separator>":"</separator>
      </classpath>
      <initialargs>"./bootstrap.conf"</initialargs>
      <initialclass>org.enhydra.multiServer.bootstrap.Bootstrap</initialclass>
      <classpath>"."</classpath>
    </enhydra>
    <xml>
      <sax>
        <parser>"org.apache.xerces.parsers.SAXParser"</parser>
      </sax>
    </xml>
  </org>
</properties>

Die Methode getContent( ) steht auch in der Klasse Element zur Verfügung und gibt sämtlichen Inhalt des Elements zurück, ungeachtet des Typs (Elemente, Steueranweisungen, Kommentare, Entities sowie Strings für Textinhalte).

Wichtig sind auch die notwendigen Änderungen an XMLProperties, um Attribute statt Elementinhalte als Eigenschaftswerte verwenden zu können. Sie haben bereits die Codeänderungen gesehen, die beim Speichern von Eigenschaften nötig sind (in der Tat ist die Änderung im Quellcode auskommentiert, so daß Sie nichts neu schreiben müssen). Zu der Änderung gehört, wie beim Laden, daß nun nach einem Attribut statt nach dem Textwert eines Elements gesucht wird. Dies kann mit Hilfe der Methode getAttributeValue(String name) geschehen, die den Wert des Attributs mit dem angegebenen Namen zurückgibt, oder null, wenn kein solcher Wert existiert. Die Änderung wird hier gezeigt:

    private void loadFromElements(List elements, StringBuffer baseName) {
        // Jedes Element einzeln durchgehen
        for (Iterator i = elements.iterator(); i.hasNext(  ); ) {
            Element current = (Element)i.next(  );
            String name = current.getName(  );
            // String text = current.getTextTrim(  );
            String text = current.getAttributeValue("value");            
            
            // Keinen "." hinzufügen, falls kein baseName da ist
            if (baseName.length(  ) > 0) {
                baseName.append(".");
            }            
            baseName.append(name);
            
            // Überprüfen, ob wir einen Attributwert haben
            if ((text == null) || (text.equals(""))) {
                // If no text, recurse on children
                loadFromElements(current.getChildren(  ),
                                 baseName);
            } else {                
                // Falls Text da ist, ist dies eine Eigenschaft
                setProperty(baseName.toString(  ), 
                            text);
            }            
            
            // Bei der Rückkehr aus der Rekursion den letzten Namen entfernen
            if (baseName.length() == name.length(  )) {
                baseName.setLength(0);
            } else {                
                baseName.setLength(baseName.length(  ) - 
                    (name.length(  ) + 1));
            }            
        }        
    }    

Kompilieren Sie die geänderte Fassung, und Sie können mit Attributwerten arbeiten statt mit Elementinhalten. Lassen Sie den Code in dem Zustand, in dem Sie ihn lieber haben (wie ich weiter oben erwähnte, mag ich persönlich die Werte lieber als Elementinhalt). Wenn Sie also Textinhalte von Elementen möchten, stellen Sie sicher, daß Sie diese Änderungen rückgängig machen, nachdem Sie gesehen haben, welchen Einfluß sie auf die Ausgabe haben. Egal welche Variante Sie bevorzugen, Sie fangen hoffentlich langsam an, sich in JDOM zurechtzufinden.

Und genau wie bei SAX und DOM empfehle ich auch hier wärmstens, ein Bookmark auf das zugehörige Javadoc anzulegen (entweder lokal oder online), und zwar als schnelle Referenz für die Methoden, an die Sie sich gerade nicht erinnern. Auf jeden Fall wollen wir kurz vor Schluß noch ein wenig über eine häufige Frage in bezug auf JDOM sprechen: die Standardisierung.

Ist JDOM ein Standard?

Häufiger, als mir irgendeine andere Frage über JDOM gestellt wird, werde ich gefragt, ob JDOM ein Standard ist. Dies ist eine gängige Frage, sowohl von denjenigen, die JDOM benutzen möchten (und eine Rechtfertigung brauchen), als auch von denjenigen, die JDOM nicht mögen (und eine Rechtfertigung brauchen). Ich spreche in diesem Abschnitt einige dieser Themen an; zu welchem der beiden Lager Sie also auch gehören mögen, lernen Sie die Details von JDOM und seinem Standardisierungsprozeß kennen.

Zuallererst ist JDOM nun ein offizieller JSR, also ein Java Specification Request. Mit anderen Worten durchläuft es den formalen Standardisierungsprozeß, der von Sun gesponsert und vom JCP (Java Community Process) bestimmt wird. Sie können alles über JSR- und JCP-Prozesse unter http://java.sun.com/aboutJava/communityprocess/ nachlesen. Was JDOM angeht, heißt es jetzt offiziell JSR-102 und befindet sich online auf der JPC-Website unter https://jcp.org/en/jsr/detail?id=102.

Während JDOM den JCP durchläuft, werden verschiedene Dinge passieren. Zunächst wird es einen höheren Status bezüglich der Standardisierung erlangen; auch wenn der JCP und Sun nicht vollkommen sind, bieten sie doch eine gewisse Vertrauensbasis. Zu den Mitgliedern und Unterstützern von JCP zählen IBM, BEA, Copmpaq, HP, Apache und andere. Darüber hinaus wird es sehr einfach werden, JDOM in andere Java-Standards einzufügen. Zum Beispiel hat Sun Interesse daran bekundet, JDOM zu einem Teil der nächsten Version von JAXP, entweder 1.2 oder 2.0, zu machen (ich gehe im gleichnamigen Kapitel näher auf JAXP ein). Zu guter Letzt besteht die Aussicht, daß eine XML-Unterstützung zum Kern zukünftiger JDK-Versionen gehören wird; in späteren Jahren könnte JDOM in jedem Java-Download enthalten sein.

Behalten Sie im Hinterkopf, daß JDOM dadurch nicht in eine Art bevorzugten Status befördert wird; DOM und SAX sind beide bereits ein Teil von JAXP, und in dieser Hinsicht haben sie JDOM tatsächlich etwas voraus. Allerdings lohnt es sich, einige Kommentare zur »Standardisierung« von DOM und SAX abzugeben. Zunächst kam SAX aus dem Public Domain-Bereich und bleibt bis heute ein De-facto-Standard. Da es zunächst in der XML-dev-Mailingliste entwickelt wurde, hat kein Standardisierungsgremium SAX ratifiziert oder akzeptiert, bis es sehr häufig im Einsatz war. Auch wenn ich SAX ohne Zweifel kritisiere, nehme ich doch Rücksicht auf Leute, die meinen, daß JDOM nicht benutzt werden sollte, weil es nicht von einem Standardisierungsgremium entwickelt wurde.

Auf der anderen Seite wurde DOM vom W3C entwickelt und ist ein formaler Standard. Aus diesem Grund hat es eine treue Gefolgschaft. DOM ist für viele Anwendungen eine großartige Lösung. Aber wieder ist das W3C nur eines von vielen Standardisierungsgremien; der JCP ist ein anderes, die IETF ist wieder ein anderes und so weiter. Ich bestreite nicht die Verdienste jeder einzelnen dieser Gruppen; ich warne Sie lediglich davor, irgendeinen Standard (JDOM oder andere) zu akzeptieren, wenn er nicht den Anforderungen Ihrer Anwendung genügt. Streits über die »Standardisierung« sind zweitrangig gegenüber der praktischen Benutzbarkeit.

Wenn Sie DOM mögen und es Ihren Ansprüchen genügt, benutzen Sie es. Das gleiche gilt für SAX und JDOM. Ich würde es begrüßen, wenn alle damit aufhören würden, Entscheidungen für andere treffen zu wollen (und ich weiß, daß ich eigentlich nur meine API verteidigen will, aber daß mir das auch immer wieder passiert!). Ich hoffe, daß dieses Buch Sie tief genug in alle drei APIs einführt, damit Sie eine wohlüberlegte Entscheidung treffen können.

Vorsicht Falle!

Ich enttäusche Sie auch diesmal nicht, sondern möchte Sie vor einigen typischen JDOM-Stolperfallen warnen. Ich hoffe, daß Sie dadurch ein wenig Zeit bei Ihrer JDOM-Programmierung sparen.

Zuallererst sollten Sie sich merken, daß JDOM nicht DOM ist. Es umhüllt DOM nicht und bietet keine Erweiterung zu DOM. Mit anderen Worten: Die beiden haben keinen technischen Bezug zueinander. Wenn Sie sich diese einfache Wahrheit merken, werden Sie eine Menge Zeit und Aufwand sparen; es erscheinen heutzutage viele Artikel, in denen die Rede davon ist, daß Sie die DOM-Interfaces benötigen, um JDOM zu benutzen, oder daß Sie JDOM vermeiden sollten, da es einige DOM-Methoden verdeckt. Diese Behauptungen verwirren die Leute mehr als alles andere. Sie brauchen die DOM-Interfaces nicht, und DOM-Aufrufe (wie appendChild( ) oder createDocument( )) funktionieren mit JDOM einfach nicht. Entschuldigung, falsche API!

Ein weiterer interessanter Aspekt von JDOM, der darüber hinaus einiges an Kontroversen aufgeworfen hat, sind die Rückgabewerte von Methoden, die Elementinhalte auslesen. Zum Beispiel können die unterschiedlichen getChild( )-Methoden der Klasse Element den Wert null zurückgeben. Ich habe dies im Beispielcode PropsToXML bereits erwähnt und gezeigt. Das Problem tritt auf, wenn statt der Überprüfung, ob ein Element existiert (wie im Beispielcode geschehen), einfach davon ausgegangen wird, daß es existiert. Dies kommt am häufigsten dann vor, wenn eine andere Anwendung oder Komponente Ihnen XML sendet und wenn Ihr Code erwartet, daß es ein bestimmtes Format hat (ob nun eine DTD, ein XML Schema oder einfach ein vereinbarter Standard). Schauen Sie sich zum Beispiel den folgenden Code an:

Document doc = otherComponent.getDocument(  );
String price = doc.getRootElement(  ).getChild("artikel")
                                   .getChild("preis")
                                   .getTextTrim(  );

Das Problem in diesem Code besteht darin, daß Aufrufe der Methode getChild( ) den Wert null zurückgeben, wenn unterhalb der Wurzel kein Element artikel und unterhalb von diesem kein Element preis existiert. Plötzlich fängt dieser harmlos aussehende Code an, NullPointerExceptions auszulösen, die sehr schwer zu finden und zu beseitigen sind. Sie können mit dieser Situation auf zweierlei Arten fertigwerden. Die erste Möglichkeit ist es, bei jedem einzelnen Schritt auf den Wert null zu überprüfen:

Document doc = otherComponent.getDocument(  );
Element root = doc.getRootElement(  );
Element item = root.getChild("artikel");
if (item != null) {
    Element price = item.getChild("preis");
    if (price != null) {
        String price = price.getTextTrim(  );
    } else {
        // Außergewöhnlichen Zustand behandeln
    }
} else {
    // Außergewöhnlichen Zustand behandeln
}

Die zweite Option ist, das gesamte Codefragment in einen try/catch-Block einzufügen:

Document doc = otherComponent.getDocument(  );
try {
    String price = doc.getRootElement(  ).getChild("artikel")
                                       .getChild("preis")
                                       .getTextTrim(  );
} catch (NullPointerException e) {
    // Außergewöhnlichen Zustand behandeln
}

Obwohl jeder der beiden Ansätze funktioniert, empfehle ich den ersten; er ermöglicht eine feiner abgestufte Fehlerbehandlung, weil es möglich ist, genau zu bestimmen, welcher Test gescheitert ist, und damit, welches Problem aufgetreten ist. Das zweite Codefragment informiert Sie lediglich, daß irgendwo ein Problem aufgetreten ist. In jedem Fall kann Ihnen die sorgfältige Überprüfung der Rückgabewerte einige ziemlich ärgerliche NullPointerExceptions ersparen.

Zu guter Letzt sollten Sie sehr vorsichtig sein, wenn Sie mit der Klasse DOMBuilder arbeiten. Es geht nicht darum, wie Sie die Klasse benutzen, sondern wann Sie sie benutzen. Wie ich bereits erwähnte, arbeitet diese Klasse mit der Eingabe auf ähnliche Art und Weise wie SAXBuilder. Und genau wie ihre SAX-Schwesterklasse besitzt sie build( )-Methoden, die verschiedene Arten der Eingabe wie ein Java-File oder einen InputStream entgegennehmen. Allerdings ist es stets langsamer, ein JDOM-Document aus einer Datei, URL oder einem I/O-Stream herzustellen, als SAXBuilder zu verwenden; das kommt daher, daß in DOMBuilder SAX benutzt wird, um einen DOM-Baum zu bauen, der dann in JDOM konvertiert wird. Natürlich ist das viel langsamer, als den Zwischenschritt (den Aufbau eines DOM-Baums) wegzulassen und einfach direkt von SAX zu JDOM überzugehen.

Wenn Sie also jemals Code wie diesen hier sehen

DOMBuilder builder = new DOMBuilder(  );

// aus einer Datei bauen
Document doc = builder.build(new File("input.xml"));

// aus einer URL bauen
Document doc = builder.build(
    new URL("http://newInstance.com/javaxml2/copyright.xml"));

// Aus einem I/O-Stream bauen
Document doc = builder.build(new FileInputStream("input.xml"));

dann sollten Sie schreiend davonlaufen! Natürlich hat DOMBuilder seine Berechtigung: Er ist sehr gut geeignet, um existierende JDOM-Strukturen in JDOM zu übertragen. Aber für die reine schnelle Eingabe ist es einfach die schlechtere Wahl bezüglich der Performance. Ersparen Sie sich einige Kopfschmerzen, und merken Sie sich diese Tatsache jetzt gut!

Und was kommt jetzt?

Es folgt ein Kapitel über fortgeschrittenes JDOM. In diesem Kapitel werde ich einige Feinheiten der API behandeln, wie Namensräume, die DOM-Adapter, den JDOM-internen Umgang mit Listen und alles andere, das diejenigen unter Ihnen interessieren könnte, die wirklich tief in die API einsteigen möchten. Dies sollte Ihnen umfassendes Wissen darüber vermitteln, wie Sie JDOM in Ihren Anwendungen neben DOM und SAX verwenden.

1)
Da JDOM 1.0 noch nicht endgültig ist, könnte sich zwischen der Veröffentlichung dieses Buches und Ihrem Download noch einiges ändern. Ich werde versuchen, eine aktuelle Liste von Änderungen auf der JDOM-Website (http://www.jdom.org) bereitzuhalten, und mit O’Reilly daran arbeiten, diese Änderungen und Updates so schnell wie möglich verfügbar zu machen.
2)
Standardmäßig ist dieser Parser Xerces, der mit JDOM geliefert wird. Allerdings können Sie auch jeden anderen Parser mit JDOM verwenden.
3)
Falls Sie mit *NIX nicht vertraut sind: Der Backslash am Ende jeder Zeile ( \) ermöglicht Ihnen einfach die Weiterführung eines Befehls in der nächsten Zeile; Windows-Benutzer sollten den vollständigen Befehl in einer Zeile eingeben.
4)
Beachten Sie, daß die Zeileneinteilung in dem Beispiel lediglich der Veröffentlichung dient; in Ihrem Dokument sollte jede Eigenschaft mit öffnendem Tag, Text und schließendem Tag in einer eigenen Zeile stehen.
5)
Sie entfernt auch mehr als ein Leerzeichen zwischen Wörtern. Der Textinhalt »sehr viele Leerzeichen« würde von getTextTrim( ) als »sehr viele Leerzeichen« zurückgegeben.