SAX verstehen

(Auszug aus "Python & XML" von Christopher A. Jones & Fred L. Drake, Jr.)

Die erste Aufgabe mit SAX besteht darin, einen Handler zu entwerfen und zu implementieren, der mit Ihren speziellen XML-Dokumenten funktioniert. Bei der Arbeit an einem großen Projekt oder an einem großen Katalog gültiger Dokumente mag es Sinn machen, ein paar umfassende Handler zu implementieren, die mit mehreren Dokumenttypen umgehen können. Für kleinere Projekte kann es jedoch wünschenswerter sein, Handler für jeden speziellen Dokumenttyp zu entwickeln, dem Sie begegnen. Sobald Sie komplexere Anwendungen bauen werden, werden Sie sehen, daß die Dinge, die Sie mit XML versuchen zu tun, ebenso wie die XML-Dokumente selbst, die Art beeinflussen können, wie Sie Ihre Dokument-Handler entwickeln. Oft extrahieren die SAX-Methoden, die Sie implementieren, Daten aus dem Ereignisstrom, die Sie dann an eine andere Anwendung (etwa eine Datenbank) weitergeben können. Oder Sie wollen vielleicht eine intelligente Geschäftslogik darauf anwenden. Es ist sehr wahrscheinlich, daß die entsprechende Aufgabe Ihre Entwicklungsstrategie beeinflussen wird.

In der Praxis ist SAX immer eine Callback-basierte API, in der Sie Handler-Objekte implementieren, um XML zu verarbeiten. Sie reichen eine Referenz auf Ihre SAX-Handler-Objekte an einen SAX-fähigen Parser (oder Treiber; wir benutzen »Parser« für beides). Sobald das Parsen beginnt, ruft der Parser die Methoden Ihrer Handler-Objekte auf und gestattet Ihnen, das XML zu verarbeiten, auf daß Sie damit etwas Nützliches in Ihren Anwendungen und verteilten Systemen tun mögen.

SAX ist eine vorzügliche strombasierte API. Sie erlaubt eine schnellere Verarbeitung von Dokumenten ebenso wie den Umgang mit Dokumenten, die einfach zu groß sind, um sie in den Hauptspeicher zu laden. Außerdem ermöglicht Ihnen die ereignisbasierte API, auf Parsing-Ereignisse und -Fehler in »Echtzeit« zu reagieren, also dann, wenn sie auftreten, während das Dokument geparst wird, ohne darauf zu warten, daß das gesamte Dokument erst geladen werden müßte. Das kann besonders dann sehr wertvoll sein, wenn es um eine graphische Anwendung geht, die auf den Benutzer reagieren muß. Ein anderer in vielen Anwendungen großer Vorteil ist der geringere Speicherverbrauch, verglichen mit DOM-basiertem Code. Indem der Anwendung der Zugriff auf beliebige beim Parsen erzeugte Objekte erlaubt wird, kann sie den notwendigen Speicher-Overhead minimieren und Objekte sofort freigeben, wenn sie nicht mehr gebraucht werden.

SAX ist die Schnittstelle der Wahl, wenn Sie aus einem oder mehreren Dokumenten einige anwendungsspezifische Datenstrukturen erzeugen, aber nicht die XML-Struktur in Ihrer Anwendung verwalten müssen. Da SAX den von der Anwendung installierten Handlern einfache Ereignisse übergibt, muß der Programmierer während des Parsens vorsichtig sein, was die Protokollierung des Zustandes angeht, in dem sich die Anwendung befindet – es bietet sich an, die Anwendung als endlichen Automaten zu modellieren. Glücklicherweise muß der Programmierer keinerlei Strafen in punkto Speicherverbrauch oder Performanz zahlen, was beim Laden von potentiell sehr großen Dokumenten oft der Fall ist. Bei Verwendung der DOM-Schnittstelle wäre das schwerlich zu vermeiden, da dort normalerweise der gesamte Dokumentbaum so lange im Speicher gehalten wird, bis er freigegeben wird. (In Das Document Object Model sehen wir uns DOM im Detail an.)

