Das BillSummary-Beispiel

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

Um einige der in diesem Abschnitt vorgestellten Validierungstechniken zu kombinieren, entwickeln wir eine Beispielanwendung, die von einer DTD, der Konvertierung einer Textdatei (Flatfile) und einer XML-Validierung Gebrauch macht.

In den folgenden Programmen entwickeln wir ein Internetsystem, das eine Flatfile parst, die von einem Webbrowser stammt, den Text nach XML konvertiert, das XML validiert, es unter einer eindeutigen ID zwecks Veröffentlichung auf die Festplatte speichert und den Erfolg oder Mißerfolg an den Browser-Client (oder HTTP-Client) zurückmeldet. Ein solches Arrangement kann in einem verteilten System als HTTP-basierte Schnittstelle zur Konvertierung von Flatfiles nach XML dienen (und dazu, die resultierenden XML-Dateien über HTTP verfügbar zu machen).

Um dies zu bewerkstelligen, benutzen Sie Pythons CGI-Bibliotheken, um eine Textdatei aus einer HTTP-Anfrage herauszugreifen. Verwenden Sie String- und Datei-APIs, um die vom Browser erhaltene Textdatei zu parsen, und eine DOM-Implementierung, um – basierend auf dem Inhalt der Textdatei – ein Dokumentobjekt zu entwerfen. Ein validierender Parser wird benutzt, um zu garantieren, daß sich das konstruierte DOM gewissenhaft an die erstellte Bill Summary-DTD hält.

Alle Dateien für dieses Beispiel sind als Teil des Beispiele-Archivs verfügbar.

Die in diesem Beispiel benutzten Dateien sollten in einem CGI-fähigen Verzeichnis Ihres Webservers plaziert werden. In diesem Abschnitt erzeugen wir folgende Dateien:

flatfile.html

Erlaubt Ihnen, die Textdatei (Flatfile) mit einem Browser an das CGI-Skript zu senden. Vorher wird die Textdatei BillSummary.txt geladen.

FlatfileParser.py

Eine Klasse, die die Textdatei parst und ein DOM-Dokument zurückgibt.

ValidityError.py

Eine Klasse, die Validierungsfehler für xmlproc behandelt.

BillSummary.dtd

Eine DTD zur Validierung des konvertierten XMLs.

flat2xml.cgi

Ein CGI-Skript, das eine Textdatei annimmt, sie nach XML konvertiert, dieses anschließend validiert, dann auf die Festplatte (und damit über HTTP) »veröffentlicht« und das Ergebnis an den Browser zurückgibt.

Das CGI-Skript flat2xml.cgi ist das eigentliche Arbeitspferd, das alles zusammenhält. Es wird am Ende dieses Abschnitts ausführlich vorgestellt.

Die Flatfile-Datei

Die Textdatei (Flatfile), die wir in dieser Anwendung benutzen, ist ein Beispiel für eine Abrechnung einer fiktiven Consulting-Firma. Wie es bei einer typischen kleinen Firma vielleicht der Fall ist, hat diese besonders weitsichtige Firma eine Spreadsheet-Software für ihre Rechnungen und deren Export als Text benutzt. Unsere Aufgabe besteht darin, es zu ermöglichen, daß mit diesen Rechnungen eines Tages etwas Sinnvolles passieren kann. Ihr Ziel ist die Migration der Formulare nach XML, damit sie in Zukunft einfacher bearbeitet werden können. Sie nach XML zu konvertieren und via HTTP verfügbar zu machen ist ein guter Anfang. Der im folgenden Beispiel gezeigte Text, BillSummary.txt, wird im gesamten Abschnitt ausgiebig benutzt.

Beispiel: BillSummary.txt

#
# Bill Summary
#
Bill Summary, Format 1.2
Section: Customer
customer-id: 34287-AUHE-39383947579
name: Zeropath Corporation
address1: 123 Zeropath Street
address2:
city: Redmond
state: WA
zip: 98052
phone: 425-555-1212
billing-contact: Larry Boberry
billing-contact-phone: 425-555-1212

Section: Bill Highlights
bill-id: 3453439789-6454-77
customer: 34287-AUHE-39383947579
customer-name: Zeropath Corporation
total-hours: 80
hours-rate: 150
total-svcmtrls: 950
total-bill-amount: 12950

Section: Item
item-id: 8289893
bill-id: 3453439789-6454-77
item-name: Continued Project Work (Backend)
total-hours: 40
total-svcmtrls: 450

Section: Item
item-id: 8289894
bill-id: 3453439789-6454-77
item-name: Continued Project Work (UI)
total-hours: 40
total-svcmtrls: 500

Wenn wir diese Flatfile einmal auf Festplatte haben, können wir damit anfangen, ein Webformular zu erstellen, das sie via HTTP versendet. Wir untersuchen diese spezielle Anwendung in diesem Abschnitt.

Das Webformular

Zuerst entwickeln wir ein Webformular zum Versenden Ihrer Flatfiles zwecks Konvertierung nach XML. Wenn die Rechnungen einer Firma jeden Tag auf einer gemeinsam benutzten Festplatte als Flatfiles abgespeichert werden, kann ein Batch-Prozeß sie alle aufgreifen und via HTTP an Ihre Konvertierungsanwendung senden.

