Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 13 Die eXtensible Markup Language (XML)
  gp 13.1 Auszeichnungssprachen
    gp 13.1.1 Die Standard Generalized Markup Language (SGML)
    gp 13.1.2 Extensible Markup Language (XML)
  gp 13.2 Eigenschaften von XML-Dokumenten
    gp 13.2.1 Elemente und Attribute
    gp 13.2.2 Beschreibungssprache für den Aufbau von XML-Dokumenten
    gp 13.2.3 Schema – eine Alternative zu DTD
    gp 13.2.4 Namensraum (Namespace)
    gp 13.2.5 XML-Applikationen
  gp 13.3 Die Java-APIs für XML
    gp 13.3.1 Das Document Object Model (DOM)
    gp 13.3.2 Simple API for XML Parsing (SAX)
    gp 13.3.3 Java Document Object Model (JDOM)
    gp 13.3.4 Pull-API StAX
  gp 13.4 Serielle Verarbeitung mit StAX
    gp 13.4.1 Unterschiede der Verarbeitungsmodelle
    gp 13.4.2 XML-Dateien mit dem Cursor-Verfahren lesen
    gp 13.4.3 XML-Dateien mit dem Iterator-Verfahren verarbeiten
    gp 13.4.4 Mit Filtern arbeiten
    gp 13.4.5 XML-Dokumente schreiben
  gp 13.5 Serielle Verarbeitung von XML mit SAX
    gp 13.5.1 Schnittstellen von SAX
    gp 13.5.2 SAX-Parser erzeugen
    gp 13.5.3 Die wichtigsten Methoden der Schnittstelle ContentHandler
  gp 13.6 XML-Dateien mit JDOM verarbeiten
    gp 13.6.1 JDOM beziehen
    gp 13.6.2 Paketübersicht
    gp 13.6.3 Die Document-Klasse
    gp 13.6.4 Eingaben aus der Datei lesen
    gp 13.6.5 Das Dokument im XML-Format ausgeben
    gp 13.6.6 Der Dokumenttyp
    gp 13.6.7 Elemente
    gp 13.6.8 Zugriff auf Elementinhalte
    gp 13.6.9 Liste mit Unterelementen erzeugen
    gp 13.6.10 Neue Elemente einfügen und ändern
    gp 13.6.11 Attributinhalte lesen und ändern
    gp 13.6.12 XPath
  gp 13.7 JAXP als Java-Schnittstelle zu XML
    gp 13.7.1 Einführung in XSLT
    gp 13.7.2 Umwandlung von XML-Dateien mit JDOM und JAXP
  gp 13.8 HTML-Dokumente einlesen


Galileo Computing

13.4 Serielle Verarbeitung mit StAdowntop

Mit dem Java Web Services Developer Pack 1.6 (JWSDP 1.6) wird die Pull-API StAX fester Bestandteil der XML-Verarbeitung mit Java. Um die API zu testen, kann unter http://stax.codehaus.org/ eine Implementierung der API bezogen werden. Damit ist es möglich, StAX zu nutzen, ohne das gesamte JWSDP 1.6 zu installieren.

Die allgemeine Vorgehensweise zum Parsen eines XML-Dokuments:

gp  Erzeuge eine XMLInputFactory.
gp  Erzeuge den passenden Parser.
gp  XMLStreamReader für die Cursor-Verarbeitung
gp  XMLEventReader für die Iterator-Verarbeitung
gp  Mit next() die nächste Komponente des XML-Dokuments einlesen.
gp  Den Typ der Komponente ermitteln und verarbeiten

Die Unterschiede zwischen der Cursor- und der Iterator-Verarbeitung sind auf den ersten Blick nicht eindeutig. Im Folgenden werden die Unterschiede kurz vorgestellt.


Galileo Computing

13.4.1 Unterschiede der Verarbeitungsmodelle  downtop

