XML-Verarbeitung mit SAX und DOM

Werbung
XML-Verarbeitung mit SAX und DOM
Holger Jakobs – [email protected], [email protected]
2006-08-10
aktuelle Version: http://www.bg.bib.de/portale/xml/pdf/XML-DOM-SAX.pdf
Inhaltsverzeichnis
1 SAX
1.1 Lizenzierung von SAX . . . . . . . . .
1.2 Technische Voraussetzungen . . . . . .
1.3 Kurzbeispiel . . . . . . . . . . . . . . .
1.3.1 Dokument und Ereignisse . . .
1.3.2 Java-Code . . . . . . . . . . . .
1.4 Datenkonvertierung mit SAX . . . . .
1.4.1 XML in CSV . . . . . . . . . .
1.4.2 CSV in XML . . . . . . . . . .
1.5 Validieren mit Java . . . . . . . . . . .
1.5.1 DTD-Validation mit SAX . . .
1.5.2 Schema-Validation mit Xerces .
.
.
.
.
.
.
.
.
.
.
.
2
2
2
3
3
3
6
6
10
11
11
16
2 DOM
2.1 Parsen von XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Erzeugen eines neuen Dokuments . . . . . . . . . . . . . . . . . . . . . . .
2.3 Anwendung von XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
19
20
21
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Es gibt zwei Möglichkeiten, ein XML-Dokument zu verarbeiten: Entweder wird das gesamte
Dokument in die Anwendung eingelesen und ein passender Baum im Speicher erstellt, oder
das Dokument wird elementweise eingelesen und ebenso elementweise verarbeitet.
Bei DOM wird das gesamte Dokument eingelesen und kann im Speicher beliebig oft
gelesen und verändert werden, bevor es dann wieder – in welche Form auch immer –
ausgegeben wird. Auch die Erzeugung völlig neuer Dokumente ist möglich. Nachteil ist der
Speicherbedarf gerade beim Lesen sehr großer Dokumente.
Bei dagegen wird das Dokument nach und nach eingelesen, währenddessen bei jedem
gelesenen Element eine von Ihnen geschriebene Methode (callback) aufgerufen wird, die
Ihnen mitteilt, dass die Daten nun zur Verarbeitung bereit stehen. Sie können sie ignorieren,
in eigene Datenstrukturen einbauen oder gleich wieder ausgeben – je nach Erfordernis. Das
bedeutet, dass nur die gerade benötigten Daten überhaupt im Speicher vorgehalten werden.
Auf diese Weise können beliebig große Dokumente verarbeitet werden.
1
1 SAX
1 SAX
SAX1 ist die Abkürzung für Simple API for XML“, ein ursprünglich lediglich für Java
”
vorgesehenes Interface für die Verarbeitung von XML-Daten. Mittlerweile unterstützen
viele Programmiersprachen SAX.
SAX verwendet keinen komplett in den Arbeitsspeicher eingelesenen Baum des zu verarbeitenden Dokuments, sondern geht elementweise vor und löst bei jedem Element ein
Ereignis aus. Durch diese Ereignissteuerung werden die Inhalte des Dokuments nacheinander mittels Callbacks verarbeitet. Dies minimiert den Arbeitsspeicherbedarf und ermöglicht
die Verarbeitung auch sehr großer Dokumente.
Mit Hilfe der Callback-Handler kann durchaus während der Verarbeitung eine anwendungsspezifische Struktur des Dokuments im Arbeitsspeicher erstellt werden, aber es wird
eben kein generischer Baum der einzelnen Knoten aufgebaut, der das gesamte Dokument
repräsentiert. Tatsächlich verwenden viele DOM-Bibliotheken intern einen SAX-Parser,
wodurch sogar SAX-spezifische Fehlermeldungen bei der Verwendung von DOM auftreten
können.
1.1 Lizenzierung von SAX
SAX ist public domain“, es unterliegt keiner Lizenz, auch nicht der GPL oder LGPL.
”
Der Name ist nicht geschützt. Innerhalb von Java definiert SAX sein Interface und seinen
Code, außerhalb von Java, d. h. bei Verwendung anderer Programmiersprachen, kann SAX
abweichend definiert sein. Insbesondere Microsoft hat teilweise andere Vorstellungen davon,
was SAX tun sollte und hat es daher anders implementiert. Wegen dieser Schwierigkeiten
beschränken wir uns auf das Original von Java.
1.2 Technische Voraussetzungen
Um mit SAX arbeiten zu können, benötigt man eine möglichst aktuelle Version von Java,
mindestens aber Java 1.1, einen SAX-kompatiblen Parser und eine SAX2-Distribution im
Java-Classpath. Dies alles ist meistens bereits installiert, wenn man eine Java-Entwicklungsumgebung hat.
Man kann im Quellcode statt XMLReaderFactory.createXMLReader() auch new org.apache.xerces
verwenden, allerdings muss dann auf allen Plattformen, auf denen das Programm laufen
soll, genau dieser SAX2-Treiber vorhanden sein. Die Verwendung von createXMLReader()
lässt die Verknüpfung mit einem speziellen Treiber offen, so dass sie erst zur Laufzeit
durchgeführt wird. Dies ist mit JDBC vergleichbar, wo man einen Treiber im ProgrammQuelltext fest verankern kann, oder aber ihn erst zur Laufzeit auswählt.
1) http://sax.sourceforge.net
2
1 SAX
1.3 Kurzbeispiel
1.3 Kurzbeispiel
1.3.1 Dokument und Ereignisse
Wir gehen von diesem kleinen XML-Dokument aus:
<?xml version="1.0"?>
<doc>
<para>Hello, world!</para>
</doc>
Ein ereignisgesteuertes Interface wird dieses Dokument lesen und folgende Ereignisse auslösen:
start document
start element: doc
start element: para
characters: Hello, world!
end element: para
end element: doc
end document
Die Anwendung behandelt diese Ereignisse prinzipiell genauso wie Ereignisse einer grafischen Oberfläche. Es ist nicht notwendig, das gesamte Dokument im Arbeitsspeicher vorzuhalten.
1.3.2 Java-Code
Der Java-Code MySAXApp.java zur Verarbeitung des Dokuments und Anzeige der Ereignisse sieht so aus:
import
import
import
import
import
import
import
java.io.FileInputStream;
java.io.InputStreamReader;
org.xml.sax.XMLReader;
org.xml.sax.InputSource;
org.xml.sax.Attributes;
org.xml.sax.helpers.XMLReaderFactory;
org.xml.sax.helpers.DefaultHandler;
public class MySAXApp extends DefaultHandler {
public static void main (String args[])
throws Exception {
XMLReader xr = XMLReaderFactory.createXMLReader();
3
1.3 Kurzbeispiel
1 SAX
MySAXApp handler = new MySAXApp();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
// Eingabedateien verarbeiten
for (int i=0; i < args.length; i++) {
FileInputStream fis = new FileInputStream (args [i]);
InputStreamReader isr = new InputStreamReader (fis, "UTF-8");
xr.parse (new InputSource (isr));
} // for
} // main()
public void startDocument () {
System.out.println("Start document");
} // startDocument()
public void endDocument () {
System.out.println("End document");
} // endDocument()
public void startElement (String uri, String name,
String qName, Attributes atts) {
if ("".equals (uri))
System.out.println("Start element: " + qName);
else
System.out.println("Start element: {" + uri + "}" + name);
for (int i=0; i < atts.getLength(); i++) {
System.out.println("Attribut: " + atts.getLocalName(i) +
" (" + atts.getType(i) + ") = " + atts.getValue(i));
} // for
} // startElement()
public void endElement (String uri, String name, String qName) {
if ("".equals (uri))
System.out.println("End element: " + qName);
else
System.out.println("End element:
{" + uri + "}" + name);
} // endElement()
public void characters (char ch[], int start, int length) {
String ausgabe = "";
4
1 SAX
1.3 Kurzbeispiel
for (int i = start; i < start + length; i++) {
if (Character.isWhitespace(ch[i]) && ch[i] != ’ ’) continue;
switch (ch[i]) {
case ’\\’:
ausgabe += "\\\\";
break;
case ’"’:
ausgabe += "\\\"";
break;
case ’\n’:
ausgabe += "\\n";
break;
case ’\r’:
ausgabe += "\\r";
break;
case ’\t’:
ausgabe += "\\t";
break;
default:
ausgabe += ch[i];
break;
} // switch
} // for
if (ausgabe.length() > 0) {
System.out.print("Characters:
");
System.out.println(ausgabe);
} // if
} // characters()
} // class MySAXApp
Der Name der zu verarbeitenden Datei wird als Parameter übergeben. Es können sogar
mehrere Dateinamen übergeben werden, weil eine Schleife über alle Argumente codiert
wurde.
Durch das Überschreiben der Methoden startDocument(), endDocument(), startElement(), endElement() und characters() werden diese neuen Methoden als Callback bei
Eintreten der zugehörigen Ereignisse aufgerufen. Außer bei startDocument() und endDocument() wird über Parameter Information über das aktuell verarbeitete Element bereitgestellt. Aus diesen können die gewünschten Teile entnommen und verarbeitet werden
– in diesem einführenden Beispiel werden sie einfach auf der Standardausgabe ausgegeben.
5
1.4 Datenkonvertierung mit SAX
1 SAX
1.4 Datenkonvertierung mit SAX
Im folgenden sollen einige Beispiele zur Datenkonvertierung mit Hilfe von SAX dargestellt
werden. Viele Anwendungen benötigen Daten in bestimmten Formaten, die man aus XMLDaten leicht generieren kann. Diese Schnittstellenproblematik ist geradezu allgegenwärtig.
Die Konvertierung kann in beide Richtungen notwendig sein – je nach Aufgabenstellung.
1.4.1 XML in CSV
CSV (comma-separated values) ist eines der gängigsten klassischen Datenformate zum
Austausch und kann von vielen Programmen im- und exportiert werden, z. B. auch von
Tabellenkalkulationen und Textverarbeitungen. Daher bietet sich die Konvertierung von
und nach XML geradezu an.
Zunächst die Richtung XML in CSV, denn dazu benötigen wir die Verarbeitung von
XML mit Hilfe unseres Parsers. Vielleicht werden gar nicht alle Daten benötigt, sondern
nur manche. Je nach Art des gefundenen Elements generieren wir eine CSV-Ausgabe oder
eben auch nicht. Die zu verarbeitende Datei telefon.xml sieht so aus:
<?xml version="1.0"?>
<telefonabrechnung>
<teilnehmer>
<name>Fritz Meier</name>
<rufnummer>1234567</rufnummer>
<anschrift>
<strasse>Hauptstr. 2</strasse>
<plz>51465></plz>
<ort>Bergisch Gladbach</ort>
</anschrift>
<bankverbindung>
<bank>Postbank Köln</bank>
<blz>370 100 50</blz>
<konto>123456789</konto>
</bankverbindung>
</teilnehmer>
<zeitraum monat="05" jahr="2004">
<grundpreis>24.00</grundpreis>
<ortsgespraeche>12.54</ortsgespraeche>
<ferngespraeche>42.44</ferngespraeche>
<auslandsgespraeche>0.00</auslandsgespraeche>
<sonderrufnummern>0.00</sonderrufnummern>
</zeitraum>
<zeitraum monat="06" jahr="2004">
<grundpreis>24.00</grundpreis>
<ortsgespraeche>9.57</ortsgespraeche>
6
1 SAX
1.4 Datenkonvertierung mit SAX
<ferngespraeche>52.12</ferngespraeche>
<auslandsgespraeche>3.44</auslandsgespraeche>
<sonderrufnummern>0.48</sonderrufnummern>
</zeitraum>
</telefonabrechnung>
Frage ist, wie sich diese Daten sinnvoll in eine CSV-Datei konvertieren lassen. In ihrer Gänze
ist das sicherlich kaum möglich, weil sie verschiedene Aspekte enthalten, nämlich Angaben
zum Kunden und Angaben zu Rechnungen für diverse Zeiträume. Bei der Konvertierung
beschränken wir uns daher darauf, nur einen der Aspekte zu berücksichtigen – es sollen
hier die Rechnungsdaten extrahiert und umgewandelt werden.
Aber auch nach dieser Einschränkung bleiben noch Fragen offen: Soll pro Monat ein Datensatz mit mehreren Spalten erzeugt werden – für jede Gesprächsart eine? Oder sollen die
Gesprächsarten untereinander aufgelistet werden, so dass Monat und Jahr jeweils daneben
wiederholt werden? Oder sollen gar schon Werte summiert werden und nur als Summe
erscheinen? Natürlich gibt es noch weitere Darstellungsmöglichkeiten.
Wir können ja mal diese beiden beschriebenen Varianten realisieren. So soll es also aussehen, wenn man es mal tabellarisch darstellt:
monat
jahr grundortsfernauslandssonderpreis gespraeche gespraeche gespraeche rufnummern
05 2004
24.00
12.54
42.44
0.00
0.00
06 2004
24.00
9.57
52.12
3.44
0.48
Die zweite Variante sähe so aus:
monat
05
05
05
05
05
06
06
06
06
06
jahr
posten betrag
2004
grundpreis
24.00
2004
ortsgespraeche
12.54
2004
ferngespraeche
42.44
2004 auslandsgespraeche
0.00
2004 sonderrufnummern
0.00
2004
grundpreis
24.00
2004
ortsgespraeche
9.57
2004
ferngespraeche
52.12
2004 auslandsgespraeche
3.44
2004 sonderrufnummern
0.48
Bei der Verarbeitung sollen also nur die zeitraum-Elemente mit all ihren Kind-Elementen
berücksichtigt werden. Das Element teilnehmer wird ignoriert, mitsamt all seinen KindElementen. Genau dies muss im Java-Code umgesetzt werden. Dazu setzen wir ein Member
der Klasse namens aktiv auf false und schalten es für die Dauer der Verarbeitung von
zeitraum-Elementen auf true. Eine weitere Verarbeitung findet nur dann statt, wenn der
Schalter wahr ist.
7
1.4 Datenkonvertierung mit SAX
1 SAX
Das erste Java-Programm telefon_csv1.java erzeugt eine Ausgabe wie in der oberen
Tabelle – im Beispiel auf der Standardausgabe.
import
import
import
import
java.util.HashMap;
java.io.FileReader;
org.xml.sax.*;
org.xml.sax.helpers.*;
public class telefon_csv1 extends DefaultHandler {
boolean aktiv=false;
String monat, jahr;
HashMap kosten = new HashMap();
String currentElement;
public static void main (String args[])
throws Exception {
XMLReader xr = new org.apache.crimson.parser.XMLReaderImpl();
telefon_csv1 handler = new telefon_csv1();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
// Eingabedateien verarbeiten
for (int i=0; i < args.length; i++) {
FileReader r = new FileReader (args [i]); // nur US-ASCII!!
System.out.println ("Monat,Jahr,Grund,Ort,Fern,Ausland,Sonder");
xr.parse (new InputSource (r));
} // for
} // main()
public void startElement (String uri, String name,
String qName, Attributes atts) {
if (qName.equals ("zeitraum")) {
aktiv=true;
monat = atts.getValue("monat");
jahr = atts.getValue("jahr");
kosten.clear();
} else {
if (!aktiv) return;
} // if
currentElement = name;
} // startElement()
8
1 SAX
1.4 Datenkonvertierung mit SAX
public void endElement (String uri, String name, String qName) {
if (qName.equals ("zeitraum")) {
if (kosten.isEmpty()) {
System.out.println ("keine Daten vorhanden!");
} else {
System.out.print (monat + ",");
System.out.print (jahr + ",");
System.out.print (kosten.get ("grundpreis") + ",");
System.out.print (kosten.get ("ortsgespraeche") + ",");
System.out.print (kosten.get ("ferngespraeche") + ",");
System.out.print (kosten.get ("auslandsgespraeche") + ",");
System.out.println (kosten.get ("sonderrufnummern"));
} // if
aktiv=false;
} // if
} // endElement()
public void characters (char ch[], int start, int length) {
String s = new String (ch).substring (start, start+length);
if (s.trim().length() == 0) return;
if (!aktiv) return;
kosten.put (currentElement, Double.valueOf (s));
// Mit Hilfe von Schema-Validation kann sichergestellt werden,
// dass hier keine Format-Exception auftreten wird.
} // characters()
} // class telefon_csv1
Das zweite Java-Programm telefon_csv2.java unterscheidet sich nur in der Methode
endElement und in der Kopfzeilen-Ausgabe, weshalb auch nur diese Methode gezeigt wird:
public void endElement (String uri, String name, String qName) {
if (qName.equals ("zeitraum")) {
if (kosten.isEmpty()) {
System.out.println ("keine Daten vorhanden!");
} else {
System.out.println (monat + "," + jahr + ",grundpreis,"
+ kosten.get ("grundpreis"));
System.out.println (monat + "," + jahr + ",ortsgespraeche,"
+ kosten.get ("ortsgespraeche"));
9
1.4 Datenkonvertierung mit SAX
1 SAX
System.out.println (monat + "," + jahr + ",ferngespraeche,"
+ kosten.get ("ferngespraeche"));
System.out.println (monat + "," + jahr + ",auslandsgespraeche,"
+ kosten.get ("auslandsgespraeche"));
System.out.println (monat + "," + jahr + ",sonderrufnummern,"
+ kosten.get ("sonderrufnummern"));
} // if
aktiv=false;
} // if
} // endElement()
Tatsächlich kann man die zweite Variante sogar weiter im Sinne von XML flexibilisieren,
wenn man die Namen der einzelnen Posten gar nicht mehr fest codiert, sondern genau die
Posten ausgibt, die in der XML-Datei vorkommen. Wenn es dann neuartige Posten gibt,
braucht man das Programm nicht mehr zu verändern. Allerdings ist die Reihenfolge der
einzelnen Posten eines Monats nicht mehr steuerbar, nur alphabetisch sortieren könnte
man noch. Codiert sieht das so aus:
public void endElement (String uri, String name, String qName) {
if (qName.equals ("zeitraum")) {
if (kosten.isEmpty()) {
System.out.println ("keine Daten vorhanden!");
} else {
Iterator i = kosten.keySet().iterator();
while (i.hasNext()) {
Object key = i.next();
System.out.println (monat + "," + jahr + "," +
key + ", " + kosten.get (key));
} // while
} // if
aktiv=false;
} // if
} // endElement()
Damit es funktioniert, muss allerdings noch java.util.Iterator importiert werden.
1.4.2 CSV in XML
Nun zur umgekehrten Richtung CSV in XML, wozu man keinen XML-Parser benötigt,
sondern die Daten einfach mit Java einliest und dann XML generiert. Dies ist allgemein
der einfachere Vorgang als umgekehrt, denn das CSV-Format ist von seiner Natur her
einfacher. Damit es nicht allzu trivial wird, lesen wir zwei CSV-Dateien ein, aus denen
dann aber nur eine einzige XML-Datei erzeugt werden soll. Die Daten eines Kunden liegen
10
1 SAX
1.5 Validieren mit Java
in der Datei teilnehmer.txt, die Abrechnungsdaten in gebuehren.csv. So haben wir
alle Daten beisammen, um die im Abschnitt 1.4.1 auf Seite 6 gezeigte Datei telefon.xml
erzeugen zu können.
Es wird also zunächst die Datei mit den Kundendaten geöffnet und gelesen, so dass das
Element <teilnehmer> geschrieben werden kann. Die Datei ist folgendermaßen aufgebaut:
Rufnummer 1234567
Fritz Meier
Hauptstr. 2
51465 Bergisch Gladbach
Postbank Köln, BLZ 370 100 50, Konto 123456789
Anschließend wird die Datei gebuehren.csv eingelesen und verarbeitet, wobei aus jeder
Zeile ein Element <zeitraum> erzeugt wird:
2004,05,24.00,12.54,42.44,0.00,0.00
2004,06,24.00,9.57,52.12,3.44,0.48
Da dies im Grunde ganz gewöhnliche Dateiverarbeitung mit Java ist, wird hier kein fertiges
Programm gezeigt. Zur Probe sollte die erzeugte Datei anschließend mit den Programmen
zur Umwandlung von XML in CSV wieder verarbeitet werden. Ein Prüfen mittels SchemaValidator bietet sich ebenfalls an.
Alternativ wäre natürlich möglich, mittels DOM einen Baum zu erstellen und diesen
anschließend als XML auszugeben. Damit wäre zumindest die Wohlgeformtheit in jedem
Fall sichergestellt. Für größere Aufgaben – insbesondere wenn Flexibilität gefordert ist –
ist das auch der bessere Weg. Beispiele hierzu müssten noch erstellt werden.
1.5 Validieren mit Java
1.5.1 DTD-Validation mit SAX
Der SAX-Parser kann auch validieren. Dies muss von ihm allerdings erst gefordert werden,
was durch das Einschalten des entsprechenden Features und Überschreiben der zugehörigen
Methoden geschieht. Das kann dann beispielsweise so aussehen:
import
import
import
import
import
import
import
import
import
java.io.FileReader;
java.io.FileInputStream;
java.io.InputStreamReader;
org.xml.sax.SAXNotRecognizedException;
org.xml.sax.SAXNotSupportedException;
org.xml.sax.XMLReader;
org.xml.sax.InputSource;
org.xml.sax.helpers.XMLReaderFactory;
org.xml.sax.Attributes;
11
1.5 Validieren mit Java
import
import
import
import
1 SAX
org.xml.sax.helpers.DefaultHandler;
org.xml.sax.ErrorHandler;
org.xml.sax.SAXException;
org.xml.sax.SAXParseException;
public class SaxValidate
extends DefaultHandler
implements ErrorHandler
{
public static void main (String args[])
throws Exception {
XMLReader xr = new org.apache.xerces.parsers.SAXParser();
SaxValidate handler = new SaxValidate();
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
//xr.setProperty ("http://xml.org/sax/properties/lexical-handler", null);
xr.setFeature ("http://xml.org/sax/features/validation", true);
try {
String id = "http://xml.org/sax/features/validation";
if (xr.getFeature(id)) {
System.out.println("Parser is validating.");
} else {
System.out.println("Parser is not validating.");
} // if
} catch (SAXNotRecognizedException e) {
System.out.println("Can’t tell.");
} catch (SAXNotSupportedException e) {
System.out.println("Wrong time to ask.");
} // try-catch
// Eingabedateien verarbeiten
for (int i=0; i < args.length; i++) {
//FileReader r = new FileReader (args [i]); // Annahme: (US-)ASCII-Datei
FileInputStream fis = new FileInputStream (args [i]);
InputStreamReader isr = new InputStreamReader (fis, "UTF-8");
xr.parse (new InputSource (isr));
} // for
} // main()
public void startDocument () {
System.out.println("Start document");
12
1 SAX
1.5 Validieren mit Java
} // startDocument()
public void endDocument () {
System.out.println("End document");
} // endDocument()
public void startElement (String uri, String name,
String qName, Attributes atts) {
if ("".equals (uri))
System.out.println("Start element: " + qName);
else
System.out.println("Start element: {" + uri + "}" + name);
for (int i=0; i < atts.getLength(); i++) {
System.out.println("Attribut: " + atts.getLocalName(i) +
" (" + atts.getType(i) + ") = " + atts.getValue(i));
} // for
} // startElement()
public void endElement (String uri, String name, String qName) {
if ("".equals (uri))
System.out.println("End element: " + qName);
else
System.out.println("End element:
{" + uri + "}" + name);
} // endElement()
public void characters (char ch[], int start, int length) {
String ausgabe = "";
for (int i = start; i < start + length; i++) {
if (Character.isWhitespace(ch[i]) && ch[i] != ’ ’) continue;
switch (ch[i]) {
case ’\\’:
ausgabe += "\\\\";
break;
case ’"’:
ausgabe += "\\\"";
break;
case ’\n’:
ausgabe += "\\n";
break;
case ’\r’:
ausgabe += "\\r";
13
1.5 Validieren mit Java
1 SAX
break;
case ’\t’:
ausgabe += "\\t";
break;
default:
ausgabe += ch[i];
break;
} // switch
} // for
if (ausgabe.length() > 0) {
System.out.print("Characters:
System.out.println(ausgabe);
} // if
} // characters()
");
/*********************************************************************
Folgende Variablen und Methoden dienen zur Fehlerbehandlung
beim Validieren
**********************************************************************/
boolean
boolean
boolean
boolean
reportErrors =
abortOnError =
reportWarnings
abortOnWarning
true;
true;
= true;
= false;
private void print (String label, SAXParseException e) {
System.err.println ("** " + label + ": " + e.getMessage ());
System.err.println ("
URI = " + e.getSystemId ());
System.err.println ("
line = " + e.getLineNumber ());
} // print()
// für fatale Fehler
public void fatalError (SAXParseException fe)
throws SAXException {
if (reportErrors) print ("fatal error", fe);
if (abortOnError) throw fe;
} // fatalError()
// für nicht-fatale Fehler, z. B. Validitätsprobleme
public void error (SAXParseException e)
throws SAXException {
if (reportErrors) print ("error", e);
14
1 SAX
1.5 Validieren mit Java
if (abortOnError) throw e;
} // error()
// für Warnungen
public void warning (SAXParseException w)
throws SAXException {
if (reportErrors) print ("warning", w);
if (abortOnWarning) throw w;
} // warning()
} // class SaxValidate
Die XML-Datei wird mittels
FileInputStream fis = new FileInputStream (args [i]);
InputStreamReader isr = new InputStreamReader (fis, "UTF-8");
xr.parse (new InputSource (isr));
geöffnet und verarbeitet, weil das Öffnen in den anderen Programmen die Dateien automatisch aus US-ASCII-Dateien verarbeitet. Da in den XML-Daten aber angegeben ist,
dass es sich um UTF-8 handelt, muss das auch so gelesen werden, um eine entsprechende Warnung zu verhindern. So lange die Daten nur 7-Bit-Daten sind, ist das natürlich
kein Problem, sobald aber Umlaute oder andere Zeichen jenseits des US-ASCII-Zeichensatzes dabei sind, dann würden die Daten tatsächlich falsch interpretiert, wenn die Datei
in UTF -8 codiert ist, die Standard-Codierung der Prozessumgebung aber eine andere ist
(bei Windows 2000 beispielsweise Windows-Codepage-1252, bei Linux-Distributionen ist
sie teilweise ISO-8859-X, teilweise UTF-8).
Eine Beispieldatei, die einen Fehler meldet – wegen des falschen Elements <wurst>“,
”
wäre diese (wetter2.xml):
<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE weather-report SYSTEM "http://www.bg.bib.de/~bibjah/wreport.dtd">
<?xml-stylesheet type="text/xsl" href="wreport3-style.xml" ?>
<weather-report>
<date>März 06, 2000</date>
<time>09:10</time>
<area>
<city>Cologne</city>
<region >NRW</region>
<country>Germany</country>
<wurst>Hans</wurst>
</area>
15
1.5 Validieren mit Java
1 SAX
<measurements>
<skies>fine</skies>
<temperature scale="C">15</temperature>
<wind>
<direction>SW</direction>
<windspeed>6</windspeed>
</wind>
</measurements>
</weather-report>
Im obigen Programm ist der Aufruf des Crimson-Parsers auskommentiert und durch einen
Aufruf des neueren Xerces-Parsers (ebenfalls aus dem Apache-Projekt) ersetzt worden. Dieser kann neben DTDs auch mittels XML-Schemas validieren – was im nächsten Abschnitt
erläutert wird.
1.5.2 Schema-Validation mit Xerces
Weder die Validation mit DTDs noch die Verwendung des crimson-Parsers sind technisch
der letzte Stand. Zwar gilt crimson als der Parser, der garantiert in jeder Java-Umgebung
zur Verfügung steht, der Xerces-Parser hat aber einen deutlich größeren Funktionsumfang,
zu dem auch die Möglichkeit gehört, mittels Schemas zu validieren.
Einem XmlTester-Konstruktor übergibt man einen Dateinamen bzw. einen URL des
XML-Dokuments und als zweiten Parameter wahlweise den Dateinamen bzw. URL des
Schemas oder aber null. Im letzteren Fall muss das XML-Dokument einen Verweis auf
das zu verwendende Schema tragen – sonst findet mangels eines Schemas keine Validation
statt.
Mit Hilfe einer DocumentBuilderFactory wird ein DocumentBuilder erzeugt. Dessen
Methode parse kann man einen Dateinamen oder einen URL übergeben. Rückgabewert
von parse ist ein DOM-Document, d. h. ein Objekt einer Klasse, welche das DocumentInterface implementiert. Ein solches Document repräsentiert ein komplettes HTML- oder
XML-Dokument in einem Java-Objekt. Es ist quasi die Wurzel des Dokumentbaums und
ermöglicht den Zugriff auf die Daten des Dokuments.
Wegen der factory-Einstellungen mit setNamespaceAware, setValidating und setAttribute vor dem Erzeugen des DocumentBuilder und dem Parsen des Dokuments führt
der Parser die Schema-Validation durch. Das erzeugte Document-Objekt wird lediglich dazu
verwendet, den Namen des Root-Nodes anzuzeigen. Weitere Methoden zum Lesen, zum
Verändern und zum Schreiben in eine neue XML-Datei stehen zur Verfügung – siehe JavaAPI-Dokumentation.
/* XmlTester.java
basierend auf Code aus http://www.javaworld.com/javaworld/jw-08-2005/jw-0808-xml_p
kleine Aenderungen durch [email protected], 2006-03-02 */
16
1 SAX
import
import
import
import
import
import
import
import
import
1.5 Validieren mit Java
java.io.IOException;
javax.xml.parsers.DocumentBuilder;
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.ParserConfigurationException;
org.w3c.dom.Document;
org.w3c.dom.Node;
org.xml.sax.SAXException;
org.xml.sax.ErrorHandler;
org.xml.sax.SAXParseException;
public class XmlTester {
public static final void main (String[] args) {
if (args.length != 1 &&
args.length != 2) {
System.out.println ("Usage: java XmlTester myFile.xml [ mySchema.xsd ]\n")
System.out.println ("If a second argument is provided, the given schema fi
System.out.println ("Otherwise, the schema file location must be specified
System.exit (-1);
} // if
String xmlFile = args[0];
String schemaFile = null;
if (args.length == 2) {
schemaFile = args[1];
} // if
try {
XmlTester xmlTester = new XmlTester (xmlFile, schemaFile);
} catch (Exception e) {
System.out.println (e.getClass().getName() +": "+ e.getMessage());
} // try/catch
} // main()
public XmlTester(String xmlFile, String schemaFile)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
System.out.println ("DocumentBuilderFactory: "+ factory.getClass().getName());
factory.setNamespaceAware (true);
factory.setValidating (true);
factory.setAttribute ("http://java.sun.com/xml/jaxp/properties/schemaLanguage"
"http://www.w3.org/2001/XMLSchema");
17
1.5 Validieren mit Java
1 SAX
if (schemaFile != null) {
factory.setAttribute ("http://java.sun.com/xml/jaxp/properties/schemaSource
schemaFile);
} // if
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler (new SimpleErrorHandler());
Document document = builder.parse (xmlFile);
Node rootNode = document.getFirstChild();
System.out.println ("Root node: "+ rootNode.getNodeName());
} // XmlTester()
} // class XmlTester
class SimpleErrorHandler implements ErrorHandler {
public void error (SAXParseException exception) {
System.out.println ("error: "+ exception.getMessage());
} // error()
public void fatalError (SAXParseException exception) {
System.out.println ("fatalError: "+ exception.getMessage());
} // fatalError()
public void warning (SAXParseException exception) {
System.out.println ("warning: "+ exception.getMessage());
} // warning()
} // class SimpleErrorHandler
Damit der richtige Parser Xerces verwendet wird, muss dieser im CLASSPATH vor dem alten
Crimson-Parser liegen. Fehlt der Xerces oder liegt er im CLASSPATH weiter hinten, so wird
der Crimson-Parser verwendet, der aber die Attribute nicht kennt und daher einen Fehler
liefern wird: No attributes are implemented
Die Java-VM liest alle im CLASSPATH angegebenen Jar-Archive und schaut dort nach
der Datei META-INF/services/javax.xml.parsers.DocumentBuilderFactory. In dieser
steht dann der komplette Pfad der Klasse, die die DocumentBuilderFactory implementiert.
Wird also das zu Xerces gehörende Archiv (xercesImpl.jar) vor dem Crimson-Archiv
durchsucht, wird Xerces verwendet.
18
2 DOM
2 DOM
DOM2 ist die Abkürzung für Document Object Model“, ein plattform- und program”
miersprachenneutrales Interface des Word Wide Web Consortiums3 zur Verarbeitung von
XML-Daten. Es kann mit compilierten und mit interpretierten Sprachen verwendet werden, wobei Zugriff auf Teile und Veränderung des Dokuments möglich sind. Das Ergebnis
der Dokumentverarbeitung kann in das XML-Dokument zurückgeschrieben werden – oder
auch in ein anderes.
DOM4J4 ist eine handliche, quelloffene Bibliothek für das Arbeiten mit XML, XPath und
XSLT auf der Java-Plattform. Sie steht im XML-Portal5 zum Download zur Verfügung;
Sie brauchen das jar-Archiv lediglich Ihrem CLASSPATH hinzuzufügen.
Anhand einiger Beispiele soll das Arbeiten mit DOM4J erläutert werden.
2.1 Parsen von XML
Im ersten Beispiel soll ein XML-Dokument, dessen Name als Parameter übergeben wird,
geparst werden. Der Name des Wurzelelementes und die Namen aller direkten Kindelemente
davon werden ausgegeben.
import
import
import
import
import
org.dom4j.Document;
org.dom4j.Element;
org.dom4j.DocumentException;
org.dom4j.io.SAXReader;
java.util.Iterator;
public class ParseXML {
public static void main (String args[]) throws Exception {
// Eingabedateien verarbeiten
for (int i=0; i < args.length; i++) {
SAXReader reader = new SAXReader();
Document document = reader.read (args[i]);
iterate (document);
} // for
} // main()
public static void iterate (Document d) throws DocumentException {
Element wurzel = d.getRootElement();
2)
3)
4)
5)
http://w3.org/DOM
http://w3.org
http://dom4j.org
http://www.bg.bib.de/portale/xml/Quelltexte
19
2.2 Erzeugen eines neuen Dokuments
2 DOM
System.out.println ("Das Wurzelelement heißt "+
wurzel.getName());
System.out.println ("und hat folgende direkte Kindelemente:");
for (Iterator i = wurzel.elementIterator(); i.hasNext(); ) {
Element element = (Element) i.next();
System.out.println ("Element: " +element.getName());
} // for
} // iterate()
} // class ParseXML
Iteratoren sind ein gängiges Verfahren von Java, um Listen abzuarbeiten, schlagen Sie sie
ggf. in der Java-Online-Doku nach oder fragen Sie im Java-Unterricht.
Mit diesem kleinen Programm kann man schon erkennen, wie man an einzelne Elemente
aus dem XML-Baum herankommt. Das Interface Element hat viele Methoden, um Eigenschaften eines Elementes abzufragen, z. B. wie oben angwendet nach dem Namen, aber
auch nach dem Inhalt, nach Attributen, den enthaltenen Elementen usw. usf.
2.2 Erzeugen eines neuen Dokuments
In diesem Abschnitt sehen Sie auch, wie Sie ein bereits bestehendes, d. h. eingelesenes
Dokument verändern können. Da ein neues Dokument zunächst leer ist, müssen Sie vor
dem Schreiben in eine Datei auch hier Veränderungen vornehmen – allerdings ist es hier auf
das Hinzufügen von Elementen und Attributen beschränkt. Das Löschen oder Verändern
von vorhandenen Elementen und Attributen können Sie leicht in der API-Dokumentation
von DOM4J nachschlagen.
import
import
import
import
import
org.dom4j.Document;
org.dom4j.DocumentHelper;
org.dom4j.Element;
org.dom4j.io.XMLWriter;
org.dom4j.io.OutputFormat;
import java.io.FileWriter;
import java.io.IOException;
public class NewDoc {
public static void main (String[] args) {
Document neu = createDocument();
try {
writeDocument (neu);
} catch (Exception e) {
20
2 DOM
2.3 Anwendung von XSLT
System.out.println ("SAXException: " + e.getMessage());
System.exit (1);
} // try/catch
} // main()
public static Document createDocument() {
Document document = DocumentHelper.createDocument();
Element root = document.addElement( "Autorenliste" );
Element author1 = root.addElement( "Autor" )
.addAttribute( "Initialen", "JW" )
.addAttribute( "Ort", "Köln" )
.addText( "Johann Werkzeugmacher" );
Element author2 = root.addElement( "Autor" )
.addAttribute( "Initialen", "HS" )
.addAttribute( "Ort", "Bonn" )
.addText( "Hermann Schwaderlappen" );
return document
} // createDocument()
public static void writeDocument (Document d)
throws IOException {
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter (
new FileWriter ("neu.xml"), format);
writer.write (d);
writer.close();
} // writeDocument()
}
2.3 Anwendung von XSLT
Als letztes Beispiel soll die Verwendung des bereits früher besprochenen XSLT in einem
eigenen Java-Programm gezeigt werden. Es ist also festzustellen, dass die Verarbeitung
mit XSLT Stylesheets und die in eigenen Programmen keineswegs einander ausschließen,
sondern auch miteinander kombiniert werden können. Manche Verarbeitungsschritte lassen
sich leichter in XSLT codieren, andere leichter mit Java programmieren. Änderungen an
XSLT bedürfen keine Compilation, die am Java-Code schon. So hat alles seine Vor- und
Nachteile, die Sie in der Praxis abwägen müssen.
Das Java-Programm StyleXSLT.java sieht so aus:
21
2.3 Anwendung von XSLT
import
import
import
import
2 DOM
org.dom4j.io.SAXReader;
org.dom4j.Document;
org.dom4j.io.DocumentResult;
org.dom4j.io.DocumentSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import org.dom4j.io.XMLWriter;
import org.dom4j.io.OutputFormat;
import java.io.FileWriter;
import java.io.IOException;
public class StyleXSLT {
public static void main (String[] args) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer (
new StreamSource ("wreport-style.xml"));
SAXReader reader = new SAXReader();
Document document = reader.read ("wetter.xml");
DocumentSource src = new DocumentSource (document);
DocumentResult res = new DocumentResult();
t.transform (src, res);
Document transformedDoc = res.getDocument();
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter (
new FileWriter ("wetter.html"), format);
writer.write (transformedDoc);
writer.close();
} catch (Exception e) {
System.out.println ("SAXException: " + e.getMessage());
System.exit (1);
} // try/catch
} // main()
}
22
2 DOM
2.3 Anwendung von XSLT
Die TransformerFactory wird herangezogen, um einen neuen Transformer namens t zu
erzeugen, der das Stylesheet wreport-style.xml verwendet. Das Dokument wetter.xml
wird mit Hilfe eines SAXReaders in ein DOM-Document eingelesen. Es wird je eine Instanz
von DocumentSource und DocumentResult für den Transformationsvorgang gebildet und
mittels der Methode transform der Klasse Transformer die XSLT-Transformation durchgeführt. Das transformierte Dokument wird aus dem Ergebnis gelesen mittels der Methode
getDocument().
Damit das Ergebnis schön formatiert ausgegeben wird, wird ein entsprechendes OutputFormat generiert und anschließend das Dokument mit einem XMLWriter ausgegeben. Das
Zieldokument ist ein HTML-Dokument, wie man dem folgenden Stylesheet wreport-style.
xml entnehmen kann:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="weather-report"/>
</xsl:template>
<xsl:template match="weather-report">
<html>
<head><title>hi there</title></head>
<body> <!-- Fuer eine Auswahl von Elementen Vorlagen anwenden: -->
<xsl:apply-templates select="area|date|time|measurements"/>
</body>
</html>
</xsl:template>
<!-- Pattern = Knoten[area|date|time|measurements] -->
<xsl:template match="date|time"> <!--Fuer date ODER time-->
<p style="color:navy; background-color:yellow">
<xsl:value-of select="." /> <!-- fuege Wert(e) ein -->
</p>
</xsl:template>
<xsl:template match="area">
<p style="color:yellow; background-color:blue">
<!-- Inhalt des Kindes "country" -->
<xsl:value-of select="country" />
</p>
</xsl:template>
23
2.3 Anwendung von XSLT
2 DOM
<xsl:template match="measurements">
<p style="color:navy; background-color:yellow">
<!-- Inhalte aller direkten Kinder -->
<xsl:value-of select="./*" />
</p>
</xsl:template>
</xsl:stylesheet>
Das passende XML-Dokument wetter.xml sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="wreport-style.xml" ?>
<weather-report>
<date>Juni 06, 2000</date>
<time>09:10</time>
<area>
<!-- Dies ist ein Kommentarknoten -->
<city>Köln</city>
<region >NRW</region>
<country>Germany</country>
</area>
<measurements>
<skies>fine</skies>
<temperature scale="C">15</temperature>
<wind>
<direction>SW</direction>
<windspeed>6</windspeed>
</wind>
</measurements>
</weather-report>
Wie das erzeugte Dokument wetter.html aussieht, können Sie dem XSLT-Code entnehmen oder auch einfach ausprobieren. Die Dokumente stehen im XML-Portal6 im Bereich
Quelltexte zur Verfügung.
$Id: XML-DOM-SAX.tex,v 1.11 2006/08/10 13:33:22 hj Exp $
6) http://www.bg.bib.de/portale/xml
24
Herunterladen