Lesen eines Artikels

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

In diesem Beispiel sehen wir, wie man mit SAX aus einem XML-Dokument Informationen extrahieren und benutzen kann. Die speziellen Dokumente, mit denen unser Skript dabei arbeitet, sind einfache News-Artikel, aber wir werden sehen, wie man mit Elementen, Attributen und Textinhalten arbeitet.

Einige Kompromisse beim Einsatz von SAX hängen davon ab, was Sie zu erreichen versuchen und wie der XML-Code strukturiert ist. SAX behandelt XML als kontinuierlichen Strom und schießt mit Ereignissen auf Ihren Handler, sobald sie auftreten. Das folgende Beispiel zeigt article.xml.

Beispiel: article.xml

<?xml version="1.0"?>
<webArticle category="news" subcategory="technical">
  <header title="NASA Builds Warp Drive" length="3k" author="Joe Reporter" distribution="all"/>
  <body>Seattle, WA – Today an anonymous individual announced that NASA has completed building a Warp Drive and has parked a ship that uses the drive in his back yard. This individual claims that although he hasn't been contacted by NASA concerning the parked space vessel, he assumes that he will be launching it later this week to mount an exhibition to the Andromeda Galaxy.</body>
</webArticle>

Das Beispiel enthält Auszeichnungen, die verschiedenartig strukturiert sind, bei denen es interessant sein kann, sie mit SAX zu parsen. Bei einem Dokument wie z. B. article.xml müssen wir verstehen, wie das Dokument strukturiert ist, bevor wir einen Handler schreiben, um es zu parsen. Daher ist der Handler eng mit der Struktur des Dokuments verbunden.

Schreiben eines einfachen Handlers

Sie können die Klasse ArticleHandler in eine neue Datei namens handlers.py schreiben; im weiteren Verlauf dieses Kapitels werden wir dieser Datei neue Handler hinzufügen. Halten wir es zu Beginn einfach, um zu sehen, wie SAX funktioniert:

from xml.sax.handler import ContentHandler

class ArticleHandler(ContentHandler):
    """ Ein Handler für Artikel in XML """
    def startElement(self, name, attrs):
      print "Start-Element:", name

Nun müssen wir ein Skript erzeugen, um den Parser zu instanziieren, den Handler zuzuweisen und die eigentliche Arbeit zu erledigen.

Erzeugen des Hauptprogramms

Egal wie komplex Ihre Handler-Objekte werden mögen, man braucht selten viel Code, um den Parser aufzusetzen. Betrachten wir das folgende Beispiel, in dem wir nur die gerade erstellte ArticleHandler-Klasse verwenden und das parsen, was wir im Strom der Standardeingabe vorfinden. Die Datei art.py, die im folgenden Beispiel gezeigt wird, demonstriert, wie man das macht.

Beispiel: art.py

#!/usr/bin/env python
# art.py

import sys

from xml.sax import make_parser
from handlers import ArticleHandler

ch = ArticleHandler( )
saxparser = make_parser( )

saxparser.setContentHandler(ch)
saxparser.parse(sys.stdin)

Einmal erzeugt, können Sie den Code auf der Kommandozeile mittels Dateiumlenkung ausführen, um die Standardeingabe zu füllen (Unix und Windows):

 $> python art.py < article.xml 

Die Ausgabe, die den einfachen Artikel-Handler benutzt, sieht folgendermaßen aus:

Start-Element: webArticle
Start-Element: header
Start-Element: body

Die Ausgabe illustriert das einfache Vorgehen Ihrer ArticleHandler-Klasse, bei dem einfach nur der Name jedes vorgefundenen Tags ausgegeben wird. Um den XML-Code wirklich zu benutzen, müssen Sie dem Handler mehr Funktionalität in der Datei handlers.py hinzufügen.

Hinzufügen von Intelligenz

Mit XML kann man Informationen zu verschiedenen Zwecken parsen. Wenn Sie einen News-Artikel in XML erzeugen, kann die eine Anwendung ihn sich holen und als HTML anzeigen, während eine andere ihn in einer Suchdatenbank indizieren kann. Man kann sich leicht vorstellen, daß ein Dienstleister gerne intelligente Agenten anbieten möchte, um Internet-Quellen nach News-Beiträgen, Sonderangeboten und anderen für Sie interessanten Dingen zu durchsuchen, basierend auf Voreinstellungen, die Sie angegeben haben. XML macht diesen Vorgang möglich, verglichen mit der Alternative, HTML zuverlässig nach strukturierter Information zu parsen, was nahezu unmöglich ist. HTML gibt nur das Erscheinungsbild eines Dokuments an, aber nicht seinen strukturellen Aufbau. In HTML können zwei Dokumente im Browser exakt gleich aussehen, aber hinter der Fassade extrem verschiedene Tags benutzen. Die Informationen in HTML zu parsen wird nicht funktionieren, es sei denn, der Seitengestalter hatte dies vor, als er die Seite entwickelte.

