Erzeugen eines XML-Datenlagers

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

Die dritte Komponente der in diesem Kapitel entwickelten verteilten Anwendung ist ein »XML-Angebots-Datenlager«. Dies ist ein inhaltsbasiertes Lager von »Sonderangeboten«, die Händler ihren Kunden oft anbieten. In dieser imaginären Anwendung enthält das Angebotslager verschiedene Angebote, auf die man über das Netzwerk zugreifen kann. Verschiedene verteilte Anwendungen können aus unterschiedlichen Gründen auf diese Angebote zugreifen.

Eine webbasierte Anwendung könnte z. B. dynamisch auf Angebote zugreifen wollen, um sie den Kunden je nach deren Einkäufen oder Ausgabegewohnheiten anzubieten. Die Webanwendung wendet ein Stylesheet auf die Angebotsinformationen an, bevor sie auf einer Webseite angezeigt werden. Ebenso wäre es möglich, daß eine Intranet-Anwendung auf die Angebote zugreift, um sie zu ändern oder zu aktualisieren.

Zu diesem Zweck erstellen wir in diesem Abschnitt ein XML-Angebotslager. Im folgenden Abschnitt schreiben wir eine weitere Python-Klasse ähnlich zu CustomerProfile, die es uns ermöglicht, dieses XML-Lager recht transparent über das Netzwerk anzusprechen.

Eine große XML-Datei

Das Angebotslager besteht aus einem großen XML-Dokument auf der Festplatte. Die zuvor erwähnte Zugriffskomponente, die später entwickelt wird, wird diese Datei traversieren und dem Aufrufer die korrekten Angebote liefern. Die Grundstruktur eines Angebotes in Form von leeren Elementen ist folgende:

<offer>
  <id/>
  <internal-name/>
  <heading/>
  <description/>
  <discount/>
  <discount-type/>
  <expires/>
  <disclaimer/>
</offer>

Ein erstes vollständiges XML-Lager mit nur zwei Angeboten wird im folgenden Beispiel vorgestellt. Wenn Ihnen danach ist, fügen Sie ruhig Ihre eigenen hinzu.

Beispiel: OfferXMLStore.xml

<?xml version="1.0" encoding="iso-8859-1"?>
<OfferXMLStore>
  <offer>
    <id>9908d093j4p3j33</id>
    <internal-name>RabattMehrAls1000</internal-name>
    <heading>20% auf alle Bestellungen über EUR 1000,00</heading>
    <description>Als Anreiz für Sie, noch mehr zu kaufen, bieten wir 20% Mengenrabatt auf alle Ihre Bestellungen über EUR 1000,-! Wir bieten Ihnen diesen riesigen Rabatt, weil Sie so ein wichtiger Kunde für uns sind. So sehr lieben wir Sie!</description>
    <discount>20</discount>
    <discount-type>prozent</discount-type>
    <expires>2002-11-21</expires>
    <disclaimer>Dieser Rabatt unterliegt bestimmten Einschränkungen. Die Ware darf nicht in irgendeiner anderen Weise bereits reduziert sein. Der Rabatt kann in einigen Fällen nicht gewährt werden, wenn wir denken, dass Sie das Produkt auch ohne Rabatt kaufen würden. Wir können den Rabatt jederzeit wieder zurücknehmen, leider auch nach Ihrer Bestellung.</disclaimer>
  </offer>
  <offer>
    <id>222833fgjQZ3j30</id>
    <internal-name>Ramsch</internal-name>
    <heading>20% auf alle Ramsch-Produkte</heading>
    <description>Zur Reduzierung unserer anscheinend unverkäuflichen Ware gewähren wir einen 20%-Abschlag auf alle als Ramsch ausgezeichneten Produkte! Wir bieten Ihnen diesen Rabatt, weil Sie so ein wichtiger Kunde für uns sind. So sehr lieben wir Sie!</description>
    <discount>20</discount>
    <discount-type>prozent</discount-type>
    <expires>2002-11-21</expires>
    <disclaimer>Dieser Rabatt unterliegt bestimmten Einschränkungen. Die Ware muss als Ramsch ausgezeichnet sein. In einigen Fällen können als Ramsch markierte Produkte dieses Etikett im Moment des Kaufens verlieren. Falls Ihnen das passieren sollte, wird leider der vollständige Kaufpreis fällig.</disclaimer>
  </offer>
</OfferXMLStore>

