Das Interface ContentHandler

(Auszug aus "XML in a Nutshell" von Elliotte Rusty Harold & W. Scott Means)

Ein ContentHandler, wie wir ihn in abgespeckter Form im folgenden Code-Beispiel sehen, ist ein weiteres Interface im Package org.xml.sax. Die Klasse wird vom Programmierer nach Belieben selbst implementiert. Als Nächstes erzeugt der Programmierer eine Instanz seiner Implementierung und konfiguriert den XMLReader damit. Der XMLReader liest das XML-Dokument und ruft für jeden Bestandteil, den er findet, eine passende Methode des Objekts auf, um dem Programm Auskunft über den Inhalt des XML-Dokuments zu geben. Sie können auf die Aufrufe der Methoden in der Weise reagieren, die Sie für passend halten.

Anmerkung: Die Klasse ContentHandler nichts mit der dahinsiechenden Klasse java.net.ContentHandler zu tun. Diese Namensgleichheit kann vor allem dann zu Problemen führen, wenn Sie sowohl java.net.* als auch org.xml.sax.* in eine Java-Klasse importieren. Besser ist es, nur die tatsächlich nötigen java.net-Klassen explizit zu importieren und nicht das ganze Package.

package org.xml.sax;

public interface ContentHandler {
    public void setDocumentLocator(Locator locator);
    public void startDocument(  ) throws SAXException;
    public void endDocument(  ) throws SAXException;
    public void startPrefixMapping(String prefix, String uri)
     throws SAXException;
    public void endPrefixMapping(String prefix) throws SAXException;
    public void startElement(String namespaceURI, String localName,
     String qualifiedName, Attributes atts) throws SAXException;
    public void endElement(String namespaceURI, String localName,
     String qualifiedName) throws SAXException;
    public void characters(char[] text, int start, int length)
     throws SAXException;
public void ignorableWhitespace(char[] text, int start, int length)
     throws SAXException;
    public void processingInstruction(String target, String data)
     throws SAXException;
    public void skippedEntity(String name) throws SAXException;

}

Code-Beispiel: Das Interface org.xml.sax.ContentHandler

Jedes Mal, wenn der XMLReader wieder ein Stück des XML-Dokuments gelesen hat, ruft er eine Methode des ContentHandler auf. Nehmen wir zum Beispiel an, der Parser liest das folgende einfache XML-Dokument:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>

Code-Beispiel: Ein einfaches XML-Dokument

Der Parser wird beim Lesen des Dokuments die folgenden Methoden seines ContentHandler in der angegebenen Reihenfolge aufrufen. Die Werte der Argumente geben wir jeweils hinter dem Methodennamen an:

1.

setDocumentLocator(Locator locator) locator:
 org.apache.xerces.readers.DefaultEntityHandler@1f953d

2.

startDocument(  )

3.

processingInstruction(String target, String data)
target: "xml-stylesheet"
data: "type='text/css' href='person.css'"

4.

startPrefixMapping(String prefix, String namespaceURI)
prefix: ""
namespaceURI: "http://xml.oreilly.com/person"

5.

startElement(String namespaceURI, String localName,
String qualifiedName, Attributes atts)
    namespaceURI: "http://xml.oreilly.com/person"
    localName: "person"
    qualifiedName: "person"
    atts: {} (keine Attribute, leere Liste)

6.

ignorableWhitespace(char[] text, int start, int length)
    text: <?xml version="1.0" encoding="ISO-8859-1"?>
    <?xml-stylesheet type='text/css' href='person.css'?>
    <!DOCTYPE person SYSTEM "person.dtd">
    <person xmlns="http://xml.oreilly.com/person">
        <name:name xmlns:name="http://xml.oreilly.com/name">
            <name:vorname>Sydney</name:vorname>
            <name:nachname>Lee</name:nachname>
        </name:name>
        <auftrag project_id="p2"/>
    </person>
start: 181
length: 3

7.

startPrefixMapping(String prefix, String uri)
    prefix: "name"
    uri: "http://xml.oreilly.com/name")