Ihr News-Agent ist so konfiguriert, daß er nach Technologie-Geschichten sucht, insbesondere solchen, die mit Raumfahrt zu tun haben. Sobald er einen solchen Artikel findet, zeigt er eine Meldung an, die Schlagzeile und die ersten paar Wörter des Rumpftextes. Sie können Ihren Handler erweitern, um dies zu unterstützen.

Da SAX strombasiert ist, ist es manchmal notwendig, Flags zu setzen, anhand derer Sie mitverfolgen können, wann Sie bestimmte Elemente betreten haben oder nicht. Sollten Sie feststellen, daß Sie zu viele verschiedene Flags setzen, könnten Sie sich überlegen, es mit einem DOM-Ansatz statt mit SAX zu versuchen. SAX ist perfekt bei großen Mengen von Operationen auf einem sehr langen XML-Strom. Wenn Sie jedoch versuchen, eine komplexe Datenstruktur aus dem Dokument zu erhalten, sind Sie mit DOM vielleicht besser bedient.

Um unser Beispiel einfach zu halten, setzen Sie einige Flags, sobald die Ereignisse weitergereicht werden, und suchen Sie die gewünschte Information. Prüfen Sie in der startElement-Methode, ob Sie sich wirklich in einem News-Artikel befinden und ob Ihr Artikel wirklich ein technischer Artikel ist. Wenn er beide Kriterien erfüllt, ändern Sie eine Boolesche Variable, damit andere Methoden bei den Daten aufpassen, die sie erhalten. Setzen Sie auch eine Variable des Handlers selbst, damit die Hauptanwendung weiß, daß der Handler einen technischen Artikel gefunden hat, denn das war seine Aufgabe:

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

elif self.inArticle:
    if name == "header":
      self.title = attrs.get("title", "")
    if name == "body":
      self.inBody = 1

Anhand der letzten Bedingung sieht man, ob der Parser in das Body-Element eines relevanten Artikels eingetreten ist. Falls ja, dann weiß die characters-Methode nun, daß sie mit dem Puffern von Daten beginnen soll, wenn sie aufgerufen wird:

def characters(self, characters):
   if self.inBody:     
     if len(self.body) < 80:
       self.body += characters
     if len(self.body) > 80:
       self.body = self.body[:78] + "..."
       self.inBody = 0

Am Ende suchen Sie nach dem Abschluß des Body-Tags, um der characters-Methode anzuzeigen, daß sie Zeichendaten von nun an ignorieren kann:

def endElement(self, name):
   if name == "body":
     self.inBody = 0

Über die Implementierung dieser drei Methoden hinaus wird die Klasse dahingehend modifiziert, ihre Instanzvariablen zu initialisieren, und es wird ihr die Instanzvariable isMatch hinzugefügt, um der Hauptanwendung anzuzeigen, ob dieser Handler etwas gefunden hat, was sich zu behalten lohnt. Die vollständige Klasse (die die vorherige Klasse gleichen Namens ersetzt) wird im folgenden Beispiel gezeigt.

Beispiel: Verbesserter ArticleHandler

from xml.sax.handler import ContentHandler

class ArticleHandler(ContentHandler):    
    """
    Ein Handler für Artikel in XML
    """
    inArticle = 0
    inBody = 0
    isMatch = 0
    title = ""
    body = ""
    
    def startElement(self, name, attrs):
        if name == "webArticle":
           subcat = attrs.get("subcategory", "")
           if subcat.find("tech") > -1:
             self.inArticle = 1
             self.isMatch = 1
        
        elif self.inArticle:
        if name == "header":
           self.title = attrs.get("title", "")
           if name == "body":
              self.inBody = 1
        
    def characters(self, characters):
        if self.inBody:
           if len(self.body) < 80:
             self.body += characters
           if len(self.body) > 80:
             self.body = self.body[:78] + "..."
             self.inBody = 0
          
    def endElement(self, name):
        if name == "body":
           self.inBody = 0

Verwenden der zusätzlichen Informationen

Nun, da der Handler so erweitert wurde, daß er mehr Informationen sammelt und bestimmt, ob der Artikel interessant ist, können wir ein wenig mehr Code zu art.py hinzufügen, damit es bei einem interessanten Artikel einen Bericht für den Benutzer ausgibt und alles andere ignoriert. Dazu müssen wir diesen Code am Ende von art.py anfügen, was ursprünglich im obigen Beispiel art.py gezeigt wurde:

if ch.isMatch:
    print "News!"
    print "Titel:", ch.title
    print "Text:", ch.body

Mit article.xml als Eingabe sollten Sie die folgende Ausgabe sehen:

$> python art.py < article.xml
News!
Titel: NASA Builds Warp Drive
Text: Seattle, WA - Today an anonymous individual announced that NASA has com...

  

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