Die Wahl von HTTP als Schnittstelle läßt Kommunikationspfade für eine Vielzahl von Clients offen (z. B. Internet-Browser, HTTP-fähige Anwendungen hinter einer Firewall usw.). Man könnte textbasierte Rechnungen direkt in Browsern eingeben oder diese programmatisch einsenden, indem intelligente Clients benutzt werden, die HTTP-fähig sind.

Das Webformular ist ein einfaches HTML-Dokument, wie es das Beispiel unten zeigt. Worauf man hier besonders achten muß, ist das form-Tag und dessen Attribute method und action. Dieses Element definiert, wohin der Browser die Textdatei sendet, wenn Sie den Abschicken-Button drücken. Ein textarea-Tag wird benutzt, um den Inhalt Textdatei aufzunehmen, und der Text aus Beispiel BillSummary.txt ist dann als Vorgabe präsent, wenn Sie das Formular laden.

Beispiel: Das Webformular flatfile.html schickt Ihre Textdatei ab

<html>
<body bgcolor="#FFFFFF" text="#000000">
<h1>Auswahl der flachen Datei</h1>
<p>Klicken Sie auf den unteren Button, um die flache Datei an den Server zu schicken. Sie k&ouml;nnen die Datei auch editieren, um Fehler auf dem Server und in dem entsprechenden Code zu erzeugen.</p>
<p>
<form action="flat2xml.cgi" method="POST">
<textarea name="flatfile" rows=20 cols=80>
#
# Bill Summary
#
Bill Summary, Format 1.2
Section: Customer
customer-id: 34287-AUHE-39383947579
name: Zeropath Corporation
address1: 123 Zeropath Street
address2:
city: Redmond
state: WA
zip: 98052
phone: 425-555-1212
billing-contact: Larry Boberry
billing-contact-phone: 425-555-1212

Section: Bill Highlights
bill-id: 3453439789-6454-77
customer: 34287-AUHE-39383947579
customer-name: Zeropath Corporation
total-hours: 80
hours-rate: 150
total-svcmtrls: 950
total-bill-amount: 12950

Section: Item
item-id: 8289893
bill-id: 3453439789-6454-77
item-name: Continued Project Work (Backend)
total-hours: 40
total-svcmtrls: 450

Section: Item
item-id: 8289894
bill-id: 3453439789-6454-77
item-name: Continued Project Work (UI)
total-hours: 40
total-svcmtrls: 500

</textarea>
<br><input type="submit" value="Abschicken">
</form>
</p>
</body>
</html>

Die aus dem Code im letzten Beispiel generierte Webseite erscheint in einem Browser, wie in der folgenden Abbildung gezeigt.

Ein Webformular enthält eine Flatfile

Abbildung: Ein Webformular enthält eine Flatfile

Starten des CGI-Skripts

Nun sollten Sie über zwei Komponenten des Beispiels verfügen: ein Beispiel einer Flatfile, die eine Zusammenfassung einer Rechnung darstellt, und ein HTML-Webformular, das die Flatfile über HTTP an ein Python-Skript namens flatfile.cgi schickt, das vom action-Attribut des form-Elements definiert wird.

Bevor wir in das komplexe CGI-Skript samt Validierung eintauchen, sollten wir einfach Ihr lokales CGI-Gewässer kurz ausloten und bestätigen, daß Sie die Flatfile von Ihrem Webbrowser erfolgreich empfangen können. Das nächste Beispiel Eine erste Version von flatfile.cgi stellt einen guten Ausgangspunkt dar, um die Ausführung des CGI-Skripts und die Verbindung zum Browser zu testen. Ihr CGI-Skript muß sich die Textdatei aus der HTTP-Anfrage schnappen und sie an den Benutzer zurückschicken, um damit zu zeigen, daß alles richtig funktioniert. XML und Validierung kommen danach. Der Kern des CGI-Skripts sollte ein wenig wie die frühe Version von flat2xml.cgi aussehen, die im Beispiel unten gezeigt wird.

Beispiel: Eine erste Version von flatfile.cgi

#!/usr/local/bin/python
# flat2xml.cgi

import cgi
import os
import sys

#
# Starte HTTP/HTML-Ausgabe
#
print "Content-type: text/html"
print
print "<html><body>"

# Parse Abfrage-String nach der Flatfile
#
try:
query = cgi.FieldStorage( )
flatfile = query.getvalue("flatfile", "")[0]
except:
print "Konvertierungsanfrage nicht gefunden oder falsch formatiert."
print "</body></html>"
sys.exit(0)

#
# Zeige Flatfile an
#
print "<h1>Flache Datei</h1>"
print "<p>Empfangene flache Datei:</p> "
print "<p><pre>" + flatfile + "</pre></p>"
print "</body></html>"

Das meiste in diesem Beispiel ist ziemlich einfacher Python-Code. Der Inhalt der Flatfile-Datei wird vom Browser in Form einer GET-Anfrage als Variable flatfile im Formular gesendet. Sollte sie nicht verfügbar sein, tritt ein Fehler auf. Wenn Beispiel Eine erste Version von flatfile.cgi korrekt funktioniert, sollten Sie einen Bildschirm ähnlich dem in der folgenden Abbildung sehen.

Grundfunktionalität des CGI-Skripts

Abbildung: Grundfunktionalität des CGI-Skripts

In den folgenden Abschnitten bauen wir auf dieser Grundfunktionalität auf und ergänzen sie um die Konvertierung der Flatfile nach XML. Es folgt eine Validierung von XML, damit sicher ist, daß alles nach Plan läuft.

Konvertierung und Validierung