Verwendung von SAX in einer Anwendung

Wenn mit SAX eine Anwendung erstellt wird, kann es hilfreich sein, sich die Anwendung als eine Menge von Komponenten vorzustellen. Der XML-Parser selbst, inklusive SAX-Treiber, ist eine Blackbox-Komponente, die von der Anwendung nur wenig Steuerinformationen benötigt. Für den XML-Parser sind die Handler-Objekte der einzige Weg, mit der Anwendung zu kommunizieren, aber deren Logik sollte sich mehr mit der Interpretation der vom Parser gelieferten Ereignisse beschäftigen und weniger damit, die Anwendung zu implementieren – sie bilden oft eine separate Schicht, die die Anwendung mit dem benötigten Datenmodell versorgt. Die Anwendung selbst benutzt die abgeleiteten Datenstrukturen und höhere Ereignisse der Handler-Objekte, um die eigentliche Arbeit zu verrichten. Die Beziehungen zwischen diesen Komponenten werden in der folgenden Abbildung gezeigt.

Komponenten einer SAX-Anwendung

Abbildung: Komponenten einer SAX-Anwendung

Bei kleineren Anwendungen ist es üblich, daß die Anwendung mit den Handler-Objekten identisch ist, oft mit dem Anwendungscode in den Callback-Methoden. Das funktioniert bei großen Anwendungen nicht gut, ist aber bei sehr einfachen Anwendungen ein vernünftiger Ansatz. Während Sie noch SAX lernen, hat es auch hervorragende pädagogische Nebeneffekte zu bieten, so daß unsere Beispiele den Anwendungscode direkt in die Implementierung der Handler einbetten. Man sieht leicht, wie Abstraktionen zwischen den SAX-Handler-Objekten und einer großen Anwendung erstellt werden.

SAX bezeichnet das Parser-Objekt als Reader (Leser). Es liest seine Eingabe aus einer Quelle und erzeugt für bestimmte Ereignisse in der Eingabe Aufrufe für die Handler-Methoden. (Es ist keine Bedingung, daß die Quelle ein XML-Dokument sein muß, obwohl das meistens der Fall ist.) Die Anwendung registriert Handler-Objekte mit den Methoden des Readers und darf einige weitere Eigenschaften des Parsers setzen. In unserer Übersicht der API beginnen wir mit der Untersuchung der Handler-Objekte, die dem Parser übergeben werden können, bevor wir dann einen kurzen Blick auf die Reader-Schnittstelle werfen.

SAX-Handler-Objekte

SAX setzt sich aus vier Hauptschnittstellen zusammen, die von Parsern für die verschiedenen Ereignisse aufgerufen werden, die während des Parsens auftreten. Python hat diese Methoden vom ursprünglichen Java leicht abgewandelt (meist durch Verwendung von Pythons mächtigeren primitiven Datentypen), um SAX in der Python-Umgebung gewissenhaft zu implementieren. Durch die Implementierung der verschiedenen Schnittstellen der Callback-API können Sie alle vom Parser erzeugten Ereignisse empfangen, während er unterschiedliche Teile des XML-Dokuments durchläuft. Gönnen wir uns einen kurzen Blick auf die unterschiedlichen Handler-Objekte, die implementiert werden können. (Eine komplette Referenz der Methoden, die vom Parser für jedes Objekt aufgerufen werden, wird in Die Python-SAX-API angegeben.)

ContentHandler

Die ContentHandler-Schnittstelle ist die meistbenutzte aller SAX-Schnittstellen und stellt die wichtigste Art dar, wie Ihre Anwendungen Parsing-Ereignisse erhalten. Parsing-Ereignisse zielen auf die Hauptauszeichnungen und Zeichendaten in Dokumenten ab. Mit der Methode setContentHandler unterrichten Sie Ihren SAX-fähigen Parser über Ihre Implementierung dieser Schnittstelle.