8.

startElement(String namespaceURI, String localName,
        String qualifiedName, Attributes atts)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "name"
    qualifiedName: "name:name"
    atts: {} (no attributes, an empty list)

9.

ignorableWhitespace(char[] text, int start, int length)
    text: <?xml version="1.0" encoding="ISO-8859-1"?>
    <?xml-stylesheet type='text/css' href='person.css'?>
    <!DOCTYPE person SYSTEM "person.dtd">
    <person xmlns="http://xml.oreilly.com/person">
      <name:name xmlns:name="http://xml.oreilly.com/name">
        <name:vorname>Sydney</name:vorname>
        <name:nachname>Lee</name:nachname>
      </name:name>
      <auftrag project_id="p2"/>
    </person>
    start: 236
    length: 5

10.

startElement(String namespaceURI, String localName,
        String qualifiedName, Attributes atts)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "vorname"
    qualifiedName: "name:vorname"
    atts: {} (keine Attribute, leere Liste)

11.

characters(char[] text, int start, int length)
    text: <?xml version="1.0" encoding="ISO-8859-1"?>
    <?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>
start: 253
length: 6

12.

endElement(String namespaceURI, String localName, String qualifiedName)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "vorname"
    qualifiedName: "name:vorname"

13.

ignorableWhitespace(char[] text, int start, int length)
    text: <?xml version="1.0" encoding="ISO-8859-1"?>
    <?xml-stylesheet type='text/css' href='person.css'?>
    <!DOCTYPE person SYSTEM "person.dtd">
    <person xmlns="http://xml.oreilly.com/person">
      <name:name xmlns:name="http://xml.oreilly.com/name">
        <name:vorname>Sydney</name:vorname>
        <name:nachname>Lee</name:nachname>
      </name:name>
      <assignment project_id="p2"/>
    </person>
start: 272
length: 5

14.

startElement(String namespaceURI, String localName, String qualifiedName,
            Attributes atts)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "nachname"
    qualifiedName: "name:nachname"
    atts: {} (keine Attribute, leere Liste)

15.

characters(char[] text, int start, int length)
text: <?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>
start: 288
length: 3

16.

endElement(String namespaceURI, String localName, String qualifiedName)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "nachname"
    qualifiedName: "name:nachname"

17.

ignorableWhitespace(char[] text, int start, int length)
text: <?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>
start: 303
length: 3

18.

endElement(String namespaceURI, String localName, String qualifiedName)
    namespaceURI: "http://xml.oreilly.com/name"
    localName: "name"
    qualifiedName: "name:name"

19.

endPrefixMapping(String prefix)
    prefix: "name"

20.

ignorableWhitespace(char[] text, int start, int length)
text: <?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>
start: 318
length: 3

21.

startElement(String namespaceURI, String localName, String qualifiedName,
            Attributes atts)
    namespaceURI: "http://xml.oreilly.com/person"
    localName: "auftrag"
    qualifiedName: "auftrag"
    atts: {project_id="p2"}

22.

endElement(String namespaceURI, String localName, String qualifiedName)
namespaceURI: "http://xml.oreilly.com/person"
    localName: "auftrag"
    qualifiedName: "auftrag"

23.

ignorableWhitespace(char[] text, int start, int length)
text: <?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type='text/css' href='person.css'?>
<!DOCTYPE person SYSTEM "person.dtd">
<person xmlns="http://xml.oreilly.com/person">
  <name:name xmlns:name="http://xml.oreilly.com/name">
    <name:vorname>Sydney</name:vorname>
    <name:nachname>Lee</name:nachname>
  </name:name>
  <auftrag project_id="p2"/>
</person>
start: 350
length: 1

24.

endElement(String namespaceURI, String localName, String qualifiedName)
    namespaceURI: "http://xml.oreilly.com/person"
    localName: "person"
    qualifiedName: "person"

25.

endPrefixMapping(String prefix)
    prefix: ""

26.

endDocument(  )