Um die Flatfile nach XML zu konvertieren, müssen wir sie zuerst parsen. Obwohl es langweilig erscheinen mag, ein einzigartiges Format von Flatfiles zu parsen, kommen wir um diesen Aspekt der XML-Integration nicht herum. Tatsächlich ist diese Langeweile, alle Strukturen von Flatfiles auch dann parsen zu müssen, wenn sie sich nur minimal unterscheiden, vielleicht die Haupttriebkraft hinter XML. XML erzwingt ein Format, das von allen Anwendungen leicht geparst werden kann. Die gute Nachricht ist die, daß die Flatfiles im Prinzip in jedem System die gleiche Struktur annehmen sollten und Sie nur einen einzigen Satz von Parsing-Routinen für jeden Typ von Flatfiles schreiben müssen, der Ihnen begegnet.

Konvertieren von Text nach XML

Die in diesem Beispiel verwendete Flatfile-Datei ist in Abschnitte aufgeteilt, mit zusätzlichen Daten pro Abschnitt. Wir können nicht völlig sicher sein, wie viele Abschnitte letztendlich im Dokument vorkommen werden, da diese Zahl von der Anzahl der »Beratungsstunden« abhängt, die auf die Rechnung des Kunden gesetzt werden. Der FlatfileParser, den Sie erzeugen, muß daher flexibel sein und darf keine starre Reihenfolge von Abschnitten im Dokument voraussetzen oder Vermutungen über ihre Eindeutigkeit oder Gruppierung in anderen Abschnitten anstellen. Um sein Ziel zu erfüllen und aus Flatfiles XML-Dokumente zu erstellen, benutzt der FlatfileParser die DOM-implementation, um eine DOM-Struktur zu erzeugen, die all die verschiedenen Textstücke aufnimmt, die der FlatfileParser extrahiert:

#
# FlatfileParser.py
#

from xml.dom    import implementation
class FlatfileParser:
   def parseFile(self, fileAsString):

Die Klasse FlatfileParser hat eine Methode namens parseFile. Diese erwartet einen einzigen String, der den Inhalt einer Datei darstellt. Python bietet auch die Klasse StringIO, mit der ein String wie eine Datei über Lese- und Schreiboperationen verfügt. StringIO ist eine gute Wahl für diese Klasse, aber um die Dinge einfach zu halten, arbeiten wir in diesem Beispiel mit einem kompletten String.

Die nächsten paar Schritte sind entscheidend. Dabei erzeugen wir ein neues DOM-Dokument namens BillSummary und erhalten dessen Wurzelelement. Wir fügen diesem Element Kinder hinzu, während sich unser FlatfileParser durch den Flatfile-Text arbeitet:

# Erzeuge DocType-Deklaration
doctype = implementation.createDocumentType('BillSummary', '',
                                                   'BillSummary.dtd')

# Erzeuge leeres DOM-Dokument und hole Wurzelelement
doc = implementation.createDocument('', 'BillSummary', doctype)
elemDoc = doc.documentElement

Die implementation-Klasse wird als xml.dom.implementation importiert, und ihre Methode createDocumentType wird verwendet, um einen BillSummary-Typ zu bauen, der eine Datei namens BillSummary.dtd referenziert (siehe Beispiel BillSummary.dtd). Ein Dokumentobjekt wird mit der Methode createDocument erzeugt, bei der der frisch erzeugte doctype als Parameter angegeben wird. Schließlich erhalten wir das Wurzelelement des Dokuments mit der Methode doc.documentElement.

Nun, da die Grundlage für das Dokument gelegt ist, kann der Code über die Zeilen in der Datei iterieren und deren Inhalt untersuchen. Neue Abschnitte resultieren in neuen Abschnittselementen, und die Daten in diesen Elementen resultieren in neuen Kindern der Abschnittselemente. Hier ist ein Abschnitt der Struktur der Flatfile:

Section: Bill Highlights
bill-id: 3453439789-6454-77
customer: 34287-AUHE-39383947579
customer-name: Zeropath Corporation
total-hours: 80
hours-rate: 150
total-svcmtrls: 950
total-bill-amount: 12950

In unserem FlatfileParser bewirkt ein »Section«-String, daß ein neues Element erzeugt wird. Dieses Element wird dann zum Dokument hinzugefügt und als »aktuelles« Element markiert. Alle anderen Zeilen des Dokuments wie z. B. bill-id und total-hours werden so lange als Kinder zum »aktuellen« Element hinzugefügt, bis eine neue »Section« entdeckt wird. Die erste Hälfte des Codes prüft, ob Sie es mit einem section-Element zu tun haben. Wenn dem so ist, gibt die zweite Hälfte des Codes die aktuelle Zeile in XML als Paar aus einem Element und CDATA aus:

# Lies jede Zeile der Textdatei zum Bearbeiten ein
for line in fileAsString.splitlines():
  # Teste, ob wir uns in einem Abschnitt befinden oder nicht

if bInElement:    
  # Prüfe, ob wir einen Abschnitt verlassen
  if ':' in line:
    # Füge section-Element hinzu, setze Flag zurück
    elemDoc.appendChild(elemCurrent)
    bInElement = 0
  else:
    # Parse eine Abschnittszeile auf ':'
    k,v = line.split(':')
    
    # Erzeuge Elementnamen und Kindtext aus key/value-Paar
    elem = doc.createElement(k.strip())
    elem.appendChild(doc.createTextNode(v.strip()))
    
    # Füge Element an aktuelles section-Element an
    elemCurrent.appendChild(elem)

