XML-Verarbeitung mit Java

Werbung
Westfälische Wilhelms-Universität Münster
Thema:
XML-Verarbeitung mit Java
Ausarbeitung im Rahmen des Hauptseminars „Softwaretechnik“
am Lehrstuhl für Praktische Informatik
Themensteller:
Betreuer:
Prof. Dr. Herbert Kuchen
Dipl.-Wirt. Inform. Christoph Lembeck
vorgelegt von:
Stefan Heß
Franz-Ludwig-Weg 7
48149 Münster
0172/2772044
[email protected]
Abgabetermin:
2004-12-20
- II -
Inhaltsverzeichnis
Inhaltsverzeichnis ............................................................................................................. II
1 Einleitung......................................................................................................................1
2 Die Extensible Markup Language (XML)....................................................................1
2.1 Grammatiken für XML-Dokumente......................................................................2
2.2 XPath .....................................................................................................................3
2.3 XML Stylesheet Language ....................................................................................3
2.4 Namensräume ........................................................................................................4
2.5 Processing Instructions ..........................................................................................5
2.6 Whitespaces ...........................................................................................................5
3 XML-Verarbeitung mit SAX........................................................................................5
3.1 SAX-Interfaces ......................................................................................................6
3.1.1 Das Interface XMLReader ...........................................................................7
3.1.2 Das Interface ContentHandler......................................................................7
3.1.3 Weitere Interfaces ......................................................................................10
3.2 Weitere hilfreiche Möglichkeiten von SAX ........................................................11
4 XML-Verarbeitung mit DOM ....................................................................................12
4.1 Einlesen der XML-Daten.....................................................................................13
4.2 Manipulieren von XML-Dokumenten .................................................................15
5 XML-Verarbeitung mit Stylesheets............................................................................17
6 XML-Verarbeitung mit JDOM...................................................................................18
6.1 Einlesen und Ausgeben der XML-Daten.............................................................18
6.2 Manipulieren von XML-Dokumenten .................................................................18
7 Fazit und Ausblick ......................................................................................................19
Ergänzende Literatur .......................................................................................................20
Anhang ............................................................................................................................21
A Beispieldokument „dvdsammlung.xml“..............................................................21
B Beispieldokument “dvdsammlung.dtd“ ...............................................................22
C Beispieldokument „dvdsammlung.xsl“ ...............................................................23
D Beispielklasse „cHandler.java“............................................................................24
E Beispielklasse „DOMBeispiel.java“....................................................................25
F Beispielklasse „JDOMBeispiel.java“ ..................................................................26
-1-
1
Einleitung
In den vergangenen Jahren hat die Verwendung der Extensible Markup Language
(„XML“) als Datenformat für die Speicherung und Weitergabe von Informationen zunehmend an Bedeutung gewonnen. Vor allem die Plattformunabhängigkeit verhalf XML zu
seiner großen Bedeutung. Um die XML-Dateien auch automatisiert erstellen, manipulieren, auswerten und speichern zu können, wurden Schnittstellen und APIs entwickelt, standardisiert und implementiert. Insbesondere zeichnet sich Java durch einen Vielzahl von
Möglichkeiten für die Behandlung von XML-Dokumenten aus.
Zunächst sollen die Grundlagen von XML aufgezeigt werden, um ein Verständnis für das
Konzept dieser Sprache zu entwickelt. Danach soll die Arbeitsweise von SAX und DOM
als derzeit populärste APIs für die XML-Verarbeitung vorgestellt werden, sowie anschauliche Beispiele für die Umsetzung dieser Ansätze in Java gegeben werden.1 Anschließend
wird noch kurz auf die XML-eigene Transformationssprache XSL und deren Verwendung
in Java sowie einen neueren, Java-speziellen Ansatz für die XML-Verarbeitung (JDOM)
eingegangen.
2
Die Extensible Markup Language (XML)
Die Extensible Markup Language („XML“) ist eine Metasprache.2 Durch sie sollen
Sprachelemente einer anderen Sprache definiert werden. Dieses geschieht mit Hilfe von
Auszeichnungselementen („Tags“), welche die Struktur eines XML-Dokuments bestimmen. Da jedes geöffnete Tag auch wieder geschlossen werden muss und das bei Verschachtelungen genau in der umgekehrten Reihenfolge des Öffnens, entsteht in jedem
XML-Dokument eine Baumstruktur.
Ein XML-Dokument besteht aus einer Menge von Elementen. Ein Element besteht immer
aus einem Anfangs- und einem End-Tag sowie sämtlichen Daten zwischen diesen beiden
Tags. Diese Daten können einfacher Text (konkrete Daten) oder wiederum weitere Unterelemente („Kindelemente“) sein. Darüber hinaus kann ein Element auch mit Attributen
(definiert im Anfangs-Tag) versehen werden. Im Kopf eines XML-Dokuments können
1
2
Die Beispiele sind einfach strukturiert, um die Verständlichkeit zu sichern, z.B. wurde auf eine ausführliche Exception-Behandlung verzichtet.
Derzeit aktuelle Version ist XML 1.1, vgl. http://www.w3.org/TR/REC-xml/
-2-
formale Angaben über ein Dokument (z.B. Versionsnummer, Kodierung etc.) angegeben
werden.3
XML hat sich als das führende Format für den Datenaustausch etabliert. Dies liegt zunächst einmal daran, dass XML so gut wie keine Einschränkungen an die Struktur und den
Inhalt eines Dokumentes definiert. So können alle zu transportierenden Daten in der am
besten für den Austausch geeigneten Struktur formatiert werden. Da des Weiteren jedes
XML-Dokument ein reines Textdokument ist, können XML-Dateien hervorragend über
unterschiedlichste Übertragungswege transportiert werden. Zudem ist XML dadurch vollkommen plattformunabhängig.
2.1
Grammatiken für XML-Dokumente
Um XML-Dateien sinnvoll und effektiv verarbeiten können, ist es notwendig, dass die
Struktur der Datei bekannt ist (z.B. für den Datenaustausch zwischen 2 Unternehmen).
Hierzu besteht die Möglichkeit, eine Grammatik für ein XML-Dokument zu definieren.
Die zuvor herausgestellte Freiheit bei der Gestaltung von XML-Dokumenten leidet keineswegs darunter, da auch bei der Festlegung der Grammatik nahezu uneingeschränkte
Gestaltungsfreiheit für den Anwender besteht. Die Document Type Definition („DTD“) ist
eine einfache und die derzeit noch die verbreitetste Möglichkeit, eine solche Grammatik zu
definieren. In ihr können die Struktur des Dokuments (z.B. die Verschachtelung der Elemente) sowie die erlaubten Elemente und deren Eigenschaften (z.B. Attribute, Art der Daten etc.) festgelegt werden. Anhand seiner DTD kann man das XML-Dokument auf seine
Gültigkeit überprüfen.4 Die DTD wird entweder als externe Datei bereitgestellt und über
ein DOCTYPE-Element in die XML-Datei eingebunden oder direkt in das Dokument integriert, was sich allerdings der Übersichtlichkeit wegen weniger empfiehlt.5
DTDs bieten also eine sehr einfache und schnelle Möglichkeit, eine Grammatik für XMLDokumente festzulegen. Diese Einfachheit bringt aber auch einige Nachteile mit sich. So
verwenden DTDs selbst keine XML-Syntax und passen daher schlecht zu dem Ziel, eine
plattformunabhängige und hochportable Spezifikation zu schaffen. Zum anderen können
DTDs nur sehr einfache Datenstrukturen definieren und nutzen so die Möglichkeiten von
XML bei weitem nicht aus (z.B. exakte Datentypisierung, Definition von Namensräumen6
3
4
5
6
Ein beispielhaftes XML-Dokument findet sich in Anhang A („dvdsammlung.xml“). Dieses wird auch
für die Verarbeitung durch die später folgenden Programmbeispiele herangezogen.
Ein zu seiner DTD konformes XML-Dokument bezeichnet man somit als „gültiges Dokument“. Dies ist
eine Steigerung zu einem lediglich syntaktisch korrekten Dokument, welches als „wohlgeformt“ bezeichnet wird.
Als Beispiel findet sich in Anhang B eine mögliche DTD („dvdsammlung.dtd“) für das XMLDokument aus Anhang A.
zum Begriff Namensräume siehe Kapitel 2.4.
-3-
u.v.m.). Als weit mächtigere Möglichkeit, eine Grammatik zu definieren, wurden daher die
XML-Schemata entwickelt. Diese sind sehr viel komplexer als DTDs und daher auch weniger einfach zu erstellen und zu verstehen. Dafür können XML-Dokumente aber auch viel
exakter definiert werden. XML-Schemata sind selbst gültige XML-Dokumente und daher
sind sämtliche verfügbare XML-Techniken auf diese anwendbar. Auf eine detaillierte Betrachtung von XML-Schemata wird an dieser Stelle jedoch verzichtet, da derzeit DTDs
noch weit häufiger zur Definition einer Grammatik benutzt werden und daher noch besser
unterstützt werden.
2.2
XPath
Mit Hilfe der XML Path Language („XPath“7) wird es möglich, innerhalb eines XMLDokuments zu navigieren oder auf Daten (also Elemente, Attribute oder konkrete Datensätze) zuzugreifen. Dies geschieht durch die Angabe eines Pfades, welcher durch die
Baumstruktur führt. Dies kann sowohl ausgehend vom Wurzelelement des Dokuments als
auch relativ zum „aktuellen Standort“ im Dokument geschehen. Die Syntax von XPath
ähnelt der für Pfadangaben in Unix/Linux. Es ist außerdem möglich, Beschränkungen
(„Constraints“) für die Elementauswahl festzulegen, z.B. über den Wert von Attributen
oder die geforderte Existenz von Kindelementen.
Beispiele:
/dvd//person/surname
...findet alle Nachnamen von Personen die innerhalb von dvd-Elementen stehen
/dvd[genre]/length
...findet die Länge aller DVDs, für welche ein genre-Element existiert
//actor[@gender=“m“]/name
...findet alle Vornamen von allen männlichen Schauspielern
2.3
XML Stylesheet Language
Ein wesentliches Merkmal von XML ist die strikte Trennung von Inhalt und Layout. Eine
XML-Datei speichert lediglich eine Datenstruktur mit Informationen, aber keinerlei Angaben über die Darstellung dieser Daten. Um trotzdem die Information eines XMLDokuments anschaulich darstellen zu können, wird die Extensible Stylesheet Language
(„XSL“8) verwendet. Eine XSL-Datei enthält Formatierungsanweisungen für die Darstellung einer XML-Datei. So kann die Darstellung des Dokuments einfach und schnell geändert werden, ohne dass dabei das Dokument selbst verändert werden müsste. Ausserdem
7
8
Derzeit aktuelle Version ist XPath 1.0, vgl. http://www.w3.org/TR/xpath
Derzeit aktuelle Version ist XSL 1.0, vgl. http://www.w3.org/TR/xsl
-4-
können alternative Darstellungen für das gleiche XML-Dokument bereitgestellt werden.
Wichtigster Bestandteil von XSL ist die Extensible Stylesheet Language Transformation
(„XSLT“). Mit dieser Technik können neben „optischen“ Formatierungen auch komplette
Transformationen der XML-Dokumentstruktur vorgenommen werden. So können mit Hilfe von XSLT nicht benötigte Daten ausgeblendet werden oder neue Dokumente mit einer
vom Quelldokument komplett unterschiedlichen Struktur generiert werden. Hierzu bedient
sich XSLT unter anderem der schon erwähnten Technik XPath, um auf die Daten der
Quelle zuzugreifen.9 Des Weiteren stehen umfangreiche Möglichkeiten für Werteabfragen,
Schleifen, Variablendeklarationen u.v.m. zur Verfügung.
2.4
Namensräume
Wie schon erwähnt gibt es kaum Einschränkung bei der Gestaltung von XMLDokumenten. So kann auch die Bezeichnung von Elementen nahezu beliebig erfolgen.10
Solange ein Dokument ausschließlich für sich allein betrachtet und verarbeitet wird, ist
dies auch nicht weiter ein Problem. Ist dies nicht der Fall, kann es allerdings zu Namenskonflikten kommen, z.B. wenn in zwei Dokumenten gleichnamige Elemente existieren,
welche unterschiedliche Merkmale bezeichnen. Hier schafft das Konzept der Namensräume („Namespaces“) Abhilfe. Durch die Festlegung eines oder auch mehrerer Namensräume wird ein abgeschlossener Bereich festgelegt (in etwa vergleichbar mit lokalen Variablen), in welchem die Bezeichner der Elemente einmalig sind. Durch die Zuordnung zu einem jeweils eigenen Namensraum können nun auch gleichnamige Elemente eindeutig
voneinander unterschieden werden. Es ist ebenfalls möglich innerhalb eines Dokuments
mehrere Namensräume zu definieren und sogar innerhalb eines Namenraums einen weiteren Namensraum festzulegen. Die Einzigartigkeit eines Namensraums wird in der Namensraumdeklaration durch den Verweis auf einen URI sichergestellt. Um nun die einzelnen
Elemente einem Namensraum zuzuordnen, wird die Vorsilben-Notation verwendet, d.h.
jeder Elementbezeichnung wird die Bezeichnung des Namensraums vorangestellt (durch
Doppelpunkt getrennt). Um sich viel Tipparbeit zu ersparen und das Dokument übersichtlich zu halten, besteht auch die Möglichkeit der Deklaration eines Default-Namensraums.
Diesem Namensraum gehören dann alle Elemente des Dokuments an, sofern sie nicht explizit einem anderen Namensraum zugeordnet werden.
9
10
Als Beispiel findet sich in Anhang C eine mögliche Stylesheet-Datei („dvdsammlung.xsl“) für das
XML-Dokument aus Anhang A.
Einschränkungen bestehen im Grunde nur durch geschützte Namen und die Beschränkung der Menge
der erlaubten Zeichen.
-5-
<NS1:A xmlns:NS1=“http://irgend.was/NS1“>
<NS2:B xmlns:NS2=“http://irgend.was/NS2>
<NS1:C>Daten in Element C</NS1:C>
<NS2:D>Daten in Element D</NS2:D>
</NS2:B>
</NS1:A>
2.5
<!-<!-<!-<!-<!-<!--
NS1 wird
NS2 wird
C gehört
D gehört
NS2 wird
NS1 wird
deklariert
deklariert
zu NS1
zu NS2
geschlossen
geschlossen
-->
-->
-->
-->
-->
-->
Processing Instructions
Processing Instructions („PIs“) sind Verarbeitungshinweise für die das XML-Dokument
verarbeitende Applikationen (z.B. Parser). Der Dokumentenkopf ist zum Beispiel eine PI,
in der die XML-Version oder der Zeichensatz des Dokuments (also Meta-Informationen)
mitgeteilt werden. PIs werden durch Fragezeichen innerhalb der spitzen Klammern definiert.
<?xml version=“1.0“ encoding=“UTF-8“?>
<?xml-stylesheet href=“dvdsammlung.xsl“ type=“text/xsl“ ?>
2.6
Whitespaces
Whitespaces sind nicht sichtbare Zeichen wie Zeilenumbrüche, Tabulatoren, Einrückungen
und Leerzeichen. Teilweise sind diese Zeichen nicht Teil des Elementinhaltes sondern dienen nur zur Formatierung eines XML-Dokuments, um dieses für das menschliche Auge
besser lesbar zu machen. Siee haben also keinen Informationswert und können bei der Verarbeitung des XML-Dokuments ignoriert werden. Die Entscheidung, ob ein Whitespace
ignoriert werden kann oder zu den Nutzdaten gehört, kann der Parser nur durch Hinzunahme einer Grammatik (DTD oder XML-Schema) fällen.
3
XML-Verarbeitung mit SAX
Die Simple API for XML („SAX“11) ist eine weit verbreitete Möglichkeit, XMLDokumente zu verarbeiten. Dabei ist SAX nicht als konkreter XML-Parser zu verstehen,
sondern besteht aus einer Menge von Empfehlungen, welche die Arbeitsweise eines SAXParsers beschreiben. Daher ist der SAX-Standard auch nicht beschränkt auf eine bestimmte
Plattform oder eine bestimmte Programmiersprache. So existieren für alle gängigen Systemkonfigurationen Parser, die nach dem SAX-Prinzip arbeiten. Die für die Java-Plattform
geläufigste SAX-Implementierung ist das Paket „Xerces“ von der Apache Group, welches
kostenlos erhältlich ist12 und auch für die folgenden Beispiele verwendet wird. SAX wird
11
12
Derzeit aktuelle Version ist SAX 2.0.2, vgl. http://www.saxproject.org
Derzeit aktuelle Version ist Xerces2 2.6.2, vgl. http://xml.apache.org/xerces2-j/
-6-
tatsächlich von keiner Organisation explizit unterstützt, ist durch seine weite Verbreitung
in der Praxis aber allgemein akzeptiert.
Die Verarbeitung eines XML-Dokuments mit SAX funktioniert nach einem ereignisorientierten Prinzip. Das Dokument wird Schritt für Schritt eingelesen und jedes Mal, wenn der
Parser auf ein XML-Konstrukt (Elementanfang oder -ende, konkrete Daten usw.) trifft,
wird ein bestimmtes Event ausgelöst, welches dann anwendungsabhängig weiterverarbeitet
werden kann. Der XML-Parser arbeitet sich also sukzessiv linear durch das Dokument.
Für folgendes XML-Fragment würden z.B. die u.a. Events ausgelöst werden:
<dvd><titel>Der Herr der Ringe</titel></dvd>
ausgelöste EVENTS:
Element geöffnet: „dvd“
Element geöffnet: „titel“
Character Data: „Der Herr der Ringe“
Element geschlossen: „titel“
Element geschlossen: „dvd“
Nachdem ein Event ausgelöst wurde, wird der verwendete Speicher wieder freigegeben,
der XML-Parser „vergisst“ also sofort wieder die eingelesenen Daten. Dies ist einer der
Hauptgründe für die häufige Verwendung von SAX, da das zu parsende Dokument (im
Gegensatz zum DOM-Modell) für die Verarbeitung nicht vollständig im Speicher des
Rechners abgelegt werden muss, was insbesondere bei sehr großen Dokumenten problematisch sein kann (z.B. bzgl. Speicherverfügbarkeit oder Verarbeitungsgeschwindigkeit).
3.1
SAX-Interfaces
Die vom SAX-Parser ausgelösten Events müssen nun von der jeweiligen Applikation verarbeitet werden. Hierzu ist die Implementierung einer Reihe von Event-Methoden
(„EventHandler“) notwendig. Diese Schnittstellen zum XML-Parser werden im Paket
org.xml.sax bereitgestellt und müssen, genau wie der verwendete Parser, in die Anwendung importiert werden. Die folgende Tabelle zeigt die wichtigsten in diesem Paket zur
Verfügung stehenden Interfaces.
Interface
XMLReader
ContentHandler
ErrorHandler
DTDHandler
EntityResolver
Tab. 3.1:
Verwendung
Erstellung des SAX-Parsers
EventHandler für die beim Parsen identifizierten XML-Konstrukte
EventHandler für Fehler beim Parsen des XML-Dokuments
EventHandler für die Validierung von XML-Dokumenten mit Hilfe einer DTD
EventHandler für die Verwendung von externen Ressourcen
Interfaces im Paket org.xml.sax
-7-
3.1.1
Das Interface XMLReader
Diese Klasse stellt die Schnittstelle zum eigentlichen Parser zur Verfügung. Um ein Dokument parsen zu können wird ein Parser-Objekt der Klasse SAXParser (welche ebenfalls
importiert wurde) instanziiert. Über dieses Objekt kann nun der Zugriff auf das XMLDokument erfolgen. Hierzu dient die Methode parse(), welche entweder einen URI auf
das Dokument (wie im Beispiel) oder ein InputSource-Objekt (hierzu später mehr) als Parameter erfordert. Damit beim Parsen des Dokuments auch die verschiedenen
EventHandler aufgerufen werden können, müssen diese zuvor beim Parser-Objekt bekannt
gemacht werden. Hierzu wird ein Objekt der jeweiligen EventHandler-Klasse instanziiert
und über entsprechende set-Methoden beim Parser-Objekt angemeldet (vgl. Beispiel). Entsprechend können die aktuell registrierten Handler über entsprechende get-Methoden abgefragt werden.
3.1.2
Das Interface ContentHandler
Nachdem die Handler-Klassen beim Parser-Objekt angemeldet worden sind und die
parse()-Methode aufgerufen wurde, wird bei jedem neu und fehlerfrei geparsten Element
des XML-Dokuments eine Methode der Klasse ContentHandler aufgerufen, welche die
enthaltenen Daten verarbeiten soll. Das Interface ContentHandler ist jedoch abstrakt deklariert und muss daher zunächst implementiert werden. Das macht natürlich auch Sinn, da in
der Interpretation und Verarbeitung der geparsten Daten die eigentliche Programmlogik
liegt, welche für jeden Zweck unterschiedlich sein kann. Zu implementieren sind die in
folgender Tabelle aufgeführten Event-Methoden, welche abhängig vom jeweils festgestellten Event aufgerufen werden.
Methodenname
startDocument()
endDocument()
startElement()
endElement()
startPrefixMapping()
endPrefixMapping()
characters()
processingInstruction()
skippedEntity()
ignorableWhitespace()
setDocumentLocator()
Tab. 3.2:
triggerndes Event
zum Beginn eines parse-Vorgangs
zum Ende eines Parse-Vorgangs
zum Beginn eines Elements
zum Ende eines Elements
zum Beginn eines Namensraums
zum Ende eines Namensraums
beim Vorkommen von konkreten Daten
beim Vorkommen einer Processing Instruction
beim Vorkommen eines ausgelassenen Entitys
beim Vorkommen von auszulassenden Whitespaces
muss explizit aufgerufen werden
Methoden der Klasse ContentHandler
-8-
Im Folgenden werden diese Methoden etwas genauer vorgestellt und ein passendes Beispiel gegeben, welches folgenden Methodenbeginn vervollständigt:13
public class cHandler implements ContentHandler {
int elemente = 0; int dvdanzahl = 0;
boolean elementausgeben = false;
//...Event-Methoden folgen
}
//Hilfsvariablen deklarieren
public void startDocument() und public void endDocument()
Diese Methoden werden zum Anfang bzw. Ende des Parse-Vorgangs aufgerufen, sind also
immer die erste und die letzte aufgerufene Methode. So wird dem Programmierer die Möglichkeit gegeben, vor dem eigentlichen Parsen noch Einstellungen vorzunehmen oder danach evtl. während des Parsens gesammelte Informationen aufzubereiten.
public void startDocument()
{ System.out.println("Das Dokument beginnt..."); }
//Startmeldung
public void endDocument()
{ System.out.println(dvdanzahl+" DVDs mit "+elemente+" Einzelelementen
gefunden!"); }
//Schlußmeldung
public void startElement(String URI, String local, String name, Attributes atts)
Diese Methode wird immer dann aufgerufen, wenn der Parser auf ein Element trifft (also
ein öffnendes Tag parst). Der Methode werden die folgenden vier Parameter übergeben:
URI
ist die vollständige Angabe der Namensraum-URI, local ist der Name des Elements
ohne den Namensraum-Prefix und name ist der vollständige Name des Elements. Ist kein
Namensraum für das Element definiert, sind local und name also identisch. Die Attribute
des Elements werden als Verweise in einem Objekt der Klasse Attributes übergeben. Diese Klasse ist ebenfalls Teil des Pakets org.xml.sax und bietet einige Methoden, mit denen
auf die Attribut-Informationen zugegriffen werden kann, z.B. die Anzahl der Attribute oder
die Namen, Typen und Indizes einzelner Attribute. Der Zugriff auf einzelne Attribute wird
Array-ähnlich über Indizes oder - falls bekannt - den Namen des jeweiligen Attributs realisiert.
public void startElement(String URI, String local,String name, Attributes atts)
{ elemente++;
//Elemente zaehlen
if (local=="dvd") dvdanzahl++;
//DVDs zaehlen
if (local=="titel") elementausgeben = true; } //Bei „titel“-Element..
// ..Ausgabe vorbereiten
13
Die einzelnen Beispiele setzen sich zur vollständigen Implementierung des ContentHandler-Interfaces
in Anhang D zusammen.
-9-
public void endElement(String URI, String localName, String qname)
Diese Methode wird immer dann aufgerufen, wenn der Parser auf das Ende eines Elements
trifft (also ein schließendes Tag parst). Dieser Methode werden im Grunde dieselben Parameter übergeben wie der Methode startElement(), nicht allerdings der Parameter für
Attribute, da schließende Tags keine Attribute enthalten können.
public void endElement(String URI, String local, String name)
{ elementausgeben = false; }
//Hilfsvar. zuruecksetzen
public void startPrefixMapping(String prefix, String URI) und
public void endPrefixMapping(String prefix)
Diese Methoden helfen bei der Verarbeitung von Namensräumen. Die start-Methode wird
aufgerufen, wenn ein Namensraum deklariert wird. Dies ist immer dann der Fall, wenn ein
Namensraum-URI mit einem Präfix über das Attribut xmlns assoziiert wird. Diese URI
sowie das Präfix werden als String übergeben. Die end-Methode wird immer dann aufgerufen, wenn der Namensraum geschlossen wird, also wenn das Element geschlossen wird, in
dem der Namensraum definiert wurde. Übergeben wird nur die Namensraumbezeichnung.
Zu beachten ist bei der start-Methode darüber hinaus, dass im Gegensatz zur „normalen“
linearen Vorgehensweise des Parsers zunächst der EventHandler für die Namensraumdeklaration aufgerufen wird und erst dann die startElement()-Methode. Grund dafür ist,
dass das Element selbst dem neu definierten Namensraum angehört. Der defaultNamensraum wird durch einen Leerstring charakterisiert.
public void startPrefixMapping(String prefix, String URI)
{ if (!prefix.equals("")) System.out.println("Namensraumwechsel:"+prefix); }
//falls nicht default-Namensraum, Meldung ausgeben!
public void endPrefixMapping(String prefix){}
public void characters(char[] ch, int start, int length)
Diese Methode wird immer dann aufgerufen, wenn der Parser auf konkrete Daten in Form
einer Aneinanderreihung von Zeichen trifft. Dies ist im Normalfall zwischen dem öffnenden und dem schließenden Tag eines Elements der Fall. Die Zeichenkette wird vom Parser
dabei nicht als String, sondern als array von chars übergeben. Des Weiteren werden die
Startposition und die Länge der Zeichenkette angegeben.
public void characters(char[] ch, int start, int length)
{ if (elementausgeben==true) {
String str = new String(ch, start, length);
System.out.println("DVD "+dvdanzahl+":\t"+str); }
}
//Daten ggf. ausgeben
- 10 -
public void processingInstruction(String target, String data)
Diese Methode wird immer dann aufgerufen, wenn der Parser auf eine Processing Instruction stößt und liefert das Ziel (also den Namen) der PI sowie deren Argumente (als ein
String). So kann z.B. das Verhalten einer Applikation über eine bestimmte PI geändert
werden, ohne dass der eigentliche Inhalt des XML-Dokuments geändert wird. Eine Sonderstellung nimmt die einleitende PI (der XML-Dokumentkopf) ein, der zwar vom Parser
verarbeitet wird, aber kein Event auslöst.
public void processingInstruction(String target, String data)
{ System.out.println("Processing Instruction: "+data); }
//PI ausgeben
public void ignorableWhitespace(char[] ch, int start, int length)
Diese Methode wird aufgerufen, wenn der Parser auf ein Whitespace trifft, welches er als
ignorierbar einstuft. Um unterscheiden zu können, ob ein Whitespace ignoriert werden darf
oder Informationen enthält, benötigt der Parser zwingend eine Grammatik für das XMLDokument (DTD). Zwar werden die Whitespaces wie bei der characters-Methode übergeben, im Normalfall werden sie aber keine Behandlung durch die Applikation erfahren.
public void setDocumentLocator(Locator locator)
Diese Methode bietet die Möglichkeit, während des Parsens die aktuelle Position im Dokument auszulesen. Dazu muss ein Locator als Instanzvariable des ContentHandler initialisiert werden. Die set-Methode wird bei der Erstellung dieses Objekts aufgerufen. Danach
wird das Locator-Object immer auf die aktuellen Positionswerte gesetzt, welche ausgelesen werden können (z.B. zur Ausgabe der Zeilennummer eines Elements o.ä.).
public void skippedEntity(String name)
Diese Methode wird immer aufgerufen, wenn der Parser eine DTD nicht korrekt auflösen
kann und diese deshalb übergeht. Es liegt dann in der Hand des Programmiers, wie er in
diesem Fall weiter vorgehen möchte.
3.1.3
Weitere Interfaces
Wie bei Java üblich, sollte die Fehlerbehandlung auch bei der XML-Verarbeitung mit SAX
nicht durch Java selbst übernommen werden, sondern durch den Programmierer implementiert werden. Zu diesem Zweck dient das Interface ErrorHandler. Bei der Verarbeitung
eines XML-Dokuments können Fehler auftreten, die eine
SAXParseException
auswerfen.
- 11 -
Ein Objekt dieser Fehlerklasse wird auch von den drei Methoden des Interfaces
ErrorHandler als Argument erwartet. Diese drei Methoden unterscheiden sich durch die
Fehlerklasse, für Warnungen wird public void warning(SAXParseException e) aufgerufen, um normale Fehler kümmert sich public void error(SAXParseException e) und für
fatale Fehler (Abbruch des Parsens) ist public void fatalError(SAXParseException e)
zuständig. Das übergebene Fehler-Objekt kann wiederum mit verschiedenen get-Methoden
ausgewertet werden (Zeilen-/Spaltennummer des Fehlers, URI/Name des Fehlerdokuments
und die Fehlermeldung selbst).
Des Weiteren stehen die Interfaces DTDHandler und
eines XML-Dokumentes zur Verfügung.
3.2
EntityResolver
für die Validierung
Weitere hilfreiche Möglichkeiten von SAX
Neben dem direkten Einlesen eines XML-Dokuments aus einer Datei können auch andere
Datenquellen benutzt werden (z.B. Bytestreams), welche über ein Objekt der Klasse
java.io.InputStream
gekapselt werden. Der parse()-Methode wird dann die SystemId des
InputSource-Objekts übergeben.
Das vorgestellte Beispiel ist entgegen der ursprünglichen Absicht nicht komplett plattformunabhängig, da der verwendete Parser hart im Code festgelegt ist. Die Parser-Klasse
würde auf einem anderen System aber möglicherweise fehlen. Hier schafft die Klasse
org.xml.sax.helpers.XMLReaderFactory Abhilfe, welche die Erstellung des Parsers übernimmt. Die richtige Parser-Klasse findet diese Fabrik-Methode in der System-Eigenschaft
org.xml.sax.driver, in welcher der Klassenpfad des verwendeten Parsers festgehalten
wird. Dieser kann dann systemabhängig beim Programmaufruf14 oder in einer Config-Datei
gesetzt werden. Somit ist die Applikation wieder komplett portabel.15
Das Verhalten des SAX-Parsers kann direkt im Programm beeinflusst werden. Hierfür besitzt die XMLReader-Klasse eine setProperty()- bzw. setFeature()-Methode, mit denen der
Parser konfiguriert werden kann. Nach der Implementierung des ContentHandler kann das
Beispiel-XML-Dokument mit folgender simplen Klasse verarbeitet werden:
14
15
Dazu muss beim Programmaufruf der Parameter „-D“ gesetzt werden:
java -Dorg.xml.sax.driver=[KLASSENPFAD DES PARSERS] SAXBeispiel [XML-Datei]
Im Beispiel wird der Einfachheit halber der Pfad zum Parser direkt übergeben.
- 12 -
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.*;
public class SAXBeispiel {
public static void main (String args[]) {
SAXBeispiel beispiel = new SAXBeispiel();
beispiel.parseme(args[0]); }
public void parseme(String uri) {
try {
//Parser-Objekt erstellen...
XMLReader parser =
XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
System.out.println("Parser wurde erstellt!");
//ContentHandler-Objekt erstellen und anmelden...
ContentHandler ch = new cHandler();
parser.setContentHandler(ch);
System.out.println("ContentHandler zugewiesen!");
//...parsen...
System.out.println("Dokument '"+uri+"' wird nun geparst...");
parser.parse(uri);
}
catch (IOException e){System.out.println("IOError:"+e);}
catch (SAXException e){System.out.println("SAXError"+e);}
}
}
4
XML-Verarbeitung mit DOM
Im Gegensatz zu SAX arbeitet das Document Object Model („DOM“16) nach einem völlig
gegensätzlichen Prinzip. Zunächst wird vom Parser eine Kopie der kompletten Input-Daten
in den Speicher gelegt. Dies geschieht durch einen initialen Aufruf der parse()-Methode.
Nun stehen sämtliche Daten des Dokuments jederzeit der Anwendung zur Verfügung. Eine
weitere Eigenschaft von DOM ist es, dass das Dokument schon im Speicher in einer Art
Baumstruktur abgelegt wird und damit über bestimmte Methoden ein wahlfreier Zugriff
auf einzelne Elemente (Baumknoten) realisiert werden kann. Diese Vorzüge von DOM
definieren gleichzeitig auch die Nachteile. Zum einen ist DOM extrem speicherlastig, zum
anderen kann DOM nur vollständig vorliegende Dokumente verarbeiten und kann daher
z.B. im Gegensatz zu SAX nicht an einen konstanten Datenstrom gekoppelt werden.
Das Xerces-Paket der Apache Group hält auch einen DOM-Parser bereit. Zur Verarbeitung
nach dem DOM-Modell ist weiterhin das Paket org.w3c.dom notwendig. Analog zu SAX
muss auch bei DOM zunächst ein Parser-Objekt instanziiert werden, dessen Methode
parse() das XML-Dokument in den Speicher schreibt. Darauf wird mit Hilfe der Methode
getDocument() ein Objekt der Klasse Document erzeugt, über welches im Folgenden auf den
XML-Baum im Speicher des Rechners zugegriffen werden kann.
16
Derzeit aktuelle Version ist DOM Level 3 Core Version 1.0, vgl. http://www.w3.org/TR/DOM-Level-3Core
- 13 -
4.1
Einlesen der XML-Daten
Der im Speicher abgelegte XML-Baum wird vom DOM-Parser als Menge von Knoten
gesehen. Ein Knoten ist jede Stelle im Dokument, an der sich die Datenstränge aufspalten
und sich weiter verästeln. Dabei wird jede Information des Dokuments als Knoten angesehen, also nicht nur „komplette“ Elemente mit Kindelementen, sondern z.B. auch Attribute
oder PIs. Entlang dieser Knoten kann sich der DOM-Parser zu jedem beliebigen Element
des XML-Dokuments „hangeln“. Für den Zugriff auf die Informationen der Knoten wird
das Node-Interface aus dem org.w3c.dom-Paket benutzt, welches wiederum für jeden Knotentyp eine Unterklasse besitzt, so z.B. auch die schon erwähnte Klasse Document für das
gesamte Dokument im Speicher. Die folgende Tabelle zeigt die wichtigsten Subklassen des
Interfaces Node und welche Art von Objekten jeweils behandelt wird:
Interface...
Attr
CharacterData
Document
DocumentType
Element
ProcessingInstruction
Tab. 4.1:
...behandelt...
Attribut-Objekte
Strings außerhalb eines Elements
das gesamte XML-Dokument im Speicher
die DTD des Dokuments
Element-Objekte
PI-Objekte
Subklassen des Interfaces Node
Diese Schnittstellen haben teilweise auch noch weitere Unterklassen, um bestimmte Untergruppen der Objekte zu behandeln. Die gemeinsame Oberklasse Node dieser Interfaces
stellt sicher, dass man jedes Knotenobjekt unabhängig von der Art des Knotens mit denselben Methoden behandeln kann. Die folgende Tabelle zeigt die wichtigsten Methoden und
was diese leisten:
Methoden-Signatur
NodeList getChildNodes()
Node getFirstChild()
NamedNodeMap getAttributes()
Node getLastChild()
String getNameSpaceURI()
String getNodeName()
short getNodeType()
String getNodeValue()
String getPrefix()
boolean hasChildNodes()
Tab. 4.2:
Rückgabewert
liefert eine Liste mit allen Kindelementen
liefert den ersten Kindknoten
liefert eine Liste mit allen Attributen (falls der Knoten ein Element ist)
liefert den letzten Kindknoten
liefert den URI des Namensraums
liefert den Namen des Knotens
liefert den Typ des Knotens
liefert den Wert des Knotens
liefert das Namensraumpräfix
stellt fest, ob Kindknoten vorhanden sind
Methoden des Interfaces Node
Bei den Methoden getChildNodes() und getAttributes() wird eine Liste von Knoten zurückgegeben, auf die mit Hilfe von Indizes bzw. im Falle von NamedNodeMap zusätzlich anhand der Namen der enthaltenen Knoten zugegriffen werden kann. Der Typ des Knoten
wird durch einen Ganzzahlwert repräsentiert. Diese stehen als festgelegte Konstanten im
- 14 -
über welche man den Knotentyp abfragen kann.17 Im folgenden Beispiel
wird dies über eine switch-case-Konstruktion realisiert. Für jeden speziellen Knotentyp
stehen weitere spezifische Methoden zur Verfügung18, z.B. zur Behandlung von Namensräumen oder die Verarbeitung von PIs und Kommentaren. Insbesondere das schon erwähnte Subinterface Document stellt einige nützliche Methoden für den Zugriff auf das Dokument zur Verfügung, z.B. solche, die eine Knotenliste mit allen Knoten eines bestimmten
Namens liefern (getElementsByTagName(), auch verfügbar im Interface Element) oder zur
Umbenennung von Knoten (renameNode()).
Node-Interface,
Die Baum-Struktur eines XML-Dokuments bietet sich geradezu an für eine Abarbeitung
mit Schleifen und Rekursion. Bei jedem Element-Knoten, der weitere Kindelemente besitzt, wird rekursiv die Schlüsselmethode aufgerufen (im Beispiel gibBaum()) und der jeweilige Kindknoten als neue Baumwurzel übergeben. Mit den bisher vorgestellten Möglichkeiten kann nun ein XML-Dokument vollständig mit DOM durchgearbeitet werden.
Für die Behandlung der diversen Knotentypen werden natürlich nicht alle, sondern jeweils
beispielhafte Verarbeitungsmethoden dargestellt.
import
import
import
import
java.io.*;
org.w3c.dom.*;
org.xml.sax.SAXException;
com.sun.org.apache.xerces.internal.parsers.DOMParser;
public class DOMBeispiel {
public static void main (String args[]) {
DOMBeispiel beispiel = new DOMBeispiel();
beispiel.parseme(args[0]);
}
private void parseme(String uri) {
try {
DOMParser parser = new DOMParser();
//Parser erstellen
parser.setFeature("http://apache.org/xml/features/
dom/include-ignorable-whitespace",false);
//Whitespaces ignorieren
parser.parse(uri);
//Datei in den Speicher legen
Document d = parser.getDocument();
//Document-Objekt erzeugen
Element root = d.getDocumentElement();
//Wurzel-Objekt erzeugen
this.gibBaum(root);
//Schlüsselmethode aufrufen
}
catch (SAXException e){System.out.println("Fehler!");}
catch (IOException e){System.out.println("Fehler!");}
}
private void gibBaum(Node n) {
//Schlüsselmethode
switch(n.getNodeType()) {
//Abfrage des Knotentyps
case Node.ELEMENT_NODE:
NodeList kinder = n.getChildNodes();
//Knotenliste für Kindelemente
System.out.print("Element "+n.getNodeName+": ");
//Name des Elements ausgeben
17
18
http://java.sun.com/j2se/1.5.0/docs/api/constant-values.html#org.w3c.
Für die Anwendung der typspezifischen Methoden muss die Knotenvariable auf den speziellen Typ
gecastet werden.
- 15 -
//Schleife, um jedes Kindelement rekursiv abzuarbeiten:
for (int i=0; i < kinder.getLength(); i++)
{this.gibBaum(kinder.item(i));}
break;
case Node.TEXT_NODE:
System.out.println("Text: "+n.getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
System.out.println("CData: "+n.getNodeValue());
break;
case Node.ATTRIBUTE_NODE:
Attr a = (Attr)n;
String attr_str = "Attribut "+a.getNodeName()+":"+a.getNodeValue();
System.out.println(attr_str);
break;
case Node.COMMENT_NODE:
System.out.println("Kommentar: "+n.getNodeValue());
break;
case Node.PROCESSING_INSTRUCTION_NODE:
System.out.println("PI: "+n.getNodeValue());
break;
}
}
}
Die Datenausgabe ist in diesem Beispiel zwar sehr unschön und schlecht lesbar, aber das
Vorgehen des DOM-Parsers solte gut nachvollziehbar sein. Der Import der SAXException
ist erforderlich, da der Xerces-Parser beim Einlesen des Dokuments interessanterweise auf
den SAX-Parser zurückgreift. Dies ist aber nicht bei allen DOM-Parsern so. Über die Methode setFeature() des Parsers kann man die Verhaltensweise des Parsers beeinflussen.
Im Beispiel werden über einen solchen Schalter die im XML-Dokument zwar vorhandenen, aber nicht für die Verarbeitung vorgesehenen Whitespaces ignoriert, da sie keinerlei
Informationsgehalt besitzen. Wie beim SAX-Parser ist eine Grammatik für das Dokument
notwendig, um die ignorierbare Whitespaces von den informationshaltigen unterscheiden
zu können.
4.2
Manipulieren von XML-Dokumenten
Wenn das XML-Dokument einmal als Baum im Speicher liegt, kann man über sehr einfache Methoden die Informationen und die Struktur des Dokuments verändern.
Um neue Knoten (z.B. Elemente oder Attribute) in den Baum einzufügen, muss zunächst
ein Knoten des gewünschten Typs über die dem Typ zugehörige create-Methode der
Document-Klasse erstellt werden. Dieser neue Knoten kann dann auf drei verschiedene Arten in den Baum eingehangen werden. Alle drei Einfüge-Methoden sind im Interface Node
implementiert, der neue Knoten steht also immer im Verhältnis zum ausführenden NodeObjekt. appendChild() hängt den neuen Knoten an das ausführende Objekt an,
insertBefore() fügt den neuen Knoten vor einem anderen ein und replaceNode() ersetzt
einen bestehenden Knoten durch einen neuen. Eine Ausnahme dieses Vorgehens bilden
- 16 -
Knoten vom Typ Attr, also Attribute eines Elements. Für diese existieren im ElementInterface eigene set- und remove-Methoden zum Hinzufügen und Entfernen von Attributen. Des Weiteren verfügt das Node-Interface über zahlreiche nützliche Methoden zum Entfernen, Ersetzen, Vertauschen und Bearbeiten von Knoten. Das folgende Beispiel19 fügt
einen neuen Elementknoten mit Attribut und Text in jedes dvd-Element des Dokuments ein
und entfernt das length-Element.
NodeList nl = d.getElementsByTagName("dvd"); //Knotenliste mit DVD-Elementen
for (int i=0; i < nl.getLength(); i++) {
//Schleife über die Knotenliste
Element e = (Element)nl.item(i);
//einzelnes DVD-Elem. ausfiltern
Element neuE = d.createElement("misc");
//neuen Elementknoten erzeugen
Attr neuA = d.createAttribute("mark");
//Attributknoten erzeugen
Text neuT = d.createTextNode("new text");
//Textknoten erzeugen
neuE.setAttributeNode(neuA);
//Attrib. dem Elem. zuweisen
Node neuN = e.appendChild(neuE);
//neues Elem. an dvd-Knoten hängen
neuN.appendChild(neuT);
//Textknoten an neuen Knot. hängen
e.removeChild(e.getElementsByTagName("length").item(0));
//length-Knoten entfernen
}
Um die getätigten Änderungen auch dauerhaft zu sichern, muss das geänderte Dokument
auch wieder in eine Datei geschrieben werden, schließlich wurden die Änderungen bisher
nur im Arbeitsspeicher vollzogen. Dazu muss zunächst ein java.io.FileWriter-Objekt
erstellt werden, um eine neue Datei zu erzeugen. Zusätzlich wird ein Serializer benötigt,
der den gespeicherten XML-Baum in bestimmte Ausgabeformate überführt. Der
Serializer wird üblicherweise vom DOM-Parser-Paket zur Verfügung gestellt. Die folgenden Beispielzeilen schreiben also schon die geänderte XML-Datei.20
FileWriter fw = new FileWriter("dvdsammlung_neu.xml", false);
XMLSerializer ausgabe = new XMLSerializer (fw, null);
ausgabe.serialize(d);
Nicht unerwähnt bleiben soll die hier vernachlässigte, jedoch nicht weiter erläuterte Fehlerbehandlung mit Hilfe der Exception-Klasse DOMException. Des Weiteren ist am Beipiel zu
kritisieren, dass der verwendete DOM-Parser explizit importiert wird, wodurch die Plattformunabhängigkeit zerstört wird. Für solche und viele andere Probleme hilft Java mit der
Java API for XML Parsing („JAXP“21), welche etliche Hilfsklassen, Fabriken u.v.m. zur
Verfügung stellt. Auf die vielen Möglichkeiten der JAXP soll aus Platzgründen an dieser
Stelle nicht weiter eingangen werden.
19
20
21
Der Code ist in die Beispiel-Klasse einzufügen, nach der Erzeugung des Dokumentobjekts.
Die zusammenhängende Beispielklasse DOMBeispiel.java befindet sich in Anhang E.
Derzeit aktuelle Version ist JAXP 1.3, vgl. http://java.sun.com/xml/jaxp
- 17 -
5
XML-Verarbeitung mit Stylesheets
Auch in Java ist es möglich, XML-Dokumente nach den Anweisungen einer XSL-Datei
umzuformen. Hierzu wird ein XSLT-Prozessor benötigt. Ein weit verbreiteter XSLTProzessor ist der kostenlos von der Apache Group erhältliche Xalan-Prozessor.22 Zusätzlich
stellt das Paket javax.xml.transform nützliche Klassen bereit. So befinden sich in diesem
Paket u.a. die Schnittstellen Source und Result, mit deren Hilfe wir die Eingabequellen
und das Ausgabeziel kapseln können. Im Paket javax.xml.transform.stream befinden sich
fertige Implementierungen dieser Interfaces, so dass ohne viel Aufwand die beiden Eingabeobjekte (xml- und xsl-Datei) und das Ausgabeobjekt (z.B. html-Datei) instanziiert werden können. Nun fehlt nur noch die eigentliche Transformation der Daten. Hierfür stellt
das Paket eine Fabrik-Klasse namens TransformerFactory zur Verfügung. Deren Methode
newInstance() instanziiert ein Objekt dieser Klasse, welches den im jeweiligen System zur
Verfügung stehenden XSLT-Prozessor automatisch einbindet. Ist dies erfolgreich geschehen (keine TransformerConfigurationException), kann mit diesem Fabrik-Objekt ein so
genannter Transformer instanziiert werden, welcher als Parameter das Quell-Objekt der
transformierenden Datei erwartet. Nun müssen nur noch die XML-Datei und das gewünschte Ziel an die transform()-Methode des Transformer-Objekts übergeben werden
und die Verarbeitung ist durchgeführt.
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
public class XSLTBeispiel {
public static void main(String args[]) {
//Quelle und Ziel instanziieren
Source xml = new StreamSource("dvdsammlung.xml");
Source xsl = new StreamSource("dvdsammlung.xsl");
Result output = new StreamResult("dvdsammlung.html");
try {
//Transformer erzeugen und benutzen
TransformerFactory factory = TransformerFactory.newInstance();
Transformer t = factory.newTransformer(xsl);
t.transform (xml,output);
}
catch (TransformerConfigurationException e){}
catch (TransformerException e){}
}
}
Darüber hinaus existieren weitere Methoden, um den Transformationsprozess zu beeinflussen. Die Methode TransformerFactory.getAssociatedStylesheet() liest z.B. die Processing Instruction für das xml-stylesheet aus dem xml-Dokument aus und kann so die zugehörige xsl-Datei dynamisch lokalisieren. Über Transformer.setOutputProperty() kann die
Ausgabe durch bestimmte Schalter konfiguriert werden, z.B. bzgl. der Codierung oder
Versionsangabe des Output-Dokuments.
22
Derzeit aktuelle Version ist Xalan-Java version 2.6.0, vgl. http://xml.apache.org/xalan-j
- 18 -
6
XML-Verarbeitung mit JDOM
Zuletzt soll kurz die Möglichkeit der Verarbeitung von XML mit JDOM23 vorgestellt werden. JDOM ist im Gegensatz zu SAX und DOM eine speziell und ausschließlich für Java
entwickelte API für die Verarbeitung von XML-Daten. Daher muss sich JDOM nicht an
plattformübergreifende Standards und Vorschriften halten und ist demnach recht schlank
und schnell. Die Syntax und das Programmierkonzept sind Java-typisch und damit für einen Java-Programmierer sehr intuitiv. Als Beispiel ist hier die Verwendung der Java
Collections-Klassen anstelle von speziellen NodeList-Klassen (wie DOM-Parser sie nutzen) zu nennen.
6.1
Einlesen und Ausgeben der XML-Daten
JDOM bietet keinen eigenen Parser an, sondern ist als Repräsentation eines XMLDokuments in Java aufzufassen. Daher muss JDOM beim Einlesen eines Dokuments auf
SAX oder DOM zurückgreifen24. Das Paket org.jdom.input stellt hierfür die Erbauerklassen SAXBuilder und DOMBuilder zur Verfügung, mit deren Hilfe ein Objekt der Klasse
org.jdom.Document erstellt werden kann. Dabei eignet sich der SAXBuilder wegen seiner
Effizienz vor allem, um Datenströme oder Dateien einzulesen, der DOMBuilder ist für das
Importieren von schon existierenden DOM-Strukturen geeignet. JDOM stellt die XMLDaten ähnlich wie DOM in einer baumartigen Struktur dar, wobei das JDOM-DocumentObjekt den gesamten Baum repräsentiert. Für die Ausgabe der bearbeiteten XML-Daten
sind die Klassen des Pakets org.jdom.output zuständig. Der DOMOutputter liefert ein
DOM-Document-Objekt, der SAXOutputter löst die bekannten SAX-Events aus und der
XMLOutputter schreibt das XML-Dokument in eine Datei oder in einen Output-Stream.
//Builder instanziieren und Dokument aufbauen
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(new FileInputStream("dvdsammlung.xml"));
//Outputter instanziieren und Dokument ausgeben
XMLOutputter outputter = new XMLOutputter();
outputter.output(doc, new FileOutputStream("dvdsammlung_neu.xml"));
6.2
Manipulieren von XML-Dokumenten
JDOM kommt im Gegensatz zu SAX und DOM mit konkreten Klassen daher. Das macht
die Instanziierung von neuen Elementen für ein Dokument sehr einfach. Das folgende Bei-
23
24
Derzeit aktuelle Version ist JDOM 1.0, vgl. http://www.jdom.org
Im JDOM-Paket ist der Xerces-Parser enthalten.
- 19 -
spiel soll verdeutlichen, wie leicht es ist, mit JDOM den XML-Baum zu bearbeiten (in
diesem Fall Einfügen und Löschen von Kindelementen).25
public void fuegeEinUndEntferne(Document doc) {
Element root = doc.getRootElement();
Iterator kinder = (root.getChildren()).iterator();
while (kinder.hasNext()) {
Element current = (Element)kinder.next();
Element neu = new Element("language");
neu.setText("undefined");
current.addContent(neu);
current.removeChild("length");
}
}
7
//Wurzel auslesen
//Wurzelkinder auslesen
//über Kinder iterieren
//aktuelles Kind lesen
//neues Elem. setzen
//Wert setzen
//neues Elem. anhängen
//“length“ entfernen
Fazit und Ausblick
Java bietet also inzwischen eine Vielzahl von Möglichkeiten, XML zu verarbeiten. Diese
Entwicklung ist auch noch längst nicht abgeschlossen, JDOM z.B. gehört derzeit noch
nicht zum Java-Standard.26 Welche der Möglichkeiten nun im konkreten Fall für die XMLVerarbeitung tatsächlich genutzt werden soll, lässt sich pauschal nicht beantworten. Je
nach Zweck, Umfang und Häufigkeit der XML-Verarbeitung, ja nach Größe, Quelle und
Struktur der XML-Datei und nicht zuetzt je nach Vorliebe des Programmierers erscheint
die eine oder die andere Möglichkeit als bessere Wahl.
25
26
Die vollständige JDOM-Beispiel-Klasse findet sich in Anhang F.
Vgl. http://java.sun.com/aboutJava/communityprocess/jsr/jsr_102_jdom.html.
- 20 -
Ergänzende Literatur
Bücher
Ammelburger, Dirk:
XML mit Java in 21 Tagen. Markt+Technik 2002.
Brownel, David:
SAX2. O’Reilly 2002.
Burke, Eric E.:
Java&XSLT. O’Reilly 2001 (auch in deutscher Übersetzung erhältlich).
Hunter, David; Cagle, Kurt; Dix, Chris:
Beginning XML. 3rd Edition, Wrox 2004.
McLaughlin, Brett:
Java&XML. 2nd Edition, O’Reilly 2001.
Internetquellen
XML allgemein:
http://www.w3.org/XML
http://www.xml.org
http://www.w3schools.com/xml
XML und Java:
http://java.sun.com/xml
http://xml.apache.org
http://www.cafeconleche.org/books/xmljava
SAX und DOM:
http://www.saxproject.org
http://www.w3schools.com/dom
http://www.w3.org/DOM
JDOM:
http://www.jdom.org
http://www.cafeconleche.org/slides/xmlsig/jdom
http://www.jdom.net
Online-Java-Dokumentationen
http://java.sun.com/j2se/1.5.0/docs/api
http://xml.apache.org/xerces2-j/javadocs/api
http://xml.apache.org/xalan-j/apidocs
http://www.jdom.org/docs/apidocs
- 21 -
Anhang
A
Beispieldokument „dvdsammlung.xml“
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dvdsammlung SYSTEM "dvdsammlung.dtd">
<dvdsammlung>
<dvd num="1" year="1995">
<title>Bad Boys</title>
<genre>Action</genre>
<director>
<person>
<name xmlns:x="http://jovelstefan.de/ns">Michael</name>
<surname>Bay</surname>
</person>
</director>
<actor gender="m">
<person>
<name>Will</name>
<surname>Smith</surname>
</person>
</actor>
<actor gender="m">
<person>
<name>Martin</name>
<surname>Lawrence</surname>
</person>
</actor>
<length>112</length>
</dvd>
<dvd num="2" year="1988">
<title>Die Hard</title>
<genre>Action</genre>
<director>
<person>
<name>John</name>
<surname>McTiernan</surname>
</person>
</director>
<actor gender="m">
<person>
<name>Bruce</name>
<surname>Willis</surname>
</person>
</actor>
<actor gender="f">
<person>
<name>Bonnie</name>
<surname>Bedelia</surname>
</person>
</actor>
<length>127</length>
</dvd>
<dvd num="3" year="1999">
<title>Cruel Intentions</title>
<genre>Drama</genre>
<director>
<person>
- 22 -
<name>Roger</name>
<surname>Kumble</surname>
</person>
</director>
<actor gender="f">
<person>
<name>Sarah Michelle</name>
<surname>Gellar</surname>
</person>
</actor>
<actor gender="m">
<person>
<name>Ryan</name>
<surname>Phillippe</surname>
</person>
</actor>
<actor gender="f">
<person>
<name>Reese</name>
<surname>Witherspoon</surname>
</person>
</actor>
<length>105</length>
</dvd>
<dvd num="4" year="1995">
<title>Seven</title>
<genre>Thriller</genre>
<director>
<person>
<name>David</name>
<surname>Fincher</surname>
</person>
</director>
<actor gender="m">
<person>
<name>Brad</name>
<surname>Pitt</surname>
</person>
</actor>
<actor gender="m">
<person>
<name>Morgan</name>
<surname>Freeman</surname>
</person>
</actor>
<length>124</length>
</dvd>
</dvdsammlung>
B
Beispieldokument “dvdsammlung.dtd“
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT dvdsammlung (dvd+)>
<!ELEMENT dvd (title, genre, director+, actor+, length)>
<!ATTLIST dvd
num ID #REQUIRED
year CDATA #IMPLIED
>
<!ELEMENT title (#PCDATA)>
<!ELEMENT genre (#PCDATA)>
- 23 -
<!ELEMENT director (person)>
<!ELEMENT actor (person)>
<!ATTLIST actor
gender (m|f) #IMPLIED
>
<!ELEMENT length (#PCDATA)>
<!ELEMENT misc (#PCDATA)>
<!ELEMENT person (name,surname)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT surname (#PCDATA)>
C
Beispieldokument „dvdsammlung.xsl“
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title>dvdsammlung.xsl</title>
</head>
<body>
<center>
<h1>DVD-Sammlung</h1>
<table border="1">
<xsl:for-each select="/dvdsammlung/dvd">
<xsl:apply-templates select="title" />
<xsl:apply-templates select="director" />
<xsl:apply-templates select="length" />
</xsl:for-each>
</table>
</center>
</body>
</html>
</xsl:template>
<xsl:template match="title">
<tr>
<td colspan="2"><b>
<xsl:value-of select="." /></b><br />
<xsl:value-of select="../genre" />
</td>
</tr>
</xsl:template>
<xsl:template match="director">
<tr>
<td>Regie:</td><td>
<xsl:value-of select="person/name" />
<xsl:value-of select="person/surname" />
</td>
</tr>
</xsl:template>
<xsl:template match="length">
<tr>
<td>Länge:</td><td>
<xsl:value-of select="." />
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
- 24 -
D
Beispielklasse „cHandler.java“
import org.xml.sax.*;
public class cHandler implements ContentHandler
{
int elemente = 0;
int dvdanzahl = 0;
boolean elementausgeben = false;
public void startDocument()
{
System.out.println("Das Dokument beginnt...");
}
public void endDocument()
{
System.out.println(dvdanzahl+" DVDs mit "+elemente+"
Einzelelementen gefunden!");
}
public void startElement(String URI, String local,
String name, Attributes atts)
{
elemente++;
if (local=="dvd") dvdanzahl++;
if (local=="title") elementausgeben = true;
}
public void endElement(String URI, String local, String name)
{
elementausgeben = false;
}
public void startPrefixMapping(String prefix, String URI)
{
if (!prefix.equals("")) {System.out.println("Namensraumwechsel:"+prefix);}
}
public void endPrefixMapping(String prefix)
{
}
public void characters(char[] ch, int start, int length)
{
if (elementausgeben==true)
{
String str = new String(ch, start, length);
System.out.println("DVD "+dvdanzahl+":\t"+str);
}
}
public void processingInstruction(String target, String data)
{
System.out.println("Processing Instruction: "+data);
}
public void skippedEntity(String name)
{
}
public void ignorableWhitespace(char[] ch, int start, int length)
{
}
- 25 -
public void setDocumentLocator(Locator locator)
{
}
}
E
Beispielklasse „DOMBeispiel.java“
import
import
import
import
import
java.io.*;
org.w3c.dom.*;
org.xml.sax.SAXException;
com.sun.org.apache.xerces.internal.parsers.DOMParser;
com.sun.org.apache.xml.internal.serialize.XMLSerializer;
public class DOMBeispiel
{
public static void main (String args[])
{
DOMBeispiel beispiel = new DOMBeispiel();
beispiel.parseme(args[0]);
}
private void parseme(String uri)
{
try
{
DOMParser parser = new DOMParser();
parser.setFeature("http://apache.org/xml/features/dom/
include-ignorable-whitespace",false);
parser.parse(uri);
Document d = parser.getDocument();
NodeList nl = d.getElementsByTagName("dvd");
for (int i=0; i < nl.getLength(); i++)
{
Element e = (Element)nl.item(i);
Element neuE = d.createElement("misc");
Attr neuA = d.createAttribute("mark");
Text neuT = d.createTextNode("noch nichts");
neuE.setAttributeNode(neuA);
Node neuN = e.appendChild(neuE);
neuN.appendChild(neuT);
e.removeChild(e.getElementsByTagName("length").item(0));
d.renameNode(e.getElementsByTagName("name").item(0),"","first_name");
}
Element wurzel = d.getDocumentElement();
this.gibBaum(wurzel);
FileWriter fw = new FileWriter("dvdsammlung_neu.xml", false);
XMLSerializer ausgabe = new XMLSerializer (fw, null);
ausgabe.serialize(d);
}
catch (SAXException e){System.out.println("Fehler!");}
catch (IOException e){System.out.println("Fehler!");}
}
private void gibBaum(Node n)
{
switch(n.getNodeType())
{
case Node.ELEMENT_NODE:
String name = n.getNodeName();
NodeList kinder = n.getChildNodes();
System.out.println("Element "+name+":");
for (int i=0; i < kinder.getLength(); i++)
- 26 -
{
this.gibBaum(kinder.item(i));
}
break;
case Node.TEXT_NODE:
System.out.println("Text: "+n.getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
System.out.println("CData: "+n.getNodeValue());
break;
case Node.ATTRIBUTE_NODE:
Attr a = (Attr)n;
String attr_str = "Attribut "+a.getNodeName()+":"+a.getNodeValue();
System.out.println(attr_str);
break;
case Node.COMMENT_NODE:
System.out.println("Kommentar: "+n.getNodeValue());
break;
case Node.PROCESSING_INSTRUCTION_NODE:
System.out.println("PI: "+n.getNodeValue());
break;
}
}
}
F
import
import
import
import
import
Beispielklasse „JDOMBeispiel.java“
org.jdom.*;
org.jdom.input.*;
org.jdom.output.*;
java.io.*;
java.util.*;
public class JDOMBeispiel
{
public static void main(String args[])
{
try
{
JDOMBeispiel beispiel = new JDOMBeispiel();
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(new FileInputStream("dvdsammlung.xml"));
beispiel.fuegeEinUndEntferne(doc);
XMLOutputter outputter = new XMLOutputter();
outputter.output(doc, new FileOutputStream("dvdsammlung_neu.xml"));
}
catch (IOException e){}
catch (JDOMException e){}
}
public void fuegeEinUndEntferne(Document doc)
{
Element root = doc.getRootElement();
Iterator kinder = (root.getChildren()).iterator();
while (kinder.hasNext())
{
Element current = (Element)kinder.next();
Element neu = new Element("language");
neu.setText("undefined");
current.addContent(neu);
current.removeChild("length");
}
}
}
- 27 -
Abschließende Erklärung
Ich versichere hiermit, dass ich meine Ausarbeitung „XML-Verarbeitung mit Java“ selbstständig und ohne fremde Hilfe angefertigt habe, und dass ich alle von anderen Autoren
wörtlich übernommenen Stellen wie auch die sich an die Gedankengänge anderer Autoren
eng anlehnenden Ausführungen meiner Arbeit besonders gekennzeichnet und die Quellen
zitiert habe.
Münster, den 20. Dezember 2004
Stefan Heß
Herunterladen