Dabei gibt es einige nicht-deterministische Punkte. Beachten Sie zum Beispiel, dass das an die Methode characters( ) übergebene char-Array ignorableWhitespace( ) das gesamte Dokument enthält! Das Stück, das der Parser tatsächlich zurückgibt, wird durch die zweiten beiden Argumente angegeben. Dies ist eine von Xerces-J vorgenommene Optimierung. Es kann durchaus sein, dass andere Parser verschiedene char-Arrays übergeben, sofern durch die Argumente start und length dieselben Textteile markiert werden. Es ist dem Parser auch freigestellt, ein großes Stück Text auf verschiedene Aufrufe einer der Methoden characters( ) oder ignorableWhitespace( ) zu verteilen. Sie können also nicht davon ausgehen, dass diese Methoden unbedingt sofort das größtmögliche Stück Text übergeben. Weitere Details, die von Parser zu Parser variieren können, sind zum Beispiel die Reihenfolge der Attribute innerhalb eines Tags oder ob ein Locator-Objekt durch den Aufruf von setDocumentLocator( ) spezifiziert wird.

Angenommen, Sie wollten die Anzahl der Elemente, Attribute, Steuerzeichen und Textzeichen zählen, die innerhalb eines XML-Dokuments vorkommen. Zu diesem Zweck müssten Sie zunächst eine Klasse schreiben, die das ContentHandler-Interface implementiert. Die aktuelle Anzahl der vier zu zählenden Bausteine wird in einem Feld gespeichert. Die Werte dieser Felder werden anfangs über die Methode startDocument( ) auf 0 gesetzt. Diese Methode wird genau einmal am Anfang jedes zu parsenden Dokuments aufgerufen. Die anderen Methoden haben nichts anderes zu tun, als die jeweiligen Felder zu inkrementieren. Die Methode endDocument( ) muss dann lediglich das Ergebnis ausgeben. Eine solche Klasse sehen Sie im folgenden Code-Beispiel.

import org.xml.sax.*;

public class XMLCounter implements ContentHandler {

  private int anzahlElemente;
  private int anzahlAttribute;
  private int anzahlVerarbeitungsanweisungen;
  private int anzahlZeichen;

  public void startDocument(  ) throws SAXException {
    anzahlElemente = 0;
    anzahlAttribute = 0;
    anzahlVerarbeitungsanweisungen = 0;
    anzahlZeichen = 0;
  }

  // Wir können wahlweise die Start-Tags oder die End-Tags zählen,
  // aber nicht beide. Leere Elemente werden von beiden Methoden gemeldet.
  public void startElement(String namespaceURI, String localName,
   String qualifiedName, Attributes atts) {
    anzahlElemente++;
    anzahlAttribute += atts.getLength(  );
  }

  public void endElement(String namespaceURI, String localName,
   String qualifiedName) {}

  public void characters(char[] text, int start, int length) {
    anzahlZeichen += length;
  }

  public void ignorableWhitespace(char[] text, int start, int length) {
    anzahlZeichen += length;
  }

  public void processingInstruction(String target, String data)
   throws SAXException {
    anzahlVerarbeitungsanweisungen++;
  }

  // Am Ende des Dokuments geben wir die Ergebnisse aus.
  public void endDocument(  ) {
    System.out.println("Anzahl Elemente: " + anzahlElemente);
    System.out.println("Anzahl Attribute: " + anzahlAttribute);
    System.out.println("Anzahl Verarbeitungsanweisungen: "
     + anzahlVerarbeitungsanweisungen);
    System.out.println("Anzahl Textzeichen: "
     + anzahlZeichen);
  }
// Die folgenden Methoden bewirken nichts, müssen aber implementiert
  // werden, um den Anforderungen des Interface zu genügen:
  public void setDocumentLocator(Locator locator) {}
  public void startPrefixMapping(String prefix, String uri) {}
  public void endPrefixMapping(String prefix) {}
  public void skippedEntity(String name) {}

}

Code-Beispiel: Die Klasse XMLCounter