Der Code prüft zuerst, ob eine Leerzeile vorliegt oder ein Abschnitt beendet wurde. Ist dem nicht so, wird angenommen, daß die aktuelle Zeile ein Kind des aktuellen Abschnitts ist, und sie wird am Doppelpunkt (:) aufgeteilt. Mit der Methode doc.createElement wird dann das Element mit einem Tag-Namen aus der linken Hälfte des Text-Strings erzeugt, während die Zeichendaten des Elements aus der rechten Hälfte des Text-Strings als Kind angefügt werden. Dieser Vorgang setzt sich fort, bis es keine weiteren Zeilen mehr in der Datei gibt.

Was der vorherige Code-Schnipsel nicht zeigt, ist, was passiert, wenn bInElement gleich false ist (in Python gleich null). Basierend auf der Struktur der Flatfile-Datei gilt, daß es an der Zeit ist, einen neuen Abschnitt zu eröffnen, wenn bInElement gleich false ist. Der Code sucht dann nach einem weiteren »Section«-String. Wenn einer gefunden wird, wird er zu einem Element konvertiert, dieses wird als »aktuelles« Element gesetzt, und bInElement wird auf true zurückgesetzt.

# Erzeuge neues Element abhängig davon, in welchem
# Abschnitt der Flatfile wir uns befinden...
section = line.strip()   
if section == "Section: Customer":
   elemCustomer = doc.createElement("Customer")
   bInElement = 1

   # Setze aktuelles Arbeitselement für Customer-Abschnitt
   elemCurrent = elemCustomer

if section == "Section: Bill Highlights":
   elemBillHighlights = doc.createElement("BillHighlights")
   bInElement = 1

   # Setze aktuelles Arbeitselement für BillHighlights-Abschnitt
   elemCurrent = elemBillHighlights

if section == "Section: Item":
   elemItem = doc.createElement("Item")
   bInElement = 1

   # Setze aktuelles Arbeitselement für Item-Abschnitt
   elemCurrent = elemItem

Bei jeder Zeile Code in Ihrer Datei nimmt FlatfileParser an, daß Sie sich in einem Abschnitt befinden oder es gerade mit den Kindern von Abschnitten zu tun haben. Wenn irgendwelche Daten nach einem Abschnitt und vor einem neuen Abschnitt auftauchen, werden sie ignoriert, weil Ihre Parsing-Schleifen alle Dinge danach behandeln, in welchem Abschnitt sie sich befinden. Wenn das Dokument schließlich keine weiteren Zeilen mehr enthält, wird das neue DOM-Dokument zurückgegeben:

 return doc 

Das folgende Beispiel zeigt das vollständige Listing von FlatfileParser.py. Ein später in diesem Abschnitt gezeigtes CGI-Skript verwendet die Klasse FlatfileParser.

Beispiel: FlatfileParser.py

# FlatfileParser.py
#
from xml.dom import implementation

class FlatfileParser:      
  def parseFile(self, fileAsString):

      # Erzeuge DocType-Deklaration
      doctype = implementation.createDocumentType('BillSummary', '',
                                            'BillSummary.dtd')
      
      # Erzeuge leeres DOM-Dokument und hole Wurzelelement
      doc = implementation.createDocument('', 'BillSummary', doctype)
      elemDoc = doc.documentElement
      
      # Boolesches Textparsing-Flag zum leichteren Navigieren in Flatfile
      bInElement = 0
      
      # Lies jede Zeile der Flatfile zum Bearbeiten ein
      for line in fileAsString.splitlines( ):
       # Teste, ob wir uns in einem Abschnitt befinden oder nicht
       if bInElement:
        # Prüfe, ob wir einen Abschnitt verlassen
        if ':' in line:
         # Füge section-Element hinzu, setze Flag zurück
         elemDoc.appendChild(elemCurrent)
         bInElement = 0
      else:
         # Parse eine Abschnittszeile auf ':'
         k,v = line.split(':')
      
         # Erzeuge Elementnamen und Kindtext aus key/value-Paar
         elem = doc.createElement(k.strip( ))
         elem.appendChild(doc.createTextNode(v.strip( )))   
      
         # Füge Element an aktuelles section-Element an
         elemCurrent.appendChild(elem)
      
      # Erzeuge neues Element abhängig davon, in welchem
      # Abschnitt der Flatfile wir uns befinden...
      section = line.strip( )
      if section == "Section: Customer":
         elemCustomer = doc.createElement("Customer")
         bInElement = 1
      
         # Setze aktuelles Arbeitselement für Customer-Abschnitt
         elemCurrent = elemCustomer
      
      if section == "Section: Bill Highlights":
         elemBillHighlights = doc.createElement("BillHighlights")
         bInElement = 1
      
         # Setze aktuelles Arbeitselement für BillHighlights-Abschnitt
         elemCurrent = elemBillHighlights
      
      if section == "Section: Item":
      elemItem = doc.createElement("Item")
      bInElement = 1
      
      # Setze aktuelles Arbeitselement für Item-Abschnitt
      elemCurrent = elemItem
     
  return doc

Validieren des XML-Codes

Sie fragen sich vielleicht, welches XML produziert wird, wenn FlatfileParser auf dem in Beispiel BillSummary.txt gezeigten Beispieltext ausgeführt wird. Wenn Sie FlatfileParser auf Bill-Summary.txt ausführen, sollten Sie ein DOM erhalten, das wie BillSummary.xml im folgenden Beispiel aussieht (vorausgesetzt, Sie geben Ihr DOM mit PrettyPrint oder ähnlichem aus).

