Abstract

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.

Definition extrahierender Parser

Ein extrahierender Parser gestattet dem Programmierer die konsekutive lesende Verarbeitung der einzelnen Primitive eines XML-Dokuments gemäß der Dokumentordnung.

extrahierende Parser: Implementierungen

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.

XPP API - Die grundlegenden Schnittstellen

Das XPP-API definiert zur Verwaltung mindestens die folgenden Schnittstellen:

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.

XPP API - Schnittstellen

Zur Extraktion der Inhalte eines XML-Dokuments sieht die XPP-Schnittstelle folgende durch Instanzen der Klasse XmlPullParser umgesetzte Methoden vor:

XPP API - ein erstes Beispiel

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");
            } //while

Das 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 XPPsimple

XMLPP - unterstützte Ereignistypen

Einschließlich der im vorherigen Beispiel verwendeten Ereignistypen unterstützen XPP-konforme Parser folgende Ereignistypen:

XMLPP - zweites Beispiel: Zählen der einzelnen XML-Primitive

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.

XPP Beispiel 3: Elementhäufigkeit zählen

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 XPPsample2

XPP - Fehlerbehandlung

Ausgehend 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 XPPsample4

Fü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)