In letzter Zeit hat sich ein weiterer Ansatz zur lesenden
Verarbeitung von XML-Dokumenten durch Programmiersprachen
etabliert: die sogenannten
extrahierenden Parser
(engl. pull parser). Ihr Verarbeitungsmodell wurde gezielt
im Kontrast zu den bisher vorgestellten Mechanismen
entwickelt.
SAX und DOM verarbeiten XML-Eingaben, ohne dabei dem
Anwender den Eingriff in den Fluß der Verarbeitung zu
gestatten. Der Kontrollfluß wird nicht durch den Code im
Programm definiert, sondern wird zur Ausführungszeit durch
die Struktur des verarbeiteten XML-Dokuments bestimmt. Bei
SAX spiegelt sich dies in der Reihenfolge der durch den
Parser aufgerufenen Call-back-Routinen wieder, bei DOM sind
im Verlauf der Baumkonstruktion durch einen Parsers
keinerlei Eingriffsmöglichkeiten für den Programmierer
vorgesehen.
Extrahierende Parser kehren dies Paradigma um. Sie
definieren eine Schnittstelle um innerhalb des
Programmflusses aktiv Inhalte eines XML-Dokumentes zu
extrahieren. Allerdings erfolgt der Zugriff nicht wahlfrei
(wie es beispielsweise bei Abfragen mit XPath der Fall ist),
sondern konsekutiv entlang der Reihenfolge der einzelnen
Primitive im XML-Dokument. Dieses Verhalten ist daher
weniger mit einer Anfrage, als eher mit dem Vorrücken eines
Dateizeigers bei linearer Konsumption vergleichbar.
Ein extrahierender Parser gestattet dem Programmierer die konsekutive lesende Verarbeitung der einzelnen Primitive eines XML-Dokuments gemäß der Dokumentordnung.
Gegenwärtig sind Pull-Parser für die Java-Sprachwelt verfügbar. Es liegt auch eine Implementierung als Bestandteil des Microsoft .NET-Frameworks vor. Jedoch konnte sich für diese Art der Verarbeitung noch kein allgemein anerkanntes und unterstütztes API herausbilden.
Lediglich die
Common API for XML Pull Parsing (XPP)
konnte im Java-Umfeld einige Bedeutung erlangen und ist
verfügbar. Dieser Schnittstellenvorschlag bildet auch
die Grundlage eines Standardisierungsansatzes (JSR 173)
im Rahmen des Java Community Processes: Die
Streaming API for XML (StAX)
.
Das vorliegende Kapitel stützt sich auf eine
Implementierung dieser API die unter dem Namen XPP3/MXP1
kostenfrei verfügbar
ist.
Das XPP-API definiert zur Verwaltung mindestens die folgenden Schnittstellen:
XmlPullParserFactory
: Fabrikklasse zur Erzeugung eines Parsers
XmlPullParser
: Abstrakte Klasse, die den tatsächlichen Parser
repräsentiert. Alle konkret instantiierbaren Parser
erben von dieser Klasse
XmlPullException
: Generische Ausnahmeklasse zur Signalisierung
verschiedenster Fehler
Bereits diese Übersicht zeigt als einen ersten wesentlichen Unterschied zu den klassischen Parserschnittstellen: Bei der XPP-API wurde Wert auf die Standardisierung der Infrastruktur zur Erzeugung des Parsers sowie die Darstellung auftretender Fehler gelegt.
Zur Extraktion der Inhalte eines XML-Dokuments sieht die XPP-Schnittstelle folgende durch Instanzen der Klasse XmlPullParser umgesetzte Methoden vor:
getEventType
: liefert Aufschluß darüber welche XML-Primitive
(START_TAG, END_TAG, CONTENT ...) extrahiert wurde
getLocalName
: liefert für Ereignisse des Typs START_TAG und
END_TAG den lokalen Namen des extrahierten Elements
getNamespaceUri
: liefert die URI des Namensraumes dem das
extrahierte Element zugeordnet ist
getPrefix
: liefert -- sofern vorhanden -- das
Namensraum-Präfix des extrahierten Elements
readContent
: liefert den textuellen Inhalt des extrahierten
Elements
getAttributeCount
: liefert für Ereignisse des Typs START_TAG die
Anzahl der für das extrahierte Element definierten
Attribute
getAttributeLocalName(int i)
: liefert den lokalen Namen des i-ten Attributs für
Ereignisse des Typs START_TAG
getAttributeNamespaceUri(int i)
: liefert die URI des Namensraumes dem das i-te
Attribut zugeordnet ist
getAttributePrefix(int i)
: liefert -- sofern vorhanden -- das
Namensraum-Präfix des i-ten Attributs
getAttributeType(int i)
: liefert -- sofern durch Zugriff auf eine DTD oder
ein XML-Schema möglich -- den Datentyp des i-ten
Attributs
getAttributeValue(int i)
: liefert den Wert des i-ten Attributs
getAttributeValue(String namespace, String
localName)
: liefert den Wert des durch Namen und Namensraum
bezeichneten Attributs
Der Code des hier vorgestellten Beispiels zeigt die
Nutzung der XPP-API.
Zuerst wird eine Parser-Fabrik erzeugt, mit der wiederum
später der tatsächlichen Parsers erzeugt wird.
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
Diese wird durch Aufruf der Methode
setNamespaceAware
dahingehend parametrisiert, daß die von ihr zur
Verfügung gestellten Parser in einem Modus konform zur
Namensraumspezifikation arbeiten.
factory.setNamespaceAware(true);
Anschließend wird von der Fabrik eine neue Parserinstanz angefordert deren Eingabekanal auf die als Kommandozeilenparameter übergebene Datei gelenkt wird.
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(argv[0]));
Die Extraktion der einzelnen Bestandteile des
Eingabedokuments vollzieht sich in der Schleife ab Zeile
17. Dort wird, solange nicht das Ereignis
END_DOCUMENT
gelesen wurde, mittels Aufruf der Methode
next
ein weiteres Ereignis aus dem XML-Eingabestrom
angefordert.
Nach Feststellung des Ereignistyps (Zeilen 18 und 20)
kann eine Verarbeitung der mit dem Ereignis verknüpften
XML-Primitive erfolgen.
while ((eventType = xpp.next()) != xpp.END_DOCUMENT) {
if (eventType == xpp.START_TAG)
System.out.println("element started");
if (eventType == xpp.END_TAG)
System.out.println("element ended");
} //whileDas gesamte Codebeispiel:
import org.gjt.xpp.XmlPullParser;
import org.gjt.xpp.XmlPullParserException;
import org.gjt.xpp.XmlPullParserFactory;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class XPPsimple {
public static void main(String argv[]) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(argv[0]));
int eventType;
while ((eventType = xpp.next()) != xpp.END_DOCUMENT) {
if (eventType == xpp.START_TAG)
System.out.println("element started");
if (eventType == xpp.END_TAG)
System.out.println("element ended");
} //while
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} //try
} //main()
} //class XPPsimpleEinschließlich der im vorherigen Beispiel verwendeten Ereignistypen unterstützen XPP-konforme Parser folgende Ereignistypen:
START_DOCUMENT
: Beginn eines Dokuments bevor XML-Anteile durch den
Parser verarbeitet wurden. Dieses Ereignis wird
nicht durch alle Implementierungen zur Verfügung
gestellt, da es implizit für den gesamten
programmiersprachlichen Quellcode vor dem ersten
Aufruf des XPP-Parsers gilt.
CDSECT
: Markiert das Auftreten einer CDATA-Sektion
innerhalb des XML-Dokumentes.
COMMENT
: Markiert das Auftreten eines XML-Kommentars
innerhalb des Eingabedokumentes.
START_TAG
: Markiert durch Extraktion des Start-Tags den
Beginn eines Elements.
END_TAG
: Markiert durch Extraktion des End-Tags den
Abschluß eines Elements. Für leere Elemente wird
dieses Ereignis, unabhängig von der möglicherweise
genutzten Minimierungssyntax, direkt nach dem
zugehörigen START_TAG-Ereignis extrahiert.
ENTITY_REF
: Markiert das Auftreten einer Entitätsreferenz.
IGNORABLE_WHITESPACE
: Markiert das Auftreten von Leerraumsymbolen, die
durch den Parser ohne Informationsverlust überlesen
werden können.
PROCESSING_INSTRUCTION
: Markiert das Auftreten einer Processing
Instruction.
TEXT
: Markiert das Auftreten freien Textes als
Elementinhalt.
END_DOCUMENT
: Markiert das Ende des Dokuments. Nach diesem
Ereignis können keine Weiteren extrahiert werden.
In diesem Beispiel sollen die Auftreten der einzelnen XML-Primitive auf der Basis eines extrahierenden Parsers gezählt werden. Damit entspricht das Programm einer Re-Implementierung des im Kapitel SAX vorgestellten Beispiels mit der gleichen Funktionalität.
import org.gjt.xpp.XmlPullParser;
import org.gjt.xpp.XmlPullParserException;
import org.gjt.xpp.XmlPullParserFactory;
import java.io.FileReader;
import java.io.IOException;
public class XPPsample3 {
public static void main(String argv[]) {
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(argv[0]));
int eventType;
int startDocument = 1;
int endDocument = 0;
int startTag = 0;
int endTag = 0;
int elementContent = 0;
do {
eventType = xpp.next();
if (eventType == xpp.END_DOCUMENT) {
endDocument++;
} else if (eventType == xpp.START_TAG) {
startTag++;
} else if (eventType == xpp.END_TAG) {
endTag++;
} else if (eventType == xpp.CONTENT) {
elementContent++;
} //if
} while (eventType != xpp.END_DOCUMENT);
System.out.println("# startDocument events:" + startDocument + "\n" +
"# endDocument events:" + endDocument + "\n" +
"# startTag events:" + startTag + "\n" +
"# endTag events:" + endTag + "\n" +
"# element content events:" + elementContent + "\n"
);
} catch (XmlPullParserException e) {
System.out.println("EXCEPTION CAUGHT: " + e.getClass().getName());
e.printStackTrace();
} catch (IOException e) {
System.out.println("EXCEPTION CAUGHT: " + e.getClass().getName());
e.printStackTrace();
} //try
} //main()
} //class XPPsample3
Insbesondere im Vergleich zum ereignisbasierten Lesen
unter Nutzung der SAX-Schnittstelle fällt die veränderte
Umsetzung auf.
So muß der Gesamtablauf nun nicht mehr in verschiedene
Methoden aufgespalten werden, die durch den Parser
aufgerufen werden, sondern kann innerhalb einer Methode
realisiert werden.
Aus diesem Grunde kann auch die konzeptionell
suboptimale Verwendung globaler Variablen unterbleiben.
Im Code fällt die Initialisierung der Zählvariable
startDocument
mit
1
auf (Zeile 15). Diese wird notwendig, da die verwendete
Parserimplementierung keine Extraktion von
START_DOCUMENT
leistet. Dieses Ereignis liegt immer implizit vor dem
Start des Extraktionsvorganges an.
Auch dieses Beispiel entspricht funktional einem Beispiel des SAX-Kapitels, es zählt die Auftretenshäufigkeit der Elemente in einem XML-Dokument. Jedoch unterstreicht es -- insbesondere im Vergleich zum SAX-basierten Beispiel -- die Natur der XPP-Schnittstelle. Im vorliegenden Fall werden durch den Parser keine Methodenaufrufe durchgeführt. Die Extraktion und Verarbeitung der einzelnen Elemente des betrachteten XML-Dokuments liegt ausschließlich in der Verantwortung des Programmierers.
import org.gjt.xpp.XmlPullParserFactory;
import org.gjt.xpp.XmlPullParser;
import org.gjt.xpp.XmlPullParserException;
import java.util.HashMap;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
public class XPPsample2 {
private static final Integer ONE = new Integer(1);
public static void main(String argv[]) {
try {
HashMap elementHM = new HashMap();
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new FileReader(argv[0]));
int eventType;
Integer freq;
String qName;
while ((eventType = xpp.next()) != xpp.END_DOCUMENT) {
if (eventType == xpp.START_TAG) {
qName = xpp.getNamespaceUri()+":"+xpp.getLocalName();
freq = (Integer) elementHM.get(qName);
elementHM.put(qName, (freq == null ? ONE : new Integer(freq.intValue() + 1)));
} //if
} //while
System.out.println("RESULT=" + elementHM);
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} //try
} //main()
} //class XPPsample2Ausgehend von den vorstehend beschriebenen Ereignistypen läßt sich leicht eine Prüfung auf Wohlgeformtheit realisieren. Üblicherweise wird dies von allen verfügbaren XPP-Implementierungen durchgeführt. Allerdings können Verletzungen der grundlegenden XML-Strukturierungsregeln auch bei diesem Parsingmodell, wie auch für SAX, nicht während eines nach außen abgeschlossenen Aufrufs erkannt werden. Stattdessen werden diese erst im Verlaufe des Extraktionsvorganges offenbar.
import org.gjt.xpp.XmlPullParser;
import org.gjt.xpp.XmlPullParserException;
import org.gjt.xpp.XmlPullParserFactory;
import java.io.FileReader;
import java.io.IOException;
public class XPPsample4 {
public static void main(String argv[]) {
XmlPullParser xpp = null;
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
xpp = factory.newPullParser();
xpp.setInput(new FileReader(argv[0]));
int eventType;
while ((eventType = xpp.next()) != xpp.END_DOCUMENT) {
} //while
} catch (XmlPullParserException e) {
System.out.println("EXCEPTION CAUGHT: " + e.getClass().getName());
System.out.println("Error occured at line: " + xpp.getLineNumber() + " at column " + xpp.getColumnNumber());
e.printStackTrace();
} catch (IOException e) {
System.out.println("EXCEPTION CAUGHT: " + e.getClass().getName());
e.printStackTrace();
} //try
} //main()
} //class XPPsample4Führt man den Code des Beispiels mit einem nicht wohlgeformten Dokument als Eingabedokument aus, so erzeugt der Parser folgende Ausnahme:
EXCEPTION CAUGHT: org.gjt.xpp.impl.tokenizer.TokenizerException
Error occured at line: 3 at column 17
org.gjt.xpp.impl.tokenizer.TokenizerException: attribute value must start with double quote or apostrophe not 'a' at line 3 and column 17 seen "...<root>\r\n\t<elementA att"...
at org.gjt.xpp.impl.tokenizer.Tokenizer.next(Tokenizer.java:1156)
at org.gjt.xpp.impl.pullparser.PullParser.next(PullParser.java:392)
at XPPsample4.main(XPPsample4.java:23)