Beispiel: Eine wohlgeformte, konvertierte, gültige Datei BillSummary.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE BillSummary SYSTEM "BillSummary.dtd">
<BillSummary>
  <Customer>
    <customer-id>34287-AUHE-39383947579</customer-id>
    <name>Zeropath Corporation</name>
    <address1>123 Zeropath Street</address1>
    <address2></address2>
    <city>Redmond</city>
    <state>WA</state>
    <zip>98052</zip>
    <phone>425-555-1212</phone>
    <billing-contact>Larry Boberry</billing-contact>
    <billing-contact-phone>425-555-1212</billing-contact-phone>
  </Customer>
  <BillHighlights>
    <bill-id>3453439789-6454-77</bill-id>
    <customer>34287-AUHE-39383947579</customer>
    <customer-name>Zeropath Corporation</customer-name>
    <total-hours>80</total-hours>
    <hours-rate>150</hours-rate>
    <total-svcmtrls>950</total-svcmtrls>
    <total-bill-amount>12950</total-bill-amount>
  </BillHighlights>
  <Item>
    <item-id>8289893</item-id>
    <bill-id>3453439789-6454-77</bill-id>
    <item-name>Continued Project Work (Backend)</item-name>
    <total-hours>40</total-hours>
    <total-svcmtrls>450</total-svcmtrls>
  </Item>
  <Item>
    <item-id>8289894</item-id>
    <bill-id>3453439789-6454-77</bill-id>
    <item-name>Continued Project Work (UI)</item-name>
    <total-hours>40</total-hours>
    <total-svcmtrls>500</total-svcmtrls>
  </Item>
</BillSummary>

Ein wichtiger Aspekt, der in unserem Beispiel bislang fehlt, ist die eigentliche DTD. Damit Flatfiles nach XML konvertiert werden können und damit das XML anschließend als gültig betrachtet werden kann, muß es eine DTD geben. Die DTD für das BillSummary-Dokument ist sehr einfach. Sie benutzt die Konzepte von Inhaltsmodellen und Elementanordnung, wie sie in Dokumenttyp-Definitionen erläutert wurden.

Sie müssen eine DTD als Datei BillSummary.dtd abgespeichert haben, damit die Validierung erfolgreich sein kann.

Beispiel: BillSummary.dtd