Diese zwei Angebote reichen für den Anfang. OfferXMLStore ist lediglich eine große XML-Datei auf der Festplatte. Je größer die Datei wird, desto mehr nimmt der Overhead hinsichtlich des Parsens und der Bearbeitung der Datei zu. Ab einem gewissen Punkt ist es besser, OfferXMLStore in einer Datenbank unterzubringen. Obwohl die XML-Darstellung der Daten essentiell ist, ist die Verwendung einer echten Datenbank als physischen Speicher auf der Platte wesentlich effizienter als die Verarbeitung einer großen Textdatei. XML spielt seine größten Trümpfe als Dokumentformat und »Klebesprache« aus. Mit anderen Worten: Ein XML-Dokument mit einem Gigabyte an Informationen ist nicht das, was Sie wollen, wohl aber ein Gigabyte an XML-Dokumenten in Ihrer Datenbank. Aufgrund der Flexibilität des XML-Zugriffsobjekts, das im nächsten Abschnitt erstellt wird, ist es einfach, den zugrundeliegenden Speichermechanismus der XML-Angebote zu ändern, ohne daß die XML-Information selbst betroffen wäre, die rein- und rausgeht.

Erzeugen eines XML-Zugriffsobjekts

In diesem Abschnitt erzeugen wir eine weitere Komponente für den Zugriff auf Daten im XML-Lager. Diese Art von Objekt ist praktisch, wenn es darum geht, eine Datenquelle von ihrem Benutzer abzukapseln. Das wurde schon zuvor beim CustomerProfile-Objekt getan, welches die ODBC-Datenquelle hinter einer XML-Maske versteckte. Die XMLOffer-Klasse enthält mehrere Methoden zum Speichern, Suchen und Modifizieren von Angeboten im XML-Lager.

Die Schnittstellen

Die Schnittstellen der Klasse XMLOffer sind denjenigen von CustomerProfile sehr ähnlich, sind allerdings um die Methode getAllOffers erweitert. Diese Methoden dienen dazu, es Netzwerkanwendungen zu ermöglichen, mit dem XML-Lager ausschließlich über XML zu interagieren, ohne wissen zu müssen oder sich darum zu kümmern, welche Art von Speichermechanismus dem Lager zugrundeliegt. Tatsächlich könnten Sie bei gleichbleibender Schnittstelle, aber mit einer anderen Implementierung die XMLOffer-Klasse so ändern, daß sie mit einer Datenbank kommuniziert, statt mit einer XML-Datei. Diese Änderung ist völlig transparent für die Clients, die davon überhaupt nichts mitbekommen würden.

getOffer(id)
Diese Methode erwartet einen ID-String und gibt das entsprechende XML-Angebot aus dem Lager als einfaches XML in einem String zurück.

getOfferAsDomElement(id)
Wie Sie vielleicht erwarten, arbeitet diese Methode auf identische Weise wie die analoge Methode in CustomerProfile und gibt ein DOM-Element-Objekt statt eines Strings zurück.

getAllOffers( )
Diese Methode liefert das gesamte OfferXMLStore als String.

insertOffer(strXML)
Diese Methode erwartet ein Stück XML als Argument und trägt es im XML-Lager ein.

updateOffer(strXML)
Ähnlich zu CustomerProfile.updateProfile, nimmt diese Methode ein Angebot als XML an, löscht das entsprechende Angebot aus dem Lager und fügt dieses hinzu.

deleteOffer(id)
Wie Sie sich denken können, nimmt diese Methode eine ID als Argument und entfernt das entsprechende Angebot aus dem XML-Lager.

Diese Schnittstellen sollen es Anwendungen im Netzwerk so einfach wie möglich machen, mit XML-Angeboten zu arbeiten.

Diese Klasse wird zusammen mit CustomerProfile im XML-Switch untergebracht werden. Natürlich könnten sie leicht hinter einem Webserver oder CORBA-Server plaziert werden, aber um das Ganze abzukürzen, stehen sie dem XML-Switch als importierbare Klassen zur Verfügung.

Verwenden der XMLOffer-Klasse

Die Verwendung der XMLOffer-Klasse ist einfach. Vorausgesetzt, Ihnen steht die Datei OfferXMLStore.xml auf der Platte zur Verfügung (gezeigt im Beispiel OfferXMLStore.xml), können Sie wie folgt anfangen, die XMLOffer-Klasse zu benutzen:

from XMLOffer import XMLOffer
xo = XMLOffer( )
print xo.getOffer('9908d093j4p3j33')

Das Ergebnis ist ein Angebot mit einem übereinstimmenden ID-Kindelement. Umgekehrt, wenn Ihre Website Ihnen ein Angebot zuschickt oder Ihre GUI-Anwendung den XML-Inhalt eines Textfelds als String übergibt, könnten Sie die Methode insertOffer verwenden:

 xo.insertOffer(strXMLOffer) 

Die Schnittstellen sind wirklich alle sehr einfach.

Erzeugen der XMLOffer-Klasse

Die XMLOffer-Klasse ist einfach, aber ihre Implementierung hängt sehr stark von DOM und XPath ab. Wie bei CustomerProfile liefert auch die XMLOffer-Klasse normalerweise nach jedem Methodenaufruf eine 1 oder 0 zurück. Die Ausnahme bilden natürlich jene Methoden, die XML zurückgeben.

Um zu verstehen, wie die XMLOffer-Klasse eine Hülle von Hilfsfunktionen um das große OfferXMLStore bildet, z. B. get, insert, update oder delete, muß man verstehen, wie man XML auf viele verschiedene Arten bearbeiten kann. Die Implementierung der Klasse XMLOffer illustriert den Gebrauch von DOM und XPath.

Suchmethoden

Zum Abrufen eines Angebots akzeptiert die Klasse einen ID-String vom Aufrufer und benutzt XPath, um das Angebot mit der entsprechenden ID im Lager zu finden:

offerdoc = FromXmlStream("OfferXMLStore.xml")
offer = Evaluate("offer[id='" + strId + "']",
                offerdoc.documentElement)

Mit XPath wird nach dem angegebenen ID-String (strId) in offer-Elementen im XML-Lager gesucht. Wenn ein Ziel gefunden wird, wird es als offer-Element zurückgegeben. Wenn Sie einen Elementknoten (offer) angefordert haben, könnten Sie getOfferAs DomElement aufrufen, während Sie bei einem String getOffer aufrufen:

if dom:
   return offer[0]
else:
   strXML = StringIO.StringIO( )
   PrettyPrint(offer[0], strXML)
   return strXML.getvalue( )

Natürlich funktioniert getOfferAsDomElement genauso wie getProfile in der Klasse CustomerProfile. Diese Methode ruft einfach ihre verwandte Methode mit dem kürzeren Namen mit einem optionalen Parameter auf und gibt an, daß ein Knoten statt eines Strings zurückgegeben werden soll.

Die Methode getAllOffers benutzt einen einfachen direkten Ansatz, um das XML-Lager zu liefern – sie gibt Ihnen einfach die gesamte Datei als String zurück.

# Öffne Angebotsdatei
fd = open("OfferXMLStore.xml", "r")
doc = fd.read( )
fd.close( )

# Gib großen String zurück
return doc

Bearbeitungsmethoden

Für die Bearbeitung und die Verwaltung von Angeboten im XML-Lager existieren mehrere Methoden. Die Methode insertOffer ermöglicht es Ihnen, neue Angebote im Lager aufzunehmen. Die Methoden updateOffer und deleteOffer ermöglichen eine weitere Verwaltung.

Die Methode insertOffer erzeugt eine DOM-Instanz aus dem eingegangenen XML, um dessen Wohlgeformtheit zu prüfen (und potentiell auch dessen Gültigkeit, wenn Sie diesen Aufwand unternehmen). Sie wird in einen String konvertiert und durch das End-Tag des OfferXMLStore ausgetauscht. Dies ist ein schneller und einfacher Weg, das neue Element zum Dokument hinzuzufügen. Sie können mit Strings arbeiten, weil das eingegangene XML zuerst eine DOM-Instanz war und in diesem Stadium validiert werden konnte.

Die Methode updateOffer extrahiert die ID und führt dann eine Lösch- und Einfüge-Operation aus. Die Methode deleteOffer extrahiert eine ID und entfernt dann den Knoten von einer DOM-Instanz:

try:   
   targetNode = Evaluate("offer[id=\"" + strId + "\"]",
                   xmlstore.documentElement)

except:
   print "XPath-Auswertung fehlerhaft."
   return 0

# Benutze Node.removeChild(XPathResult)
try:
   xmlstore.documentElement.removeChild(targetNode[0])
except:
   # Entweder es existierte nicht, oder
   # der XPath-Aufruf hat nichts ergeben...
   return 0

XPath wird dazu benutzt, die spezifische ID zu bestimmen, und der Evaluate-Aufruf gibt den eigentlichen Knoten zurück. Dieser Knoten wird dann weitergegeben an die Methode removeChild() des Knotens documentElement. Der Rest des Codes schreibt schließlich die Datei zurück auf die Platte. Das folgende Beispiel zeigt XMLOffer.py.

Beispiel: XMLOffer.py