Beide Verarbeitungsmodelle bieten ähnliche Methoden, und die Verarbeitung der Inhalte ist auch sehr ähnlich. Der wesentliche Unterschied ist die Art und Weise, wie die Komponenten des XML-Dokuments geliefert werden. Bei der Cursor-Verarbeitung wird die Komponente direkt mit dem Parser-Objekt verarbeitet. Bei der Iterator-Verarbeitung wird ein XMLEvent-Objekt geliefert, das anderen Funktionen übergeben und in einer Datenstruktur gespeichert werden kann.

Bei der Cursor-Verarbeitung ist die zentrale Klasse der XMLStreamReader, mit dem auch auf die Inhalte der XML-Datei zugegriffen wird. Da diese Klasse ebenso verwendet wird, um auf das nächste Element der XML-Datei zugreifen zu können, steht zu einem Zeitpunkt immer nur eine Komponente des XML-Dokuments zur Verfügung. Der Vorteil ist die hohe Effizienz, da bei der Verarbeitung keine neuen Objekte erzeugt werden.

Bei beiden Versionen unterscheiden wir den lesenden und den schreibenden Zugriff auf die Elemente. Beim Lesen des XML-Dokuments ist es grundsätzlich nicht möglich, den Inhalt zu verändern. Für die Ausgabe bietet die API die Klassen XMLStreamWriter und XMLEventWriter. Damit ist es möglich, Element, die über die Reader gelesen werden, an die Writer zu übergeben und damit Änderungen an den Inhalten zu schreiben.


Galileo Computing

13.4.2 XML-Dateien mit dem Cursor-Verfahren lesen  downtop

Zunächst muss ein Parser erzeugt werden, mit dem das XML-Dokument verarbeitet werden soll.

Inputstream in = new FileInputStream("party.xml");
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader parser = factory.createXMLStreamReader(in);

Mit diesem Parser wird die XML-Datei iteriert (Tiefensuche) und der Typ der vom Parser gelieferten Komponente als ganzzahliger Wert zurückgegeben.

Der Parser liefert beim Verarbeiten eine Reihe von Events. Die Eventtypen sind als Konstanten in der Klasse XMLStreamConstants festgelegt. Passend zum Event stehen verschiedene Methoden zur Verfügung.

Der Parser liefert die folgenden Elemente:

gp  START_DOCUMENT: Der Beginn der XML-Datei. Bei diesem Event können Eigenschaften wie das Encoding des Dokuments ermittelt werden.
gp  END_DOCUMENT: Hier steht nur die Methode close() zum Schließen der Eingabe zur Verfügung.
gp  START_ELEMENT: Dieser Typ wird geliefert, wenn ein Element beginnt. Die Attribute und der Namensraum eines Elements können hier ausgewertet werden.
gp  END_ELEMENT: Das Ende eines Elements wird erreicht.
gp  CHARACTERS: Text innerhalb von Elementen. Text kann auf Whitespace getestet werden.
gp  ENTITY_REFERENCE: Dieser Event wird ausgelöst, wenn Entitäten in der XML-Datei auftauchen. Üblicherweise werden Entitäten zuerst aufgelöst und dann als CHARACTERS-Event geliefert.
gp  DTD: Mit diesem Event wird die DTD als String geliefert, und es ist möglich, auf Teile der DTD zuzugreifen.
gp  COMMENT: Kommentare in der XML-Datei.
gp  PROCESSING_INSTRUCTION: Verarbeitungsanweisungen wie Stylesheet-Angaben.

Die Events ATTRIBUTE und NAMESPACE werden nur in Ausnahmefällen geliefert. Diese Inhalte von Attributen sowie die Namensraumdaten können beim Event START_ELEMENT ermittelt werden.

Beispiel: Mit einer Schleife und einer switch-Anweisung lassen sich die Inhalte der XML-Datei auswerten. Verarbeitet wird ein XML-Dokument mit der Iterator-Variante:

while( parser.hasNext() ) {
    int event = parser.next();
    switch (event) {
        case XMLStreamConstants.END_DOCUMENT:
            System.out.println("END_DOCUMENT");
            parser.close();
            break;
        case XMLStreamConstants.START_ELEMENT:
            System.out.println("START_ELEMENT: " + parser.getLocalName() );
            for( int i = 0; i < parser.getAttributeCount(); i++ )
               System.out.println("Attribut: " + parser.getAttributeLocalName(i)
                                + " Wert: " + parser.getAttributeValue(i));
                                break;
        case XMLStreamConstants.CHARACTERS:
            if( !parser.isWhiteSpace() )
                System.out.println("CHARACTERS: " + parser.getText() );
            break;
        case XMLStreamConstants.END_ELEMENT:
            System.out.println("END_ELEMENT: " + parser.getLocalName() );
            break;
        default:
            break;
    }
}

Dieses Beispiels demonstriert, wie eine XML-Datei mit dem Cursor-Verfahren gelesen werden kann. Der folgenden Abschnitt zeigt die Verarbeitung mit dem Iterator-Verfahren.


Galileo Computing

13.4.3 XML-Dateien mit dem Iterator-Verfahren verarbeiten  downtop

Die Verarbeitung mit der Iterator-Variante der StAX-API ist ein wenig komplizierter, aber auch viel flexibler. Es wird nicht direkt mit dem Parser-Objekt auf die Daten zugegriffen, sondern es wird bei jeder Iteration ein XML-Event erzeugt. Mit diesem Objekt kann der Typ des Events ermittelt und ganz ähnlich zur Cursor-API ausgewertet werden.

Am Anfang wird wieder ein Parser erzeugt, und in einer Schleife werden die einzelnen Komponenten ausgewertet:

Beispiel: XML-Datei mit der Iterator-Variante parsen

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader parser = factory.createXMLEventReader(in);
while( parser.hasNext() ) {
    // Hier tritt der erste Unterschied zur Cursor-API auf.
    XMLEvent event = parser.nextEvent();
    int eventType = event.getEventType();
    switch (eventType) {
        case XMLStreamConstants.END_DOCUMENT:
            parser.close();
        break;
        case XMLStreamConstants.START_ELEMENT:
            StartElement element = event.asStartElement();
            System.out.println("START_ELEMENT: " + element.getName() );
            Iterator attributes = element.getAttributes();
            while( attributes.hasNext() ) {
                Attribute attribute = (Attribute)attributes.next();
                System.out.println("Attribut: " + attribute.getName() + " Wert: " + attribute.getValue() );
            }
        break;
        case XMLStreamConstants.CHARACTERS:
            Characters characters = event.asCharacters();
            if( !characters.isWhiteSpace() )
                System.out.println("CHARACTERS: " + characters.getData() );
                break;
        case XMLStreamConstants.END_ELEMENT:
            System.out.println("END_ELEMENT: " + event.asEndElement().getName() );
            break;
    }
}

Diese Form der Verarbeitung sieht auf den ersten Blick komplizierter aus, bietet aber zusätzliche Möglichkeiten, weil die erzeugten Objekte für die weitere Verarbeitung zur Verfügung stehen.


Galileo Computing

13.4.4 Mit Filtern arbeiten  downtop

Mit Hilfe von Filtern gibt es die Möglichkeit, nur Teile eines XML-Dokuments zu parsen. Diese Filter werden durch die Implementierung einer der Schnittstellen javax.xml.stream. EventFilter, für die Iterator-Variante, oder javax.xml.stream.StreamFilter, für die Iterator-Variante, programmiert. Es muss lediglich die Methode accept() implementiert und ein Boolean-Wert zurückgegeben werden. Als Parameter erwartet diese Methode entweder einen javax.xml.stream.events.XMLEvent bei der Iterator-Variante oder einen XMLStreamReader bei der Cursor-Variante.