<!ELEMENT customer-id (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT address1 (#PCDATA)>
<!ELEMENT address2 (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
<!ELEMENT billing-contact (#PCDATA)>
<!ELEMENT billing-contact-phone (#PCDATA)>
<!ELEMENT bill-id (#PCDATA)>
<!ELEMENT customer (#PCDATA)>
<!ELEMENT customer-name (#PCDATA)>
<!ELEMENT hours-rate (#PCDATA)>
<!ELEMENT total-bill-amount (#PCDATA)>
<!ELEMENT item-id (#PCDATA)>
<!ELEMENT item-name (#PCDATA)>
<!ELEMENT total-hours (#PCDATA)>
<!ELEMENT total-svcmtrls (#PCDATA)>

<!ELEMENT Customer (customer-id, name, address1, address2, city, state, zip, phone, billing-contact, billing-contact-phone)>

<!ELEMENT BillHighlights (bill-id, customer, customer-name, total-hours, hours-rate, total-svcmtrls, total-bill-amount)>

<!ELEMENT Item (item-id, bill-id, item-name, total-hours, total-svcmtrls)>

<!ELEMENT BillSummary (Customer, BillHighlights, Item*)>

Wenn Sie nochmals zu val.py zurückgehen, das im Beispiel Kommandozeilen-Validierer (val.py) vorgestellt wurde, können Sie es benutzen, um die Gültigkeit Ihrer frisch erzeugten Datei BillSummary.dtd und die Ihrer Beispieldatei BillSummary.xml zu prüfen (d. h. falls Sie das Programm eingetippt oder aus FlatfileParser extrahiert haben). Wenn Sie das XML in einer gespeicherten Datei vorliegen haben, können Sie es entsprechend testen:

 C:\pythonxml\c7>python val.py BillSummary.xml 

Erzeugen eines Validierungs-Handlers

Nun, da Sie einen FlatfileParser, eine generierte XML-Version und eine DTD haben, brauchen Sie einen Validierungs-Handler für xmlproc. Die Klasse XMLValidator enthält eine Methode namens set_error_handler. Diese Methode können Sie verwenden, um XMLValidator mit einem Fehler-Handler zu versorgen, der das tut, was Sie erwarten, z. B. Fehler an Ihren HTTP/HTML-Client auszugeben.

Um den ErrorHandler zu implementieren, leiten Sie eine Klasse von seiner Schnittstelle ab und überschreiben Sie die Methoden, die Sie als Teil Ihres Fehlerschemas implementieren möchten. Ihr Fehler-Handler schreibt einfache Textnachrichten in einer HTML-Hülle. Das folgende Beispiel zeigt das komplette Listing von ValidityError.py, das ein ErrorHandler-konformes Objekt implementiert.

Beispiel: ValidityError.py

from xml.parsers.xmlproc.xmlapp import ErrorHandler

"""
ValidityErrorHandler -- implementiere ErrorHandler-Schnittstelle aus xmlproc
"""
class ValidityErrorHandler(ErrorHandler):

def warning(self,msg):   
   print "<p><b><font color=#FF0000>Warnung erhalten!:</b></font>"
   print "<br>" + msg + "</p>"
   self.errors = 0

def error(self,msg):
   print "<p><b><font color=#aa0000>Fehler erhalten!:</b></font>"
   print "<br>" + msg + "</p>"
   self.errors = 1

def fatal(self,msg):
   print "<p><b><font color=#aa0000>Kritischen Fehler erhalten!:</b></font>"
   print "<br>" + msg + "</p>"
   self.errors = 1

Alle Methoden des Fehler-Handlers erwarten eine Nachricht als Argument. Sie schreiben die Nachricht auf die Webseite und geben an, um welche Art von Fehler es sich handelt (Warnung, Fehler oder kritischer Fehler). Alle Methoden legen außerdem einen Schalter um (self.errors), damit das CGI-Skript erkennen kann, ob Fehler aufgetreten sind.

Nun sind Handler, DTD und XML-Dateien an ihrem Platz, und wir können das CGI-Skript vollenden.

Vollenden des CGI-Skripts

Als wir das letztemal mit flat2xml.cgi gearbeitet haben, hatten wir es angewiesen, die Flatfile-Datei einfach als Text zurückzugeben, um damit zu bestätigen, daß Ihre CGI-Umgebung korrekt funktioniert. Nun gehen wir noch einmal dorthin zurück und ergänzen einiges an Funktionalität. Am Ende dieses Abschnitts wird das vollständige Listing des CGI-Skripts im Beispiel flat2xml.cgi angegeben.

Definieren von Erfolgs- und Fehlerfunktionen

Das CGI-Skript ist der große Koordinator in dieser Beispielanwendung. Der Webserver startet es als Folge einer Seitenanfrage, und das Skript seinerseits importiert Module und Klassen und beginnt mit seiner eigentlichen Aufgabe. Zuerst beendet das CGI-Skript jedoch die Importe und definiert success- und failure-Funktionen:

#!/usr/local/bin/python
# flat2xml.cgi
import cgi
import os
import sys

from FlatfileParser import FlatfileParser
from xml.dom.ext import PrettyPrint
from xml.parsers.xmlproc import xmlval
from ValidityError import ValidityErrorHandler

# Fehlermeldung für Benutzer
def failure(msg):
   print "<h1>Fehler</h1>"
   print "<p><b>Text erhalten, Fehler aufgerufen:"
   print msg + "</b></p>"

# Erfolgsmeldung für Benutzer
def success(msg):
   print "<p><b>XML-Dokument erhalten, ist g&uuml;ltig und "
   print "wurde auf Platte gespeichert. "
   print "Meldung: " + msg + "</b></p>"

Die success-Funktion wird aufgerufen, nachdem das Dokument erfolgreich konvertiert, validiert und mit der Kunden-ID-Nummer als Teil des Dateinamens auf die Festplatte gespeichert wurde. Die failure-Funktion wird aufgerufen, wann immer das CGI-Skript auf einen kritischen Fehler stößt, obwohl es dem Aufrufer überlassen bleibt, das CGI-Skript zu beenden, wenn die Funktion failure zurückgibt.

Der nächste Schritt ist die Vorbereitung der HTTP/HTML-Ausgabe und der Beginn der Kommunikation zurück zum Browser. Das CGI-Skript arbeitet sich durch eine Folge von Bedingungen, die entweder darin resultieren, daß eine Nachricht erfolgreich an den Browser geschickt wird, oder darin, daß eine Nachricht über einen kritischen Fehler das Skript stoppt. Um mit dem HTML anzufangen, verwenden Sie einige print-Anweisungen:

# Starte HTTP/HTML-Ausgabe
print "Content-type: text/html"
print
print "<html>"
print "<body>"

Konvertieren von Flatfiles nach XML

Der Code zur Konvertierung von Flatfiles nach XML befindet sich vorwiegend im zuvor erzeugten FlatfileParser. Verwenden Sie die Python-CGI-API, um die Flatfile-Datei aus dem Abfrage-String zu fischen:

# Parse Abfrage-String nach der Flatfile
try:
  querys = cgi.FieldStorage( )
  flatfile = query.getvalue("flatfile", "")[0]

except:
  failure("Konvertierungsanfrage nicht gefunden oder falsch formatiert.")
  print "</body></html>"
  sys.exit(0)

# Instanziiere Parser & zeige Flatfile an
ffp = FlatfileParser( )
print "<h1>Flache Datei</h1>"
print "<p>Flache Datei erhalten:</p> "
print "<p><pre>" + flatfile + "</pre></p>"

Nach der Instanziierung von FlatfileParser benutzen Sie die Methode parseFile, um den Text nach XML zu konvertieren:

# Konvertiere Flatfile nach XML
print "<h1>Konvertierung</h1>"
BillSummaryDOM = ffp.parseFile(flatfile)
CustomerIdElement = BillSummaryDOM.getElementsByTagName("customer-id")
if CustomerIdElement:
  # Suche Customer-ID
  CustomerId = CustomerIdElement[0].firstChild.data
  print "<p>Konvertiert nach XML...</p>"
else:
  # Keine ID gefunden, boote jetzt Dokument
  failure("Customer-ID in DOM-Instanz nicht auffindbar.")
  print "</body></html>"
  sys.exit(0)

Beachten Sie die zusätzliche Logik nach dem BillSummaryDOM, mit der das Element customer-id herausgepickt wird. Bevor Sie es sich antun, das Dokument zu validieren, müssen Sie es mit einem speziellen Bezeichner auf die Platte speichern, damit es für andere Systeme, inklusive des validator, vom Webserver aus verfügbar ist. Extrahieren Sie dazu die Customer-ID-Zeichendaten aus dem Element customer-id im DOM. Wenn es vorhanden ist, wird die Ausführung fortgesetzt; wenn es fehlt, tritt ein kritischer Fehler ein, und das Skript wird beendet.

Validieren des konvertierten XML-Codes

Vorausgesetzt das Dokument war wohlgeformt genug, um die ID zu extrahieren, können Sie mit der Validierung beginnen. Die Validierung gibt der Anwendung die Garantie, daß das Dokument sich an Standards hält und im System keine Probleme verursachen wird. Unter der Annahme, daß die ID gefunden wird, können Sie zur Validierung übergehen, zu der es gehört, das Dokument auf die Festplatte zu speichern, wie hier gezeigt:

# Validiere das DOM
print "<h1>Validierung</h1>"
try:   
   # Schreibe Dokument auf Platte basierend auf Customer-ID
   fd = open(CustomerId + ".xml", 'w')
   PrettyPrint(BillSummaryDOM, fd)
   fd.close( )

except:
   # Problem beim Schreiben des Dokuments?
   failure("<p>XML-Dokument nicht speicherbar.</p>")
   print "</body></html>"
   sys.exit(1)

Die Funktion PrettyPrint wird dazu benutzt, das DOM in den Dateideskriptor zu schreiben. Sie steht deshalb in einem try/except-Block, damit alle Probleme mit Datei-I/O abgefangen werden, noch bevor sie den Weg aus dem Skript heraus finden und eine sichtbare Nachricht »Internal Server Error« auf dem Browser hinterlassen. Anschließend folgt die eigentliche Instanziierung von XMLValidator:

# Instanziiere Parser
xv = xmlval.XMLValidator( )

# Instanziiere Fehler-Handler
veh = ValidityErrorHandler(xv.app.locator)

# Setze Parser auf, rufe parse-Methode auf
xv.set_error_handler(veh)
xv.parse_resource(CustomerId + ".xml")

Wenn während der einzelnen Validierungsschritte irgendwelche Fehler auftreten, präsentiert Ihr eigener Fehler-Handler, ValidityErrorHandler, dem Browser die Fehler und fährt mit der Ausführung fort.

Anzeigen des XML-Codes

Unabhängig von Validierungsfehlern, zeigt das CGI-Skript den XML-Code im Browser auf der HTML-Seite mit Hilfe der Tags <pre> und <xmp> an. Falls Sie damit nicht vertraut sind: Das pre-Tag weist den Browser an, den nachfolgenden Text als vorformatierten Text anzuzeigen und dabei den Leerraum beizubehalten. Für Code-Schnipsel eignet sich dieses Tag sehr gut. Aber XML-Code kann Probleme bereiten, da der Browser ihn mit HTML-Tags verwechseln kann, die er nicht unterstützt. An dieser Stelle kommt das xmp-Tag sehr gelegen – ursprünglich für HTML-Beispielcode gedacht, funktioniert es so, daß der gesamte Text zwischen den Zeichen »<« und »>« geschützt wird, wie hier gezeigt:

# Zeige XML-Dokument an
print "<h1>XML-Dokument</h1><pre><xmp>"
PrettyPrint(BillSummaryDOM)
print "</xmp></pre>"

Um sicher zu sein, daß das DOM im HTTP-Strom als rohes XML angezeigt wird, verwenden Sie die aus dem Modul xml.dom.ext importierte PrettyPrint-Funktion. Diese »druckt« das XML in jedes dateiähnliche Objekt, das Sie angeben; wenn jedoch über das DOM hinaus nichts angegeben wird, wird davon ausgegangen, daß sys.stdout das dateiähnliche Objekt ist.

Wenn während der Ausführung Ihres Skripts keine Validierungsfehler auftreten, kommt es zu einem erfolgreichen Ende. Wenn sich jedoch aus irgendeinem Grund Validierungsfehler anhäufen, schlägt das Skript fehl, selbst wenn Ihre Flatfile-Datei und Ihr XML es über alle anderen Hürden schaffen sollten. Denken Sie daran, daß der ValidityErrorHandler mit einem Fehlerschalter so konfiguriert war, daß externe Objekte seine Erfolgsrate verfolgen konnten. Das CGI-Skript benutzt diesen Fehlerschalter, um zu bestimmen, ob es irgendwelche Validierungsfehler gibt:

# Gib Benutzer eine Bestätigung
if veh.errors:
  failure("Validierungsfehler.")
else:
  success("Erfolg.")

# Beenden
print "</body></html>"

Wenn sich irgendwelche Validierungsfehler im ValidityErrorHandler verstecken, ruft das Skript die failure-Funktion auf. Das folgende Beispiel zeigt die komplette Version von flat2xml.cgi.

Beispiel: flat2xml.cgi

#!/usr/local/bin/python
# flat2xml.cgi
import cgi
import os
import sys
from FlatfileParser import FlatfileParser
from xml.dom.ext import PrettyPrint
from xml.parsers.xmlproc import xmlval
from ValidityError import ValidityErrorHandler

# Fehlermeldung für Benutzer
def failure(msg):   
   print "<h1>Fehler</h1>"
   print "<p><b>Text erhalten, Fehler aufgerufen:"
   print msg + "</b></p>"

# Erfolgsmeldung für Benutzer
def success(msg):
   print "<p><b>XML-Dokument erhalten, ist g&uuml;ltig und "
   print "wurde auf Platte gespeichert. "
   print "Meldung: " + msg + "</b></p>"

# Starte HTTP/HTML-Ausgabe
print "Content-type: text/html"
print
print "<html>"
print "<body>"

# Parse Abfrage-String nach der Flatfile
try:
   query = cgi.FieldStorage( )
   flatfile = query.getvalue("flatfile", "")
except:
   failure("Konvertierungsanfrage nicht gefunden oder falsch formatiert.")
   print "</body></html>"
   sys.exit(0)

# Instanziiere Parser & zeige Flatfile an
ffp = FlatfileParser( )
print "<h1>Flache Datei</h1>"
print "<p>Flache Datei erhalten:</p> "
print "<p><pre>" + flatfile + "</pre></p>"

# Konvertiere Flatfile nach XML
print "<h1>Konvertierung</h1>"
BillSummaryDOM = ffp.parseFile(flatfile)
CustomerIdElement = BillSummaryDOM.getElementsByTagName("customer-id")
if CustomerIdElement:
   # Suche Customer-ID
   CustomerId = CustomerIdElement[0].firstChild.data
   print "<p>Konvertiert nach XML...</p>"
else:
   # Keine ID gefunden, boote jetzt Dokument
   failure("Customer-ID in DOM-Instanz nicht auffindbar.")
   print "</body></html>"
   sys.exit(0)

# Validiere das DOM
print "<h1>Validierung</h1>"
try:
   # Schreibe Dokument auf Platte basierend auf Customer-ID
   fd = open(CustomerId + ".xml", 'w')
   PrettyPrint(BillSummaryDOM, fd)
   fd.close( )
except:
   # Problem beim Schreiben des Dokuments?
   failure("<p>XML-Dokument nicht speicherbar.</p>")
   print "</body></html>"
   sys.exit(1)

# Instanziiere Parser
xv = xmlval.XMLValidator( )

# Instanziiere Fehler-Handler
veh = ValidityErrorHandler(xv.app.locator)

# Setze Parser auf, rufe parse-Methode auf
xv.set_error_handler(veh)
xv.parse_resource(CustomerId + ".xml")

# Zeige XML-Dokument an
print "<h1>XML-Dokument</h1><pre><xmp>"
PrettyPrint(BillSummaryDOM)
print "</xmp></pre>"

# Gib Benutzer eine Bestätigung
if hasattr(veh, "errors") and veh.errors:
   failure("Validierungsfehler.")
else:
   success("Erfolg.")

# Beenden
print "</body></html>"

Ausführen der Anwendung im Browser

Das CGI-Skript kann eine Vielzahl verschiedener Resultate haben, je nachdem, ob Sie den Text editieren, bevor Sie ihn abschicken. Die Flatfile als solche ist in der Textbox wohlgeformt, wenn Sie flatfile.html laden. Sie produziert eine HTML-Ausgabe, wie in der nächsten Abbildung gezeigt.

Eine erfolgreiche Ausführung von flat2xml.cgi

Abbildung: Eine erfolgreiche Ausführung von flat2xml.cgi

Wenn Sie den Text jedoch editieren würden, bevor Sie ihn abschicken, würden Sie ganz andere Resultate sehen. Wenn Sie z. B. die Hälfte des Abschnitts »Bill Highlights« entfernen und in den ersten Abschnitt einfügen, erzeugen Sie eine Unmenge von Validierungsfehlern, wie in der folgenden Abbildung gezeigt.

Ausführung von flat2xml.cgi mit vielen Validierungsfehlern

Abbildung: Ausführung von flat2xml.cgi mit vielen Validierungsfehlern

Die Fehler, die Sie gerade ausgelöst haben, wurden durch das Entfernen beliebig großer Mengen von Inhalt verursacht. Das CGI-Skript hat die Probleme dadurch aufgefangen, daß es eine Validierung vornahm. Die in obiger Abbildung gezeigten Fehler illustrieren ganz klar die Validierungsfehler im Dokument und können so ausgelöst werden, daß Ereignisse in anderen Teilen Ihres Systems auftreten. In einer realen Situation hätten Sie eine bestimmte Geschäftslogik, die vorschreiben würde, was zu tun wäre, wenn in den Daten ein Stück fehlt. In Ihrem Fall ist es vielleicht nicht so schlimm, wenn ein Dokument gut aussieht, aber ein Datumsfeld nicht enthält. Wenn im Dokument jedoch eine Kontonummer fehlt, könnte es ein kritischer Fehler sein, der nach einer ganz anderen Vorgehensweise verlangt. Der Grad an erzwungener Logik mag etwas mit der Komplexität des Dokuments zu tun haben, mit dem Sie arbeiten. Dieses gesamte Beispiel basiert auf einem XML-Dokument, das von Null auf erzeugt wurde. Wenn Sie Dokumente ganz von Grund auf erzeugen, können Sie diese für Ihre ganz bestimmte Situation maßschneidern (vielleicht sogar zu sehr). Wenn Sie sich für einen standardisierten Dialekt entscheiden, gibt es sogar noch mehr zwingende Erwägungen und Anforderungen an Ihre Dokumente, und Ihre Transaktionen müssen sich eventuell entlang einer von anderen bestimmten Geschäftslogik bewegen.

  

zum Seitenanfang

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