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ß