Anmerkung: Diese Klasse muss fast alle Methoden im ContentHandler-Interface überschreiben. Wenn Sie allerdings wirklich nur ein oder zwei ContentHandler-Methoden verwenden möchten, könnten Sie stattdessen eine Unterklasse der Klasse DefaultHandler schreiben. Diese Adapterklasse implementiert alle Methoden des Interface ContentHandler mit wirkungslosen Methoden. Sie müssen dann also nur noch die Methoden überschreiben, die Sie wirklich interessieren.

Als Nächstes erzeugen wir einen XMLReader und konfigurieren ihn mit einer Instanz dieser Klasse. Abschließend parsen wir das zu untersuchende Dokument wie im folgenden Beispiel.

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.io.IOException;

public class DocumentStatistics {

  public static void main(String[] args) {

    XMLReader parser;
    try {
     parser = XMLReaderFactory.createXMLReader(  );
    }
    catch (SAXException e) {
      // Im Notfall versuchen wir, den Xerces-Parser über seinen Namen aufzurufen
      try {
        parser = XMLReaderFactory.createXMLReader(
         "org.apache.xerces.parsers.SAXParser");
      }
      catch (SAXException eex) {
        System.err.println("Konnte keinen SAX-Parser finden");
        return;
      }
    }

    if (args.length == 0) {
      System.out.println(
       "Usage: java DocumentStatistics URL1 URL2...");
    }
// Ein ContentHandler wird eingerichtet
    parser.setContentHandler(new XMLCounter(  ));

    // Der Parser startet ...
    for (int i = 0; i &lt; args.length; i++) {

      // Die URIs oder Dateinamen bekommen wir von der Kommandozeile
      try {
        parser.parse(args[i]);
      }
      catch (SAXParseException ex) { // Ein Wohlgeformtheitsproblem
        System.out.println(args[i] + " ist nicht wohlgeformt.");
        System.out.println(ex.getMessage(  )
         + " at line " + ex.getLineNumber(  )
         + ", column " + ex.getColumnNumber(  ));
      }
      catch (SAXException ex) { // Eine andere Art Fehler
        System.out.println(ex.getMessage(  ));
      }
      catch (IOException ex) {
        System.out.println("Eine Statistik für " + args[i]
         + " konnte nicht generiert werden,  IOException " + ex);
      }

    }

  }

}

Code-Beispiel: Die Klasse DocumentStatistics

Wenn wir das Programm aus diesem Beispiel über das XML-Dokument aus dem Code-Beispiel Ein einfaches XML-Dokument laufen lassen, bekommen wir die folgende Ausgabe:

D:\books\xian\examples\18&gt;java DocumentStatistics 12-2.xml
Anzahl Elemente: 5
Anzahl Attribute: 1
Anzahl Verarbeitungsanweisungen: 1
Anzahl Textzeichen: 29

Das generische Programm aus dem Code-Beispiel Die Klasse DocumentStatistics kann jedes wohlgeformte XML-Dokument untersuchen. Die meisten SAX-Programme sind aber speziellerer Natur und können nur mit bestimmten XML-Dokumenten arbeiten. Zum Beispiel erwarten sie feste Elemente oder Attribute, auf die sie dann reagieren. Dabei stützen sie sich möglicherweise auf Voraussetzungen, die ein validierender Parser garantiert. Aber das ist auch für ein SAX-Programm eine selbstverständliche Möglichkeit.

Der komplizierteste Teil der meisten SAX-Programme besteht meistens im Aufbau einer geeigneten Datenstruktur, die die gelesenen Informationen so lange aufbewahrt, bis alle erforderlichen Daten erfasst sind. In manchen Fällen sind diese Informationen ähnlich komplex wie das eigentliche XML-Dokument: Dann wäre es vielleicht besser, DOM zu benutzen, damit man wenigstens keine Datenstrukturen von Hand aufbauen muss. Meistens brauchen Sie zwar nur bestimmte Informationen, es ist aber immer wichtig, dass die zu generierenden Informationen weniger komplex sein sollten als das zu analysierende XML-Dokument.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

  


Copyright © 2005 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XML in a Nutshell" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de