Die Callback-API ist der Teil von SAX, an dem Benutzer von XML das meiste Interesse haben. Diese API implementieren Sie, um mit dem Strom von Ereignissen versorgt zu werden, der vom Parser erzeugt wird. Jedes Element, das vorbeikommt, löst im Parser einen Aufruf der Methode startElement jenes Handlers aus, den Sie implementiert haben. Der für dieses XML entworfene startElement-Handler muß wissen, was er mit jedem Element anfängt, das er im Dokument vorfindet:

def startElement(self, name, attrs):
  if name == "webArticle":      
    subcat = attrs["subcategory"]
    if subcat.find("tech") > -1:
      self.inArticle = 1
      self.isMatch = 1

elif self.inArticle:
    if name == "header":
      self.title = attrs["title"]
      self.inBody = 1
    if name == "body":
      self.inBody = 1

ErrorHandler

Die ErrorHandler-Schnittstelle ermöglicht es Anwendungen, auf Fehler zu reagieren, die vom Parser zur Laufzeit erkannt werden. Dieses Objekt muß mit dem Reader-Objekt registriert werden (mit setErrorHandler), um wirksam zu sein. Alle Parsing-Fehler werden aufgrund ihres Schweregrades in drei Kategorien klassifiziert. Das Handler-Objekt implementiert für jeden Schweregrad eine andere Methode. Die geringfügigsten Fehler werden an die warning-Methode übergeben, während ernsthafte Verletzungen der Spezifikationen an die error-Methode übergeben werden, falls der Parser in der Eingabe nach weiteren Fehlern suchen kann. Wenn dies nicht möglich ist, werden sie an fatalError weitergegeben.

All diese Methoden erhalten einen einzigen Parameter, der immer eine Instanz einer SAXException-Schnittstelle ist. Diese Schnittstelle bietet eine Reihe von Methoden, um Informationen über den Fehler zu bekommen, inklusive darüber, wo und in welcher Eingabequelle der Fehler auftrat. Beschließt der Handler, die Bearbeitung einzustellen, kann das SAXException-Objekt einfach als Ausnahme ausgelöst werden.

Wenn Sie keinen Fehler-Handler angeben, besteht das Standardverhalten darin, bei Warnungen eine Fehlermeldung auf sys.stdout auszugeben und die Ausnahme bei normalen wie auch kritischen Fehlern auszulösen.

Wenn Sie das PyXML-Paket installiert haben, werden Sie dort ein paar bequeme Implementierungen im Modul xml.sax.saxutils vorfinden. Die Klasse ErrorPrinter ist ein Fehler-Handler, der unabhängig vom Schweregrad einen Fehlerbericht auf die Standardausgabe ausgibt. Und ErrorRaiser löst einfach die Ausnahme derart aus, daß Fehler die Bearbeitung immer abbrechen.

DTDHandler

Falls eine Anwendung Notationen und ungeparste Entities kennen muß, kann sie die setDTDHandler-Methode des SAX-Parsers verwenden, um ein DTDHandler-Objekt anzugeben, das diese Informationen empfängt. Objekte mit dieser Schnittstelle brauchen nur zwei Schnittstellen zu implementieren – eine für die Übergabe von Notationsdefinitionen und eine für Entity-Definitionen. Nur Definitionen von ungeparsten Entities (Entities mit spezifizierten Notationen) werden an diese Schnittstelle weitergegeben.

Das klingt vielleicht nicht so, als ob es viel von der Information abdeckt, die in einer DTD angegeben ist, aber es deckt ab, was eine Anwendung normalerweise braucht, wenn sie ungeparste Entities verwendet. Wie Sie sich erinnern, steht das »S« in SAX für »Simple« – die meisten Anwendungen brauchen tatsächlich keine Details des Inhaltsmodells oder anderer Entity-Definitionen. Wenn Sie wirklich mehr Informationen aus der DTD brauchen, sind dafür viele Mechanismen verfügbar:

  • Der in SAX optionale DeclHandler-Handler, der eventuell nicht von allen Parsern unterstützt wird.
  • Die native Schnittstelle des Expat-Parsers; siehe die Dokumentation für das Modul xml.parsers.expat in der Standardbibliothek.
  • Das Modul xml.parsers.xmlproc.dtdparser in PyXML.