"""
XMLOffer.py
"""
import StringIO
from xml.dom.ext.reader.Sax2 import FromXmlStream
from xml.dom.ext.reader.Sax2 import FromXml
from xml.dom.ext import PrettyPrint
from xml.xpath import Evaluate
class XMLOffer:  

   def getOffer(self, strId, dom=0):
      """
      getOffer nimmt eine ID als Parameter und liefert das entsprechende Angebot aus dem
      XML-Datenlager als XML-String oder als DOM, falls der dritte Parameter gesetzt wurde.
      """
      
      # Erzeuge Dokument aus dem Datenlager   
      offerdoc = FromXmlStream("OfferXMLStore.xml")
      
      # Benutze XPath, um mit Hilfe von Kind-ID-Zeichendaten
      # auf ein spezielles Angebotselement zu verweisen
      offer = Evaluate("offer[id='" + strId + "']",
                    offerdoc.documentElement)
   
      # Entscheide, welche Version zurückgegeben wird, DOM oder String
      if dom:
      # Liefere Angebotselement
      return offer[0]
      else:
      # Konvertiere in String
      strXML = StringIO.StringIO()
      PrettyPrint(offer[0], strXML)
      return strXML.getvalue()
   
   def getOfferAsDomElement(self, strId):
      """
      getOfferAsDomElement funktioniert genauso wie getOffer,
      liefert jedoch eine DOM-Elementinstanz, statt eines
      Strings. Diese Methode ruft einfach getOffer mit einem
      auf 1 gesetzten dom-Schalter auf (der dritte Parameter).
      """
      return self.getOffer(strId, 1)
   
   def getAllOffers(self):
      """
      getAllOffers liefert das gesamte Datenlager
      als String.
      """
      # Öffne Angebotsdatei
      fd = open("OfferXMLStore.xml", "r")
      doc = fd.read()
      fd.close()
      # Gib großen String zurück
      return doc
   
   def insertOffer(self, strOfferXML):
      """
      insertOffer nimmt einen XML-String und fügt
      ihn zum XML-Datenlager hinzu.
      """
      if not strOfferXML:
      return None
      
      # Erzeuge DOM aus Eingabedaten
      newoffer = FromXml(strOfferXML)
   
      #----
      # optional: hier könnten Sie mit Ihrem neuen DOM-Objekt und offer.dtd validieren;
      # siehe Kap. 7 für Details darüber, wie man xmlproc zur Validierung benutzt...
      #----
      
      # Schütte DOM in einen String aus
      newXmlOffer = StringIO.StringIO()
      PrettyPrint(newoffer.documentElement, newXmlOffer)

      # Hole Inhalt in einen Puffer
      rd = open("OfferXMLStore.xml", "r")
      bf = rd.readlines()
      rd.close()

      # Suche und ersetze in Puffer
      wd = open("OfferXMLStore.xml", "w")
      for lp in range(len(bf)):
      if (bf[lp].rfind("</OfferXMLStore>") > -1):
      # Ersetze End-Tag des Wurzelelements mit frischem
      # Angebot und Wurzelelement-End-Tag
      bf[lp] = bf[lp].replace("</OfferXMLStore>",
      newXmlOffer.getvalue() + "</OfferXMLStore>")

      # Schreibe neuen Puffer auf Platte
      wd.writelines(bf)
      wd.close()
      
      return 1

   def deleteOffer(self, strId):
      """
      deleteOffer nimmt einen ID-String und löscht diesen
      Angebotsknoten aus dem Dokument OfferXMLStore.xml
      """
      # Lies Datenlager in ein DOM und schliesse es dann
      try:
      xmlstore = FromXmlStream("OfferXMLStore.xml")
      except:
      print "XML-Datenlager konnte nicht geöffnet werden."
      return 0

      # Benutze XPath, um den id-Knoten zurückzugeben
      # offer/[id='<id>']
      try:
      targetNode = Evaluate("offer[id=\"" + strId + "\"]",
      xmlstore.documentElement)
      except:
      print "XPath-Auswertung fehlerhaft."
      return 0

      # Benutze Node.removeChild(XPathResult)
      try:
      xmlstore.documentElement.removeChild(targetNode[0])
      except:
      # Entweder es existierte nicht, oder der XPath-Aufruf hat nichts ergeben...
      return 0
      
      # Öffne Lager erneut, zum Schreiben,
      # mache ein PrettyPrint des DOMs,
      # schließe Lager wieder
      fd = open("OfferXMLStore.xml", "w")
      PrettyPrint(xmlstore, fd)
      fd.close()
      return 1

   def updateOffer(self, strOfferXML):
      if not strOfferXML:
      return 0
      else:
      try:
      offerId = Evaluate("id/text()",
      FromXml(strOfferXML).documentElement)
      if (not self.deleteOffer(offerId[0].nodeValue)
      or not self.insertOffer(strOfferXML)):
      print "Konnte nicht löschen oder einfügen."
      return 0
      except:
      print "Konnte Angebot nicht aktualisieren."
      return 0
      
      return 1

Die Klasse XMLOffer ist einfach zu benutzen. Die nächste Komponente des verteilten Systems ist der XML-Switch, der die individuellen Nachrichten zwischen den verschiedenen Anwendungen vermittelt.

  

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