Beispiel: Filter für die Iterator-Variante, der die schließenden Tags auslässt

import javax.xml.namespace.QName;
import javax.xml.stream.EventFilter;
import javax.xml.stream.events.XMLEvent;
public class PartyEventFilter implements EventFilter {
  public boolean accept(XMLEvent event) {
    return (!event.isEndElement()) ;
  }
}

Der Filter wird beim Erzeugen des Parsers mit der XMLInputFactory und dem vorhandenen XMLEventReader erzeugt.

Beispiel: Parser mit Event-Filter erzeugen

XMLEventReader filteredParser = factory.createFilteredReader( parser, 
  new PartyEventFilter() );

Dieses Verfahren der Dekoration wird in ähnlicher Form bei Streams verwendet.

Das Erzeugen eines Parsers mit Filter für die Cursor-Variante funktioniert analog. Durch Filter liefert die API eine einfache Konstruktion wenn nur bestimmte Teile des XML-Dokuments verarbeitet werden sollen.


Galileo Computing

13.4.5 XML-Dokumente schreiben  toptop

Im Gegensatz zu DOM-orientierten APIs, bei denen das gesamte Dokument im Speicher vorliegt und verändert werden kann, ist es bei StAX nicht möglich, die vorhandene XML-Datei zu verändern. Es ist aber trotzdem möglich, XML zu schreiben. Auch hier wird zwischen der Cursor- und der Iterator-Variante unterschieden. Bei der Iterator-Variante werden Event-Objekte geschrieben, die entweder aus einem gelesenen XML-Dokument stammen oder mit einer XMLEventFactory erzeugt werden. Bei der Cursor-Variante wird mit einem XMLStreamWriter die XML-Komponente direkt erzeugt und geschrieben. In beiden Fällen wird über die XMLOutputFactory ein passender Writer erzeugt. Die Reihenfolge, in der die Komponenten geschrieben werden, entscheidet über den Aufbau des zu erzeugenden XML-Dokuments.

Zuerst zeigen wir, wie mit der Cursor-Variante eine XML-Datei geschrieben werden kann. Dazu erzeugen wir mit der XMLOutputFactory ein XMLStreamWriter.

XMLOutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamWriter( new FileOutputStream( "c:/party.xml" ));

Der XMLStreamWriter schreibt die Elemente und Attribute direkt in eine XML-Datei.

// Der XML-Header wird erzeugt
writer.writeStartDocument();
// Zuerst wird das Wurzelelement mit Attribut geschrieben
  writer.writeStartElement( "party" );
    writer.writeAttribute( "datum""31.12.01" );
    // Unter dieses Element wird das Element gast mit Attribut erzeugt
    writer.writeStartElement( "gast" );
      writer.writeAttribute( "name""Albert Angsthase" );
    writer.writeEndElement();
  writer.writeEndElement();
writer.writeEndDocument();
writer.close();

Es werden keine speziellen Objekte erzeugt, sondern direkt geschrieben. Das hat den Nachteil, dass es nicht möglich ist, bereits gelesene XML-Komponenten direkt in einen anderen Stream zu schreiben – und wenn sich Elemente wiederholen, wird Programmcode wiederholt.

Genau wie beim Lesen von XML-Dokumenten wird bei der Iterator-Variante mit Event-Objekten gearbeitet. Diese Objekte können aus einem gelesenen XML-Dokument stammen oder für die Ausgabe erzeugt werden. Das Erzeugen der Event-Objekte und das Schreiben der Objekte sind zwei unabhängige Schritte. Im folgenden Beispiel wird gezeigt, wie Event-Objekte erzeugt und dann mit einem Stream geschrieben werden.

Bei der Iterator-Variante wird ebenfalls die Klasse OutputFactory verwendet. Diesmal wird aber ein Objekt der Klasse XMLEventWriter erzeugt. Für die Elemente und Attribute, die in diesem Beispiel verwendet werden, wird eine XMLEventFactory verwendet.

XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
XMLEventWriter writer = outputFactory.createXMLEventWriter( 
  new FileOutputStream( "c:/party.xml" ));
XMLEventFactory eventFactory = XMLEventFactory.newInstance();

Die Klasse XMLEventFactory bietet ähnliche Methoden wie die Klasse XMLStreamWriter an. Hier wird jedoch ein XMLEvent-Objekt erzeugt, und die Reihenfolge beim Erzeugen hat keinen Einfluss auf die Reihenfolge in der Ausgabe.

// Um Elemente zu schreiben, werden diese einzeln erzeugt.
XMLEvent header = eventFactory.createStartDocument();
// Zuerst wird für das Wurzelelement das öffnende und schließende Tag
// sowie das Attribut datum erzeugt. Es wird die Methode
// createStartElement(String prefix, String namespaceUri, String localName)
// verwendet, weil diese hier am einfachsten zu handeln ist.
XMLEvent startRoot = eventFactory.createStartElement( """""party" );
XMLEvent datumAttribut = eventFactory.createAttribute( "datum""31.12.01" );
XMLEvent endRoot = eventFactory.createEndElement( """""party" );
// Das Element gast wird auf die gleiche Art und Weise erzeugt
XMLEvent startGast = eventFactory.createStartElement( """""gast" );
XMLEvent name = eventFactory.createAttribute( "name",  "Albert Angsthase" );
XMLEvent endGast = eventFactory.createEndElement( """""gast" );
XMLEvent endDocument = eventFactory.createEndDocument();

Um die Elemente zu schreiben, werden sie dem XMLEventWriter übergeben. Hier entscheidet die Reihenfolge über den Aufbau der XML-Datei.

// Schreiben der Struktur
writer.add( header );
writer.add( startRoot );
    writer.add(datumAttribut);
    writer.add( startGast );
      writer.add( name );
    writer.add( endGast );
  writer.add( endRoot );
writer.add( endDocument );
writer.close();

In diesem Beispiel wurde gezeigt, wie Events erzeugt werden können und wie Events geschrieben werden. Das Schreiben von Elementen aus einer Eingabe funktioniert analog. Falls sich Elemente wiederholen oder aus einer anderen Quelle stammen, können sie hier mit dem Writer geschrieben werden. Dies zeigt deutlich, dass die Iterator-Variante flexibler als die Cursor-Variante ist. Diese Flexibilität wird durch einen etwas höheren Aufwand erkauft.

Der Abschnitt zeigte, wie mit der StAX-API XML gelesen und geschrieben werden kann. Es wurden die Unterschiede zwischen der Cursor- und der Iterator-Variante gezeigt, und es wurden Filter für die Eingabe kurz vorgestellt. Grundsätzlich ist die Iterator-Variante die flexiblere Lösung und in den meisten Fällen performant genug. Sie ist in jedem Fall performanter als eine DOM-basierte Lösung, wenn nicht die gesamte XML-Struktur im Speicher benötigt wird. Die Cursor-Variante sollte gewählt werden, wenn hohe Verarbeitungsgeschwindigkeit und geringer Speicherverbrauch Priorität haben. Diese Variante ist insbesondere für Endgeräte mit wenig Speicher und geringer Rechenleistung die bessere Wahl.

Die Anwendungsgebiete der StAX-API sind die gleichen wie die der SAX-API, weil die Vorteile beider Verfahren Performance und geringer Speicherverbrauch sind. Für die meisten Programmierer ist diese Form der Verarbeitung einfacher als die SAX-Variante, weil der XML-Inhalt direkt gelesen wird. SAX hat den Vorteil, dass es weit verbreitet ist und in vielen Programmiersprachen zur Verfügung steht. Wir stellen SAX im folgenden Kapitel kurz vor.

 << zurück




Copyright © Galileo Press GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de