EntityResolver

Dieser Handler muß, falls er implementiert wird, ebenfalls beim Parser mit dessen setEntityResolver-Methode registriert werden, bevor das Parsen beginnt. Wenn der Parser externe Entities findet, ruft er die resolveEntity-Methode Ihrer Implementierung auf. Anwendungsentwickler können den Parser mit dieser Methode auf eine alternative Stelle verweisen, um Entities aufzulösen, z. B. einen Cache-Speicher. Falls sie None oder einen Systembezeichner zurückgibt, versucht der Parser, das Entity mit den Mitteln von HTTP und FTP zu laden, wie sie die Python-Standardbibliothek zur Verfügung stellt.

Andere Handler-Objekte

Es gibt tatsächlich zwei weitere für SAX definierte Handler-Objekte, die jedoch als optional betrachtet werden und keine für den Parser ebenso bequem zu setzende Methoden haben. Die meisten Anwendungen werden sie nicht brauchen, aber sie zu kennen mag hilfreich sein, wenn man sie doch einmal braucht.

DeclHandler

Ein Objekt, bei dem einige Methoden aufgerufen werden, wenn der Parser Definitionen des Strukturmodells des Dokuments findet. Die Methoden werden bei Element- und Attributdeklarationen aufgerufen sowie für Deklarationen von internen und externen Entities.

LexicalHandler

Die Methoden dieses Objekts werden bei Ereignissen aufgerufen, um die sich Anwendungen eigentlich nicht kümmern, die aber bei einer Transformation nützlich sein können, die das Dokument nicht mehr als nötig verändern soll. Zu den Ereignissen, die diesem Handler berichtet werden, gehören Kommentare, Entity-Grenzen, Anfang und Ende der DTD sowie CDATA-Abschnittsgrenzen.

Ein SAX-Parser hat keine setDeclHandler- oder setLexicalHandler-Methoden. Diese Handler werden mit der Property-Schnittstelle des Parsers installiert, die wir gleich erläutern werden.

SAX-Reader-Objekte

Um Handler-Objekte zu benutzen, müssen wir sie mit einem SAX-Reader oder Parser registrieren. Alle Parser müssen die vier meistverwendeten Handler unterstützen, und es werden bequeme Methoden definiert, mit denen deren Werte gesetzt und abgefragt werden können. Die Routinen setContentHandler, setDTDHandler, setEntityResolver und setErrorHandler haben alle entsprechende Routinen, um den aktuellen Handler zu bekommen; diese Methoden haben Namen, die mit get statt set beginnen. Es gibt eine weitere Methode, setLocale, mit der die Landeseinstellungen für Fehler und Warnungen gesetzt werden können.

Zusätzlich zu diesen Konfigurationsmethoden bietet SAX Konzepte namens Features und Properties. Ein Feature ist eine gewisse Funktionalität, die ein- oder ausgeschaltet werden kann, und eine Property ist ein Wert, der mit dem Zustand des Parsers verknüpft ist und einen eigenen Namen hat. Abhängig vom speziellen Feature oder der Property (sowie von der Parser-Implementierung), können beide veränderlich oder unveränderlich sein, oder vielleicht nur dann veränderlich, wenn gerade kein Parsing-Vorgang stattfindet. Die zuvor erläuterten DeclHandler und LexicalHandler werden durch das Setzen von Parser-Properties konfiguriert. Die meisten Anwendungen werden weder Features noch Properties benötigen.

  

<< zurück vor >>

 

 

 

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

Copyright © 2002 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 "Python & XML" 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