Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 43 - XML-Verarbeitung |
Für die Verarbeitung von XML-Dokumenten mit Java gibt es diverse Standards, von denen JAXP (Java API for XML Processing) sicherlich eine zentrale Rolle spielt. JAXP definiert drei verschiedene Programmierschnittstellen, um XML-Dokumente mit Java zu interpretieren:
Alle drei Standards werden wir im Folgenden kennenlernen. Des Weiteren werden wir einen Blick auf JAXB (Java Architecture for XML Binding) werfen, einen weiteren Standard für die Verarbeitung von XML-Dokumenten in Java.
Xerces von Apache ist eine bekannte Implementation aller drei JAXP-APIs und fester Bestandteil des Standard-JDK; eine Implementation von JAXB liegt dem JDK seit Version 6 bei. Für die Beispiele in diesem Kapitel benötigen wir also keine Fremdbibliotheken, sondern wir stützen uns auf Features des aktuellen Standard-JDK.
In den folgenden Abschnitten werden die verschiedenen APIs an Hand eines einheitlichen Beispiels näher vorgestellt. Dazu soll mit Hilfe eines XML-Dokuments das Aussehen eines Swing-Fensters konfiguriert werden, in dem Titel, Schriftart, Hintergrund- und Vordergrundfarbe des Fensters mit XML-Mitteln eingestellt werden. Beim Öffnen des Fensters soll die XML-Datei gelesen und die darin enthaltenen Einstellungen auf das Fenster angewendet werden. Diese XML-Datei sieht wie folgt aus:
001 <?xml version="1.0" encoding="iso-8859-15"?> 002 <!DOCTYPE frame SYSTEM "config_jaxp.dtd"> 003 <frame> 004 <title>XML-Beispiel</title> 005 <font size="11">Arial</font> 006 <fgcolor>0</fgcolor> 007 <bgcolor>16777215</bgcolor> 008 </frame> |
config_jaxp.xml |
Als Schriftart soll Arial 11 Punkt verwendet werden, die Vordergrundfarbe ist Schwarz, die Hintergrundfarbe Weiß und der Fenstertitel lautet "XML-Beispiel". Der Farbwert 16777215 steht für 224-1, repräsentiert also den hexadezimalen Wert FFFFFF, der - als drei Bytes gelesen - die Farbkanäle Rot, Grün und Blau auf ihre jeweils maximale Helligkeit einstellt. Natürlich können Sie die Werte beliebig ändern und sich (bei jedem erneuten Start des Fensters) ansehen, wie die Anpassungen sich auf das Aussehen des Fensters auswirken.
Den Font werden wir mit der Methode getFont aus dem Schriftarten-String innerhalb des XML-Dokuments generieren, die Color-Objekte mit der Methode decode:
public static Font getFont(String nm) |
java.awt.Font |
public static Color decode(String nm) |
java.awt.Color |
Die passende DTD sieht so aus:
001 <?xml version="1.0" encoding="iso-8859-15"?> 002 <!ELEMENT frame (title, font, fgcolor, bgcolor)> 003 004 <!-- Fenstertitel --> 005 <!ELEMENT title (#PCDATA)> 006 007 <!-- Schriftart --> 008 <!ELEMENT font (#PCDATA) > 009 <!ATTLIST font size CDATA "12"> 010 011 <!-- Vordergrundfarbe --> 012 <!ELEMENT fgcolor (#PCDATA)> 013 014 <!-- Hintergrundfarbe --> 015 <!ELEMENT bgcolor (#PCDATA)> |
config_jaxp.dtd |
Wir werden sie für alle drei JAXP-APIs anwenden. Für die Implementation nach dem JAXB-Standard werden wir später eine eigene XSD entwickeln.
Das folgende Listing zeigt den Rumpf unseres Fensterprogramms. Die zunächst leere Methode readConfig werden wir in den folgenden Abschnitten ausprogrammieren, indem wir den jeweils passenden Code aus den verschiedenen XML-APIs einfügen.
001 /* Listing4319.java */ 002 003 import java.awt.*; 004 import javax.swing.*; 005 006 public class Listing4319 extends JFrame 007 { 008 private String title; 009 private Color fgcolor; 010 private Color bgcolor; 011 private Font font; 012 013 public Listing4319() 014 { 015 readConfig(); 016 017 setTitle(title); 018 019 addWindowListener(new WindowClosingAdapter()); 020 JLabel label = new JLabel("Hello, World", JLabel.CENTER); 021 label.setPreferredSize(new Dimension(200, 200)); 022 023 Container pane = getContentPane(); 024 pane.setLayout(new FlowLayout()); 025 pane.add(label); 026 027 pane.setBackground(bgcolor); 028 pane.setForeground(fgcolor); 029 label.setFont(font); 030 031 pack(); 032 } 033 034 private void readConfig() 035 { 036 // ... zunächst leer 037 } 038 039 public static void main(String[] args) 040 { 041 Listing4319 window = new Listing4319(); 042 window.setVisible(true); 043 } 044 } |
Listing4319.java |
Das Beispielprogramm erzeugt ein Fenster mit dem gewünschten Titel, der definierten Hinter- und Vordergrundfarbe und dem angegebenen Font. Das folgende Bild zeigt das Fenster, wenn es mit dem Original-XML-Dokument konfiguriert wurde: weißer Hintergrund, schwarze Vordergrundfarbe, Schriftart Arial 11 Punkt und die entsprechende Titelzeile.
Abbildung 43.1: Mittels XML konfiguriertes Fenster
In diesem Beispiel (wie auch in vielen anderen in diesem Buch) wird der Einfachheit halber die in Abschnitt 24.2.4 vorgestellte Klasse WindowClosingAdapter verwendet, um einen Listener zum Schließen des Fensters zu registrieren. Damit sich ein solches Beispiel kompilieren lässt, muss die Datei WindowClosingAdapter.java im aktuellen Verzeichnis vorhanden sein. Sie befindet sich auf der DVD zum Buch oder in Listing 24.2. |
|
Als Erstes wollen wir die XML-Konfigurationsdatei mit der DOM-Schnittstelle verarbeiten. DOM ist ein programmiersprachenunabhängiger Standard, dessen zentraler Gedanke es ist, das XML-Dokument in den Hauptspeicher zu laden und dort in Form eines Objektbaums zur Verfügung zu stellen. Ein solcher DOM-Baum lässt sich mit wenigen Zeilen Java-Code aus der XML-Datei erstellen und kann mit Hilfe geeigneter Java-Methoden weiter verarbeitet werden. Da das komplette Dokument im Hauptspeicher liegt, ist DOM für größere XML-Dateien eher weniger gut geeignet.
Die Ursprünge von DOM reichen bis zu den ersten Gehversuchen des dynamischen Webs zurück. Mit Hilfe von JavaScript kann man nämlich schon seit vielen Jahren Webseiten verändern, indem man auf den DOM-Baum des HTML-Dokuments zugreift und diesen zur Laufzeit verändert. DOM ist mittlerweile ein W3C-Standard und eine Java-Implementation des Standards ist seit der Version 1.4 fester Bestandteil des JDK.
Basis aller XML-APIs ist der XML-Parser, der den Eingabestrom in seine grammatikalischen Bestandteile zerlegt. Er liegt im Package javax.xml.parsers. Uns interessieren darin die Klassen DocumentBuilder zum Erzeugen des DOM-Baums und DocumentBuilderFactory, um den DocumentBuilder zu erzeugen (vgl. das Factory-Design-Pattern, Abschnitt 11.4.4). Eine DocumentBuilderFactory erzeut man mit Hilfe ihrer statischen Methode newInstance:
public static DocumentBuilderFactory newInstance() public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException public void setValidating(boolean validating) |
javax.xml.parsers.DocumentBuilderFactory |
Der DOM-XML-Parser stellt verschiedene Methoden zur Verfügung, um das XML-Dokument einzulesen und in einen DOM-Baum zu verwandeln:
public Document parse(File f) throws SAXException, IOException public Document parse(InputSource is) throws SAXException, IOException public Document parse(InputStream is) throws SAXException, IOException public Document parse(String uri) throws SAXException, IOException public void setEntityResolver(EntityResolver e) |
javax.xml.parsers.DocumentBuilder |
Die Interfaces des Objektbaums liegen im Package org.w3c.dom, unter anderem liegt dort das Interface Document, das den eingelesenen Baum repräsentiert. Es folgt die erste Version unserer readConfig-Methode zur Vervollständigung von Listing 43.19 nach DOM-Standard:
001 private void readConfig() 002 { 003 try { 004 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 005 factory.setValidating(true); 006 DocumentBuilder builder = factory.newDocumentBuilder(); 007 builder.setEntityResolver(new EntityResolver() { 008 public InputSource resolveEntity(String publicId, String systemId) 009 throws SAXException, IOException { 010 if (systemId.endsWith("config_jaxp.dtd")) 011 return new InputSource( 012 getClass().getResourceAsStream("config_jaxp.dtd")); 013 return null; 014 } 015 }); 016 Document document = builder.parse(getClass().getResourceAsStream( 017 "config_jaxp.xml")); 018 019 // fortsetzen: Document verarbeiten 020 } 021 catch (ParserConfigurationException x) { 022 x.printStackTrace(); 023 } 024 catch (SAXException x) { 025 x.printStackTrace(); 026 } 027 catch (IOException x) { 028 x.printStackTrace(); 029 } 030 } |
Zunächst erzeugen wir eine neue DocumentBuilderFactory
und konfigurieren diese so, dass der entstehende DocumentBuilder
validierend ist, dass er also beim Einlesen der XML-Datei prüft,
ob diese den Vorgaben ihrer DTD entspricht. Es genügt dabei nicht,
setValidating(true) aufzurufen,
sondern zusätzlich muss ein ErrorHandler
gesetzt werden, der entscheidet, was mit auftretenden Exceptions passieren
soll. Andernfalls würde man auf der Konsole folgende Meldung
erhalten (und die Fehler selbst würden stillschweigend ignoriert):
Warning: validation was turned on but an org.xml.sax.ErrorHandler was not
set, which is probably not what is desired. [...]
Das Interface besitzt drei Methoden:
public void warning(SAXParseException exception) throws SAXException public void error(SAXParseException exception) throws SAXException public void fatalError(SAXParseException exception) throws SAXException |
org.xml.sax.ErrorHandler |
Im einfachsten Fall implementiert man den ErrorHandler so, dass man die Parameter-Exception wirft. In Listing 43.21 werden wir diesen Weg wählen.
Das Validieren gegen die Grammatik, in unserem Fall gegen die DTD, kann das Einlesen des Dokuments erheblich verlangsamen. In der Praxis wird man daher mitunter abwägen müssen, ob die an und für sich recht nützliche Prüfung aus Performance-Gründen vielleicht eher abgeschaltet werden sollte. |
|
Vor dem Aufruf der parse-Methode, mit der wir die XML-Datei einlesen, setzen wir noch einen EntityResolver. Er hat die Aufgabe, die DTD von der richtigen Stelle im Dateisystem zu lesen. Der Verweis auf die DTD in unserer XML-Datei ist relativ, sie wird also in dem Verzeichnis gesucht, aus dem das Java-Programm gestartet wird.
Am DocumentBuilder rufen wir anschließend die parse-Methode auf, um die Datei config_jaxp.xml einzulesen. Das Ergebnis des Aufrufs ist ein Objekt vom Typ Document, das den DOM-Baum für das eingelesene XML-Dokument repräsentiert.
Das Document-Interface ist vom Interface org.w3c.dom.Node abgeleitet, das auch alle anderen Knoten des DOM-Baums implementieren. Am Document-Interface interessiert uns zunächst nur die Methode, mit der wir Zugriff auf das Wurzelelement haben:
public Element getDocumentElement() |
org.w3c.dom.Document |
Wenn wir uns das XML-Dokument aus Listing 43.17 als Baum vorstellen, enthält das frame-Element (also die Wurzel) selbst keine verwertbaren Informationen. Wichtiger sind für uns die Kindknoten title, font, fgcolor und bgcolor. Der Zugriff darauf erfolgt mit Hilfe der Interfaces Node und Element.
public String getNodeName() public String getTextContent() |
org.w3c.dom.Node |
public String getAttribute(String name) public NodeList getElementsByTagName(String name) |
org.w3c.dom.Element |
Mit getTextContent beschaffen wir uns den Textinhalt der Elemente. Für die Fontgröße benötigen wir Zugriff auf den Wert des Attributs size, den wir mit der Methode getAttribute erhalten. Außerdem verwenden wir die Methode getElementsByTagName zum Zugriff auf die Kindknoten.
Nun haben wir alles zusammen, was wir benötigen, um die readConfig-Methode in der DOM-Variante auszuprogrammieren:
001 /* Listing4321.java */ 002 003 import java.awt.*; 004 import java.io.IOException; 005 006 import javax.swing.*; 007 import javax.xml.parsers.*; 008 009 import org.w3c.dom.*; 010 import org.xml.sax.*; 011 012 public class Listing4321 extends JFrame 013 { 014 private String title; 015 private Color fgcolor; 016 private Color bgcolor; 017 private Font font; 018 019 public Listing4321() 020 { 021 readConfig(); 022 023 setTitle(title); 024 025 addWindowListener(new WindowClosingAdapter()); 026 JLabel label = new JLabel("Hello, World", JLabel.CENTER); 027 label.setPreferredSize(new Dimension(200, 200)); 028 029 Container pane = getContentPane(); 030 pane.setLayout(new FlowLayout()); 031 pane.add(label); 032 033 pane.setBackground(bgcolor); 034 pane.setForeground(fgcolor); 035 label.setFont(font); 036 037 pack(); 038 } 039 040 private void readConfig() 041 { 042 try { 043 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 044 factory.setValidating(true); 045 DocumentBuilder builder = factory.newDocumentBuilder(); 046 builder.setEntityResolver(new EntityResolver() 047 { 048 public InputSource resolveEntity(String publicId, String systemId) 049 throws SAXException, IOException 050 { 051 if (systemId.endsWith("config_jaxp.dtd")) 052 return new InputSource( 053 getClass().getResourceAsStream("config_jaxp.dtd")); 054 return null; 055 } 056 }); 057 builder.setErrorHandler(new ErrorHandler() 058 { 059 public void warning(SAXParseException exception) throws SAXException 060 { 061 } 062 public void fatalError(SAXParseException exception) throws SAXException 063 { 064 throw exception; 065 } 066 public void error(SAXParseException exception) throws SAXException 067 { 068 throw exception; 069 } 070 }); 071 Document document = builder.parse(getClass().getResourceAsStream( 072 "config_jaxp.xml")); 073 074 Element frameElement = document.getDocumentElement(); 075 Element titleElement = (Element) frameElement.getElementsByTagName( 076 "title").item(0); 077 title = titleElement.getTextContent().trim(); 078 079 Element fontElement = (Element) frameElement.getElementsByTagName("font") 080 .item(0); 081 font = new Font(fontElement.getTextContent().trim(), Font.PLAIN, 082 Integer.parseInt(fontElement.getAttribute("size").trim())); 083 084 Element fgcolorElement = (Element) frameElement.getElementsByTagName( 085 "fgcolor").item(0); 086 fgcolor = Color.decode(fgcolorElement.getTextContent().trim()); 087 088 Element bgcolorElement = (Element) frameElement.getElementsByTagName( 089 "bgcolor").item(0); 090 bgcolor = Color.decode(bgcolorElement.getTextContent().trim()); 091 } 092 catch (ParserConfigurationException x) { 093 x.printStackTrace(); 094 } 095 catch (SAXException x) { 096 x.printStackTrace(); 097 } 098 catch (IOException x) { 099 x.printStackTrace(); 100 } 101 } 102 103 public static void main(String[] args) 104 { 105 Listing4321 window = new Listing4321(); 106 window.setVisible(true); 107 } 108 } |
Listing4321.java |
Auf dem Rückgabewert von getTextContent rufen wir die Methode trim auf, um Leerzeichen und Zeilenumbrüche zu entfernen. getElementsByTagName liefert alle Elemente im Baum mit dem angegebenen Namen, also unter Umständen mehr als eins. In unserem Beispiel ist das jedoch unerheblich, weil jedes Tag einen eindeutigen Namen besitzt. |
|
Um das Programm auszuführen, legen wir die config_jaxp.xml und die config_jaxp.dtd (Listing 43.18) im gleichen Verzeichnis ab wie das Java-Programm. Wenn wir nun das Programm ausführen, erscheint ein Swing-Fenster mit dem Titel, der Hintergrund- und Vordergrundfarbe sowie dem Font aus der config_jaxp.xml, genau wie in Abbildung 43.1 zu sehen.
Abschließend sei anzumerken, dass sich DOM auch für die »Gegenrichtung« verwenden lässt, also zum Erzeugen von XML-Dokumenten aus dem Objektmodell. DOM-Bäume lassen sich zudem recht komfortabel mit sog. XPATH-Ausdrücken durchsuchen, deren Erläuterung hier jedoch den Rahmen sprengen würde. Außerdem ist zu erwähnen, dass es mit JDOM (http://www.jdom.org) von Jason Hunter und anderen eine Alternative zu DOM gibt, die im Gegensatz zu DOM jedoch kein offizieller Standard und auch nicht Bestandteil des JDK ist. |
|
SAX steht für Simple API for XML und stellt wie DOM eine Programmierschnittstelle zum Einlesen von XML-Dokumenten dar. SAX ist zwar kein offizieller Standard einer unabhängigen Organisation, dafür ist es programmiersprachenunabhängig und besitzt Implementationen für diverse Programmiersprachen. Seit der Version 1.4 ist es fester Bestandteil des JDK. Im Gegensatz zu DOM und JDOM erstellt der Parser hier keinen Objektbaum, sondern die Verarbeitung der XML-Datei erfolgt synchron mit ihrem Einlesen. Dazu registriert sich das verarbeitende Programm direkt am Parser und empfängt während des Einlesens der Elemente, Attribute usw. fortwährend Ereignisse, die es dann nach Bedarf verarbeitet.
Abgesehen vom Parser und seiner Factory liegen die Klassen und Interfaces dieses API im Package org.xml.sax. Wie bei DOM lässt sich der SAXParser nicht per Konstruktor erzeugen, sondern man benötigt dazu die SAXParserFactory. Auch die Fabrik erzeugt man nicht über einen Konstruktor, sondern mit Hilfe einer statischen Methode:
public static SAXParserFactory newInstance() public SAXParser newSAXParser() throws ParserConfigurationException, SAXException public void setValidating(boolean validating) |
javax.xml.parsers.SAXParserFactory |
Der Parser besitzt eine Reihe von Methoden, um ein XML-Dokument einzulesen:
public void parse(File f, DefaultHandler dh) throws SAXException, IOException public void parse(InputSource is, DefaultHandler dh) throws SAXException, IOException public void parse(InputStream is, DefaultHandler dh) throws SAXException, IOException public void parse(String uri, DefaultHandler dh) throws SAXException, IOException |
javax.xml.parsers.SAXParser |
Auffälligster Unterschied zum DOM-API ist, dass die parse-Methoden des SAX-Parser einen DefaultHandler benötigen, der eine Reihe von Callback-Methoden besitzt, die vom SAX-Parser aufgerufen werden. Der DefaultHandler ist eine Klasse mit einer Reihe von leeren Methoden, die einige Interfaces implementieren.
public void startDocument() throws SAXException public void endDocument() throws SAXException public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException public void endElement(String uri, String localName, String qName) throws SAXException public void characters(char ch[], int start, int length) throws SAXException public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException |
org.xml.sax.helpers.DefaultHandler |
Um mit der SAX-API ein XML-Dokument zu verarbeiten, müssen wir eine eigene Klasse aus DefaultHandler ableiten und die nötigen Methoden nach unseren Bedürfnissen überschreiben. Wie die Namen der Methoden vermuten lassen, wird startElement bei jedem öffnenden, endElement bei jedem schließenden Tag gerufen usw. Die folgende Skizze zeigt exemplarisch, in welcher Reihenfolge die Callback-Methoden aufgerufen werden:
startDocument startElement <frame> startElement <title> characters XML-Beispiel endElement </title> ... endElement </frame> endDocument |
Da immer nur ein kleiner Ausschnitt der einzulesenden Teile im Hauptspeicher gehalten wird, verbraucht der SAX-Parser weniger Ressourcen als sein DOM-Pendant und kann daher auch für sehr große XML-Dokumente verwendet werden. Nachteilig ist dabei, dass die SAX-Implementation tendenziell umso schwieriger wird, je mehr Informationen über das Umfeld eines Elements benötigt werden. Denn mit dem SAX-Parser gibt es keine Möglichkeit, wahlfrei im XML-Dokument (oder -Baum) zu navigieren: Das Dokument wird ein einziges Mal von vorne bis hinten durchgelesen! Je größer der benötigte Kontext ist, desto eher sollte man daher erwägen, das XML-Dokument mit einer der APIs zu verarbeiten, die eine freie Navigation ermöglichen, also zum Beispiel mit DOM oder JAXB.
Für unsere Beispielanwendung sind die Methoden startElement und characters zu überschreiben. Damit wir den Text des Elements, den wir in der Methode characters empfangen, sinnvoll interpretieren können, müssen wir uns in startElement den Namen des aktuellen Elements merken. Außerdem benötigen wir eine weitere Instanzvariable für den Zusammenbau des Font-Objekts. Die Fontsize erhalten wir nämlich über die Attributliste in startElement, den Fontnamen dagegen erst in der etwas später aufgerufenen characters-Methode; und erst dann können wir das Fontobjekt zusammenbauen.
Die Fontsize erhalten wir aus dem Attributwert, der in SAX durch das Interface Attributes repräsentiert wird. Wir erhalten in der startElement-Methode der Klasse DefaultHandler Zugriff auf Attributes-Objekte:
public String getValue(String qName) |
org.xml.sax.Attributes |
Analog zur DOM-Implementation müssen wir die Methode resolveEntity überschreiben, um die DTD zum XML-Dokument von der passenden Stelle im Dateisystem einzulesen. Damit haben wir fast alles zusammen, um die readConfig-Methode (Listing 43.19) mit Hilfe der SAX-API zu implementieren. Um das Beispiel übersichtlich zu halten, werden wir eine anonyme Klasse erzeugen, die sich vom DefaultHandler ableitet.
Für das Validieren des XML-Dokuments gegen die DTD reicht das Einschalten mit setValidating in diesem Fall nicht. Der DefaultHandler enthält nämlich nur leere Methoden, was insbesondere auch für die Methoden zur Fehlerbehandlung gilt. Standardmäßig passiert also im Fehlerfall gar nichts und es wird insbesondere keine Exception geworfen, von der unser Programmcode etwas mitbekommen würde. Um dies zu ändern, müssen wir auch die error-Methode des DefaultHandler überschreiben und die empfangene Exception weiterverarbeiten. |
|
001 /* Listing4322.java */ 002 003 import java.awt.*; 004 import java.io.IOException; 005 006 import javax.swing.*; 007 import javax.xml.parsers.*; 008 009 import org.xml.sax.*; 010 import org.xml.sax.helpers.DefaultHandler; 011 012 public class Listing4322 extends JFrame 013 { 014 private String title; 015 private Color fgcolor; 016 private Color bgcolor; 017 private Font font; 018 019 public Listing4322() 020 { 021 readConfig(); 022 023 setTitle(title); 024 025 addWindowListener(new WindowClosingAdapter()); 026 JLabel label = new JLabel("Hello, World", JLabel.CENTER); 027 label.setPreferredSize(new Dimension(200, 200)); 028 029 Container pane = getContentPane(); 030 pane.setLayout(new FlowLayout()); 031 pane.add(label); 032 033 pane.setBackground(bgcolor); 034 pane.setForeground(fgcolor); 035 label.setFont(font); 036 037 pack(); 038 } 039 040 private void readConfig() 041 { 042 try { 043 SAXParserFactory factory = SAXParserFactory.newInstance(); 044 factory.setValidating(true); 045 SAXParser parser = factory.newSAXParser(); 046 047 parser.parse(getClass().getResourceAsStream("config_jaxp.xml"), 048 new DefaultHandler() 049 { 050 private String element; 051 052 private int fontsize; 053 054 public void startElement(String uri, String localName, 055 String qName, Attributes attributes) throws SAXException 056 { 057 element = qName; 058 if (element.equals("font")) { 059 fontsize = Integer.parseInt(attributes.getValue("size").trim()); 060 } 061 } 062 063 public void characters(char[] ch, int start, int length) 064 throws SAXException 065 { 066 String text = String.valueOf(ch, start, length).trim(); 067 if (element.equals("title")) { 068 title = text; 069 } 070 else if (element.equals("font")) { 071 font = new Font(text, Font.PLAIN, fontsize); 072 } 073 else if (element.equals("fgcolor")) { 074 fgcolor = Color.decode(text); 075 } 076 else if (element.equals("bgcolor")) { 077 bgcolor = Color.decode(text); 078 } 079 } 080 081 public InputSource resolveEntity(String publicId, String systemId) 082 throws IOException, SAXException 083 { 084 if (systemId.endsWith("config_jaxp.dtd")) 085 return new InputSource(getClass().getResourceAsStream( 086 "config_jaxp.dtd")); 087 return null; 088 } 089 090 public void error(SAXParseException e) throws SAXException 091 { 092 throw e; 093 } 094 }); 095 } 096 catch (ParserConfigurationException x) { 097 x.printStackTrace(); 098 } 099 catch (SAXException x) { 100 x.printStackTrace(); 101 } 102 catch (IOException x) { 103 x.printStackTrace(); 104 } 105 } 106 107 public static void main(String[] args) 108 { 109 Listing4322 window = new Listing4322(); 110 window.setVisible(true); 111 } 112 } |
Listing4322.java |
Wenn Sie die config_jaxp.xml (Listing 43.17) und die config_jaxp.dtd (Listing 43.18) im gleichen Verzeichnis ablegen wie das Programm selbst, sehen Sie nun auch bei der SAX-Implementierung das oben beschriebene und per XML-Datei konfigurierte Fenster, nachdem Sie das Programm starten.
StAX basiert wie SAX auf dem Konzept des ereignisorientierten XML-Parsens. SAX wird als Push-Parsing bezeichnet, weil der Parser die führende Rolle übernimmt und den passiven DefaultHandler mit Ereignissen versorgt. StAX dagegen wird als Pull-Parsing bezeichnet, denn das verarbeitende Programm hat die führende Rolle und ruft wie bei einem Iterator nach und nach die Elemente aus dem an sich passiven Parser ab.
StAX ist aus dem XMLPULL-Projekt von BEA und Sun hervorgegangen. Seine Implementierung wurde im JSR 173 spezifiziert und ist seit Version 1.6 fester Bestandteil des JDK. Der StAX-Parser ist im Package javax.xml.stream und seinen Unterpackages zu finden. Genau genommen stellt StAX sogar zwei APIs zur Verfügung: die Cursor API und die Iterator API. Wir wollen uns im Folgenden die Cursor API ansehen.
Ausgangspunkt der Cursor API ist wie bei DOM eine Fabrik, die XMLInputFactory, die uns mit einen XMLStreamReader einen XML-Parser zur Verfügung stellt:
public static XMLInputFactory newInstance() throws FactoryConfigurationError public XMLStreamReader createXMLStreamReader(Reader reader) throws XMLStreamException public XMLStreamReader createXMLStreamReader(Source source) throws XMLStreamException public XMLStreamReader createXMLStreamReader(InputStream stream) throws XMLStreamException public void setProperty(String name, Object value) throws java.lang.IllegalArgumentException |
javax.xml.stream.XMLInputFactory |
Es gibt eine Reihe weiterer createXMLStreamReader-Methoden, die hier nicht weiter erläutert werden sollen. Die Factory besitzt keine setValidating-Methode wie ihr SAX- oder DOM-Pendant, sondern die Validierung schaltet man mit Hilfe der allgemeinen setProperty-Methode ein.
Die standardmäßige JDK-Implementation der StAX-API ist nicht in der Lage, gegen eine DTD zu validieren, und weist daher den Aufruf von setProperty(XMLInputFactory.IS_VALIDATING, Boolean.TRUE) mit einer Exception zurück. Tatsächlich ignoriert sie den externen Verweis auf die DTD vollständig und wir brauchen für unsere StAX-Implementation daher noch nicht einmal einen XMLResolver zu implementieren (entsprechend dem EntityResolver in SAX und DOM). |
|
Der über die Factory erzeugte XMLStreamReader besitzt neben anderen eine hasNext- und next-Methoden und wird wie ein Iterator verwendet:
public int next() throws XMLStreamException public boolean hasNext() throws XMLStreamException public void close() throws XMLStreamException public String getElementText() throws XMLStreamException public String getAttributeValue(int idx) public String getLocalName() |
javax.xml.stream.XMLStreamReader |
hasNext liefert erwartungsgemäß true zurück, solange weitere Tokens im Eingabestrom zur Verabeitung anstehen. Die next-Methode liefert das nächste Eingabesymbol in Form einer Konstante aus dem Interface XMLStreamConstants zurück:
public static final int START_DOCUMENT public static final int END_DOCUMENT public static final int START_ELEMENT public static final int END_ELEMENT public static final int CHARACTERS public static final int COMMENT public static final int ATTRIBUTE |
javax.xml.stream.XMLStreamConstants |
Das folgende Schema zeigt die unterschiedlichen Zustände des Parsers während des Einlesens unserer config_jaxp.xml-Datei:
START_DOCUMENT START_ELEMENT <frame> CHARACTERS START_ELEMENT <title> CHARACTERS XML-Beispiel END_ELEMENT </title> ... END_ELEMENT </frame> END_DOCUMENT |
Der Parser beginnt immer mit dem Symbol START_DOCUMENT und liefert bei jedem öffnenden Tag ein START_ELEMENT. Zwischen dem öffnenden frame und dem öffnenden title-Tag liegen Leerzeichen und ein Zeilenumbruch. Daher liefert der Parser anschließend ein CHARACTERS (und nicht START_ELEMENT, wie man analog zu SAX vermuten könnte). Dies geht so weiter, bis das komplette Dokument verarbeitet ist und als letztes Symbol ein END_DOCUMENT geliefert wird.
Mit Hilfe der get-Methoden lassen sich nähere Informationen beschaffen, z.B. mit getLocalName der Name des aktuellen Elements. Die get-Methoden des XMLStreamReader-Interface lassen sich jedoch nur passend zum aktuellen Eingabesymbol aufrufen, andernfalls wird der Aufruf mit einer IllegalStateException quittiert. Man kann also beispielsweise die Methode getAttributeValue nicht im Status END_ELEMENT aufrufen.
Eine Ausnahme bildet die Methode getElementText. Zum einen wirft sie eine XMLStreamException statt einer IllegalStateException, wenn sie im falschen Status aufgerufen wird. Zum anderen ist sie im Vergleich zu den restlichen get-Methoden nicht frei von Seiteneffekten. Nach dem Aufruf von getElementText im Status START_ELEMENT ist es beispielsweise nicht mehr möglich, die Attributwerte des Elements mit Hilfe von getAttributeValue abzuholen. Der Parser musste nämlich bereits das nächste Token aus dem Stream lesen (den Text zwischen dem öffnenden und dem schließenden Tag), so dass sein Status folglich bereits auf END_ELEMENT gewechselt hat. |
|
Nach diesen Erläuterungen können wir nun die readConfig-Methode in der StAX-Variante implementieren:
001 /* Listing4323.java */ 002 003 import java.awt.*; 004 005 import javax.swing.*; 006 import javax.xml.stream.*; 007 008 public class Listing4323 extends JFrame 009 { 010 private String title; 011 private Color fgcolor; 012 private Color bgcolor; 013 private Font font; 014 015 public Listing4323() 016 { 017 readConfig(); 018 019 setTitle(title); 020 021 addWindowListener(new WindowClosingAdapter()); 022 JLabel label = new JLabel("Hello, World", JLabel.CENTER); 023 label.setPreferredSize(new Dimension(200, 200)); 024 025 Container pane = getContentPane(); 026 pane.setLayout(new FlowLayout()); 027 pane.add(label); 028 029 pane.setBackground(bgcolor); 030 pane.setForeground(fgcolor); 031 label.setFont(font); 032 033 pack(); 034 } 035 036 private void readConfig() 037 { 038 try { 039 XMLInputFactory factory = XMLInputFactory.newInstance(); 040 XMLStreamReader parser = factory.createXMLStreamReader(getClass() 041 .getResourceAsStream("config_jaxp.xml")); 042 043 while (parser.hasNext()) { 044 if (parser.next() == XMLStreamConstants.START_ELEMENT) { 045 if (parser.getLocalName().equals("titelzeile")) { 046 this.title = parser.getElementText().trim(); 047 } 048 if (parser.getLocalName().equals("font")) { 049 int size = Integer.parseInt(parser.getAttributeValue(0).trim()); 050 font = new Font(parser.getElementText().trim(), Font.PLAIN, size); 051 } 052 if (parser.getLocalName().equals("fgcolor")) { 053 fgcolor = Color.decode(parser.getElementText().trim()); 054 } 055 if (parser.getLocalName().equals("bgcolor")) { 056 bgcolor = Color.decode(parser.getElementText().trim()); 057 } 058 } 059 } 060 parser.close(); 061 } 062 catch (XMLStreamException x) { 063 x.printStackTrace(); 064 } 065 } 066 067 public static void main(String[] args) 068 { 069 Listing4323 window = new Listing4323(); 070 window.setVisible(true); 071 } 072 } |
Listing4323.java |
Unter der Voraussetzung, dass Programm und XML-Dateien im selben Verzeichnis liegen, ergibt sich auch hier das gleiche Bild wie in den vorigen beiden Implementierungen: Es erscheint ein Swing-Fenster mit den konfigurierten Eigenschaften, so wie in Abbildung 43.1 zu sehen.
Abschließend ist noch zu erwähnen, dass mit der StAX-API auch XML-Dokumente erstellt werden können. Dazu stehen die XMLOutputFactory und der XMLStreamWriter zur Verfügung. Wir wollen an dieser Stelle nicht weiter darauf eingehen. |
|
Die Java Architecture for XML-Binding, kurz JAXB (http://jaxb.java.net), ist eine XML-Programmierschnittstelle, die aus den JSRs 31 und 222 hervorgegangen ist. JAXB ist also ein Java-Standard und eine Implementierung ist seit Version 6 fester Bestandteil des J2SE.
Um JAXB verwenden zu können, benötigen wir eine XML-Grammatik. DTDs sind nämlich nicht offizieller Bestandteil der Spezifikation, ihre Unterstützung ist laut Dokumentation nur experimenteller Natur. Für unsere Beispielanwendung werden wir daher eine XML Schema Definition erstellen (vgl. Abschnitt 43.1.3), die unserer DTD semantisch gleicht. Um Namenskonflikte mit den Klassen java.awt.Frame und java.awt.Font zu vermeiden, stellen wir darin den Elementnamen den Präfix »c« vor.
001 <?xml version="1.0" encoding="iso-8859-15"?> 002 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> 003 004 <xs:element name="cframe"> 005 <xs:complexType> 006 <xs:sequence> 007 <xs:element ref="ctitle"/> 008 <xs:element ref="cfont"/> 009 <xs:element ref="cfgcolor"/> 010 <xs:element ref="cbgcolor"/> 011 </xs:sequence> 012 </xs:complexType> 013 </xs:element> 014 015 <!-- Fenstertitel --> 016 <xs:element name="ctitle" type="xs:string"/> 017 018 <!-- Schriftart --> 019 <xs:element name="cfont"> 020 <xs:complexType> 021 <xs:simpleContent> 022 <xs:extension base="xs:string"> 023 <xs:attribute name="size" type="xs:int" default="12"/> 024 </xs:extension> 025 </xs:simpleContent> 026 </xs:complexType> 027 </xs:element> 028 029 <!-- Vordergrundfarbe --> 030 <xs:element name="cfgcolor" type="xs:int"/> 031 032 <!-- Hintergrundfarbe --> 033 <xs:element name="cbgcolor" type="xs:int"/> 034 035 </xs:schema> |
config_jaxb.xsd |
Auch das XML-Dokument zur Konfiguration unseres Fensterprogramms müssen wir nun leicht anpassen. Zum einen müssen wir die Referenz auf die DTD durch eine Referenz auf die XSD ersetzen (Zeile 2 und 3). Zum anderen haben sich die Elementnamen geändert, denn sie fangen ja nun mit »c« an.
001 <?xml version="1.0" encoding="iso-8859-15"?> 002 <cframe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 003 xsi:noNamespaceSchemaLocation='config_jaxb.xsd'> 004 <ctitle>XML-Beispiel</ctitle> 005 <cfont size="11">Arial</cfont> 006 <cfgcolor>0</cfgcolor> 007 <cbgcolor>16777215</cbgcolor> 008 </cframe> |
config_jaxb.xml |
JAXB funktioniert nach dem sogenannten »Data Binding«-Prinzip, das Java-Objekte in XML-Dokumente übersetzt und umgekehrt:
Zentraler Bestandteil von JAXB sind die Klassen und Interfaces im Package javax.xml.bind und seinen Unterpackages sowie der Binding-Compiler xjc, der im gleichen Verzeichnis wie der Javacompiler javac und die Virtual Maschine java liegt. Aufgabe des Binding-Compiler ist es, aus einer XML-Grammatik Java-Klassen zu erzeugen, mit deren Hilfe sich das Marshalling und Unmarshalling einfach realisieren lässt.
Um den Binding-Compiler zu testen, rufen wir ihn einfach per Kommandozeile
auf:
xjc
Das Ergebnis dieses unparametrisierten Aufrufs ist eine Auflistung aller Aufrufparameter samt zugehöriger Erklärungen. Falls der Binding-Compiler vom System nicht gefunden wird, haben Sie vielleicht eine ältere Java-Version installiert oder das bin-Verzeichnis des JDK liegt nicht in der PATH-Umgebungsvariablen Ihres Betriebssystems. Ziehen Sie in diesem Fall bitte Abschnitt 2.1 zu Rate.
Um unser Beispiel auszuprobieren, wechseln wir das Verzeichnis, in
dem die XSD-Datei liegt (Listing 43.24).
Nun rufen wir den Binding-Compiler auf, um aus der Datei config_jaxb.xsd
die für das Marshalling und Unmarshalling nötigen Java-Klassen
zu erzeugen:
xjc config_jaxb.xsd
Der Binding-Compiler generiert aus dem Wurzelelement und den komplexen
Subelementen die Java-Klassen Cfont.java,
Cframe.java und ObjectFactory.java
und legt sie in einem Paket (und gleichnamigen Unterverzeichnis) generated
an:
parsing a schema...
compiling a schema...
generated/Cfont.java
generated/Cframe.java
generated/ObjectFactory.java
Außerdem generiert er eine ObjectFactory, um entsprechende Objekte zu erzeugen. Für uns ist diese Klasse jedoch zunächst ohne Bedeutung, weil wir manuell keinen Objektbaum erzeugen wollen. Interesanter sind die Schnittstellen der anderen generierten Klassen, beispielsweise die von Cframe:
public String getCtitle() public void setCtitle(String value) public Cfont getCfont() public void setCfont(Cfont value) public int getCfgcolor() public void setCfgcolor(int value) public int getCbgcolor() public void setCbgcolor(int value) |
Für alle Subelemente des Cframe-Elements wurden get- und set-Methoden generiert, mit denen die Eigenschaften des Frames gesetzt, verändert und abgefragt werden können. Die Schnittstelle der generierten Klasse Cfont sieht ähnlich aus:
public int getSize() public void setSize(Integer value) public String getValue() public void setValue(String value) |
Anders als in Cframe wurden hier zusätzlich die Methoden getValue und setValue zum Abholen und Setzen des Elementinhalts generiert. Da das cframe-Element per Definition keinen Elementinhalt besitzt, generiert der Binding-Compiler auch keine entsprechenden Getter und Setter.
Zudem fällt auf, dass das size-Attribut vom Typ Integer ist, weil das korrespondierende Attribut in der XSD vom Datentyp int ist. Die generierten Klassen bilden also nicht nur die Struktur der Grammatik ab, sondern berücksichtigen auch den Datentyp der Attribute und Elemente. Mit einer DTD als Grundlage wäre das nicht möglich gewesen, denn DTDs kennen solche elementaren Datentypen nicht.
Für das Unmarshalling mit JAXB benötigen wir noch einige Klassen aus der JAXB-API. Die Klasse JAXBContext stellt den Einstiegspunkt in die API dar und bietet Schnittstellen zum Erzeugen von Marshaller- und Unmarshaller-Objekten an:
public static JAXBContext newInstance(String packages) throws JAXBException public Unmarshaller createUnmarshaller() throws JAXBException public Marshaller createMarshaller() throws JAXBException |
javax.xml.bind.JAXBContext |
Ein Objekt vom Typ JAXBContext erzeugt man mit Hilfe der Methode newInstance. Deren Parameter packages enthält eine durch Doppelpunkte getrennte Liste von Package-Namen, die Klassen enthalten, die mittels xjc-Compiler erzeugt wurden. In unserem Fall ist das nur das Package generated.
Über den JAXBKontext können wir uns einen Unmarshaller beschaffen, der eine Reihe von unmarshall-Methoden zur Verfügung stellt. Außerdem kann am Unmarshaller ein Schema gesetzt werden, um die Validierung einzuschalten. Möchte man den Abbruch bei Fehlern erzwingen, so ist im Gegensatz zu den früheren APIs kein expliziter ValidationEventHandler zu setzen, denn bei Fehlern bricht das Unmarshalling per Default ab.
public Object unmarshal(Reader reader) throws JAXBException public Object unmarshal(Source source) throws JAXBException public Object unmarshal(InputStream is) throws JAXBException public void setSchema(Schema schema) |
javax.xml.bind.Unmarshaller |
Ein Schema-Objekt lässt sich mit Hilfe einer SchemaFactory erzeugen:
public static SchemaFactory newInstance(String schemaLanguage) public Schema newSchema(URL schema) throws SAXException public Schema newSchema(File schema) throws SAXException |
javax.xml.validation.SchemaFactory |
Mögliche Werte für den schemaLanguage-Parameter, der den Typ der Grammatik definiert, sind in der Klasse javax.xml.XMLConstants als Konstante definiert. Wir verwenden eine XML Schema Definition, also verwenden wir die Konstante W3C_XML_SCHEMA_NS_URI.
001 /* Listing4326.java */ 002 003 import generated.Cframe; 004 005 import java.awt.*; 006 007 import javax.swing.*; 008 import javax.xml.bind.*; 009 import javax.xml.validation.*; 010 011 import org.xml.sax.SAXException; 012 013 public class Listing4326 extends JFrame 014 { 015 private String title; 016 private Color fgcolor; 017 private Color bgcolor; 018 private Font font; 019 020 public Listing4326() 021 { 022 readConfig(); 023 024 setTitle(title); 025 026 addWindowListener(new WindowClosingAdapter()); 027 JLabel label = new JLabel("Hello, World", JLabel.CENTER); 028 label.setPreferredSize(new Dimension(200, 200)); 029 030 Container pane = getContentPane(); 031 pane.setLayout(new FlowLayout()); 032 pane.add(label); 033 034 pane.setBackground(bgcolor); 035 pane.setForeground(fgcolor); 036 label.setFont(font); 037 038 pack(); 039 } 040 041 private void readConfig() 042 { 043 try { 044 JAXBContext context = JAXBContext.newInstance("generated"); 045 Unmarshaller unmarshaller = context.createUnmarshaller(); 046 047 SchemaFactory sf = SchemaFactory.newInstance( 048 javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); 049 050 Schema schema = sf.newSchema(getClass().getResource("config_jaxb.xsd")); 051 unmarshaller.setSchema(schema); 052 053 Cframe frame = (Cframe) unmarshaller.unmarshal( 054 getClass().getResource("config_jaxb.xml")); 055 this.title = frame.getCtitle(); 056 this.bgcolor = new Color(frame.getCbgcolor()); 057 this.fgcolor = new Color(frame.getCfgcolor()); 058 this.font = new Font(frame.getCfont().getValue(), Font.PLAIN, 059 frame.getCfont().getSize()); 060 } 061 catch (JAXBException x) { 062 x.printStackTrace(); 063 } 064 catch (SAXException x) { 065 x.printStackTrace(); 066 } 067 } 068 069 public static void main(String[] args) 070 { 071 Listing4326 window = new Listing4326(); 072 window.setVisible(true); 073 } 074 } |
Listing4326.java |
Das Beispiel kann auf die gleiche Weise ausprobiert werden wie alle vorigen. Wir legen einfach config_jaxb.xml im gleichen Verzeichnis ab wie das Listing und die XSD und starten dann das Fensterprogramm. Wie in allen anderen Fällen erscheint nun ein Swing-Fenster entsprechend den Konfigurationsangaben in der XML-Datei.
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |