Erstellen der Profil-Zugangsklasse

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

Zur Profiles-Datenbank kommt die Python-Klasse CustomerProfile hinzu. Objekte hiervon erledigen die XML-Ein- und -Ausgabe mit der Datenbank und verfügen über Methoden, die der XML-Switch verwenden kann. Die grundlegenden Aktionen bei Profilen sind, diese zu holen, einzufügen, zu aktualisieren und zu löschen. Die Argumente zu diesen Methoden sind XML-Versionen von Profildaten. Dadurch daß die Kundenprofil-Pakete in XML sind, ist es für andere Anwendungen einfach, diese Pakete zu erzeugen und zu konsumieren, ohne sich für die Struktur der Datenbank zu interessieren oder sogar zu wissen, wie man sie direkt anspricht. Tatsächlich könnte ein CustomerProfile sehr einfach zur Nutzlast einer SOAP-Nachricht werden. Ein Profilpaket sieht wie folgt aus:

<CustomerProfile id="555-99JKK39">
  <firstname>John</firstname>
  <lastname>Doolittle</lastname>
  <address1>396 Evergreen Terrace</address1>
  <address2/>
  <city>Springfield</city>
  <state>WA</state>
  <zip>98072</zip>
</CustomerProfile>

Beachten Sie, daß das address2-Element existiert, obwohl es leer ist. Die DTD für ein solches Dokument sieht folgendermaßen aus:

<!ELEMENT firstname (#PCDATA)>
<!ELEMENT lastname (#PCDATA)>
<!ELEMENT address1 (#PCDATA)>
<!ELEMENT address2 (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ATTLIST CustomerProfile id CDATA #REQUIRED>
<!ELEMENT CustomerProfile (firstname, lastname, address1, address2, city, state, zip)>

Eine Dokument-Instanz, die diese DTD verwendet, muß auch über eine Deklaration im Dokument verfügen:

<?xml version="1.0"?>
<!DOCTYPE CustomerProfile SYSTEM "CustomerProfile.dtd">
<CustomerProfile id="555-99JKK39">
    <firstname>John</firstname>

Um im Rahmen dieses Abschnitts zu bleiben, gehört die Einhaltung der DTD nicht zu den Aufgaben der Klasse CustomerProfile in Python, obwohl die DTD zum Dokument gehört und später vielleicht benutzt werden kann. Beim Einbetten von CustomerProfile-Elementen in eine XML-Nachricht wird der Prolog weggelassen, und nur das Element CustomerProfile wird in die XML-Nachricht eingefügt.

Die Schnittstellen

Die CustomerProfile-Klasse unterstützt vier unterschiedliche Operationen, nämlich Suchen, Einfügen, Aktualisieren und Löschen. Diese Klasse wird vom XML-Switch dazu benutzt, um zur Laufzeit das Einfügen und Suchen von Kundenprofilen im verteilten System zu unterstützen. Die gesamte Kommunikation von und zu dieser Klasse findet in Form von XML statt – das erlaubt eine größere Flexibilität dabei, wie die Daten im Backend gespeichert sind. Es verringert außerdem die Notwendigkeit für verteilte Anwendungen, sich direkt mit der Datenbank verbinden und die Struktur ihrer Tabellen verstehen zu müssen. In diesem Szenario müssen verteilte Anwendungen lediglich die Struktur eines CustomerProfile-Dokuments verstehen.

getProfile(id)
Diese Methode erwartet eine Kundenprofil-ID und liefert die entsprechenden Informationen als wohlgeformtes, gültiges CustomerProfile-Dokument in Form eines Strings.

getProfileAsDom(id)
Diese Methode ist identisch mit getProfile, außer daß der Rückgabewert kein XML-String, sondern eine DOM-Instanz ist. Mit DOM kann diese dann weiter bearbeitet werden.

insertProfile(strXML)
Diese Methode erwartet ein gültiges, wohlgeformtes CustomerProfile-Dokument in Form eines Strings und fügt es in die Datenbank ein.

updateProfile(strXML)
Ähnlich zu insertProfile erwartet diese Methode einen CustomerProfile-Brocken in XML und aktualisiert den existierenden Datensatz in der Datenbank, basierend auf der Kunden-ID. Hinter den Kulissen führt sie eine Lösch- und eine Einfüge-Operation durch.

deleteProfile(id)
Diese Methode nimmt eine Kunden-ID als Parameter an und löscht den entsprechenden Datensatz aus der Datenbank.

Mit Ausnahme von getProfile und getProfileAsDom geben diese Methoden entweder 1 oder 0 (true oder false) an den Aufrufer zurück, wodurch es möglich wird, diese Werte als Argumente von if-Anweisungen zu benutzen.

Zugreifen auf Profile

Die Klasse CustomerProfile bietet zwei Methoden für den Zugriff auf Profile: getProfile und getProfileAsDom. Beide erwarten eine customerId als Argument. In einem einfachen Testfall (in der Datei runcp.py) könnten Sie diese Methoden wie folgt benutzen:

from CustomerProfile import CustomerProfile
from xml.dom.ext import PrettyPrint

cp = CustomerProfile( )

print "Ein XML-String:"
print cp.getProfile("234-E838839")

print "Oder ein DOM:"
dom = cp.getProfileAsDom("234-E838839")
PrettyPrint(dom)

Hierbei wird angenommen, daß Sie einen Datensatz mit einer customerId von 234-E838839 in die Datenbank eingetragen haben. Das Ergebnis nach Ausführung dieses Codes ist die Ausgabe von zwei identischen XML-Darstellungen:

G:\pythonxml\c10> python runcp.py
Ein XML-String:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE CustomerProfile SYSTEM "CustomerProfile.dtd">
<CustomerProfile id='234-E838839'>
  <firstname>John</firstname>
  <lastname>Smith</lastname>
  <address1>123 Evergreen Terrace</address1>
  <address2>
  </address2>
  <city>Podunk</city>
  <state>WA</state>
  <zip>98072</zip>
</CustomerProfile>

Ein DOM:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE CustomerProfile SYSTEM "CustomerProfile.dtd">
<CustomerProfile id='234-E838839'>
  <firstname>John</firstname>
  <lastname>Smith</lastname>
  <address1>123 Evergreen Terrace</address1>
  <address2>
  </address2>
  <city>Podunk</city>
  <state>WA</state>
  <zip>98072</zip>
</CustomerProfile>

Ob Sie einen rohen XML-String oder ein DOM bevorzugen, hängt davon ab, was Sie mit dem Datensatz anstellen möchten, nachdem Sie ihn bekommen haben. Wenn Sie ihn an eine andere Anwendung zurückgeben, ist es klug, den String zu schicken oder den String zum Teil eines anderen Dokuments, z. B. eines SOAP-Pakets, zu machen (und natürlich die Prolog-Deklarationen herauszunehmen). Wenn Sie das Ergebnis jedoch bearbeiten und vielleicht wieder in die Datenbank eintragen möchten, ist ein DOM vermutlich besser geeignet.

Verbinden mit der Datenbank

Der Code für getProfile ist sehr einfach. Zuerst werden die Parameter auf einfache Weise geprüft, und dann wird eine Datenbankverbindung mit einer einfachen SQL-Anweisung vorbereitet:

def getProfile(self, strId, dom=0):    
    """
    getProfile - gibt ein XML-Profil basierend auf der angegebenen ID
    zurück. Liefert Null, falls nichts gefunden wird.
    """
    if not strId:
    return None
    # Stelle Verbindung her
    conn = odbc.odbc(CONNECTION_STRING)
    cmd = conn.cursor()
    cmd.execute("select * from customer where " +
    "customerId = '" + strId + "'")
    conn.close()

Beachten Sie, daß in diesem Code strId inspiziert wird, bevor irgend etwas anderes passiert, und der Datenbank-Befehl einer von der einfachen Sorte select * ist. Beachtung verdient auch der dritte Vorgabeparameter in der Methodendefinition, dom=0. Dieser Schalter wird von getProfileAsDom umgelegt, damit getProfile eine DOM-Instanz statt eines Strings zurückgibt.

Erzeugen des XML-Dokuments

Als nächstes wird der Datensatz geholt, und ein XML-Dokument wird mit der Klasse DOMImplementation vorbereitet.

# Hole Datensatz
prof_fields = cmd.fetchone()
if prof_fields is None:
return None

# Erzeuge XML aus den Datenfeldern,
# erzeuge CustomerProfile-doctype
doctype = implementation.createDocumentType(
"CustomerProfile", "", "CustomerProfile.dtd")

# Erzeuge neues Dokument mit doctype
newdoc = implementation.createDocument(
"", "CustomerProfile", doctype)
rootelem = newdoc.documentElement

# Erzeuge ein id-Attribut im Wurzelelement
rootelem.setAttribute("id", prof_fields[CUSTOMER_ID])

Bei diesem Code wird die DOMImplementation-Klasse dazu benutzt, ein XML-Dokument zu erstellen. Zu Beginn wird mit Hilfe der Methode fetchone, die eine Zeile liefert, die Liste von Rohwerten aus der Datenbank in prof_fields abgelegt. Die Indizes in prof_fields werden als Namenskonstanten im Modul CustomerProfile definiert. Diese Definitionen werden im kompletten Listing des Quellcodes für dieses Modul später im Beispiel CustomerProfile.py angegeben. Anschließend wird ein Dokumenttyp-Objekt erzeugt, das die zuvor erzeugte Datei CustomerProfile.dtd anführt. Dieses Objekt wird dann im Aufruf von implementation.createDocument benutzt, um ein leeres XML-Dokumentelement zu erzeugen.

An diesem Punkt zeigen wir, wie einfach es ist, mit der DOMImplementation Ihr XML-Dokument zu bauen (verglichen mit dem manuellen Erstellen eines XML-Strings, was später gemacht wird), wenn wir die Elemente in einer simplen Schleife anordnen:

# Erzeuge Dictionary mit Feldwerten
fields = ["firstname", prof_fields[FIRSTNAME],          
          "lastname", prof_fields[LASTNAME],
          "address1", prof_fields[ADDRESS1],
          "address2", prof_fields[ADDRESS2],
          "city", prof_fields[CITY],
          "state", prof_fields[STATE],
          "zip", prof_fields[ZIP],
          ]
# Laufe durch Dictionary, füge Elemente und Elementtext hinzu
for pos in range(0, len(fields), 2):  
  # Erzeuge das Element
  thisElement = newdoc.createElement(fields[pos])
  
  # Teste auf leere Werte und konvertiere in weiche Nullen
  if fields[pos + 1] is None:
    fields[pos + 1] = ""
  # Füge Feldwert als Kindtextknoten an Element hinzu
  thisElement.appendChild(newdoc.createTextNode(fields[pos+1]))
  rootelem.appendChild(thisElement)

Interessant an diesem Code ist, daß Sie alle Datenbankwerte in eine Liste plazieren und dann beim Iterieren über diese Liste Elemente erzeugen und anfügen. Benutzen Sie dabei eine Liste für die Datenwerte statt ein Dictionary, denn Sie müssen Elemente im XML-Dokument in einer bestimmten Reihenfolge erzeugen, sonst wird die DTD nicht eingehalten. (Python-Dictionaries geben ihre Schlüssel in einer zufälligen Reihenfolge zurück und halten ganz bestimmt nicht die Reihenfolge ein, in der Sie sie eingefügt haben.)

Eine Liste statt ein Dictionary zu benutzen ist aber kein Kunststück, da die Python-Funktion range dazu benutzt wird, mit einer Schrittweite von zwei durch die Liste zu springen, was es erlaubt, das aktuelle Listenelement als Elementnamen und das darauf folgende als Zeichendaten des Elements zu verwenden.

Mit diesem XML in der Hand wird eine Entscheidung darüber getroffen, ob ein XML-String oder ein DOM zurückgegeben wird, abhängig davon, wie die Methode aufgerufen wurde (wie Sie sich erinnern, wird getProfile von getProfileAsDom mit weiteren Parametern aufgerufen):

# Liefere DOM oder String, je nachdem, wie wir aufgerufen wurden...
if dom:   
   return newdoc.documentElement
else:
   # Gib String zurück
   strXML = StringIO.StringIO()
   PrettyPrint(newdoc.documentElement, strXML)
   return strXML.getvalue()

An dieser Stelle ist der Aufruf beendet. Falls ein String zurückgeliefert wird, wird die Klasse StringIO zusammen mit der PrettyPrint-Methode benutzt, um das XML so in den String zu schreiben, als ob er eine Datei wäre. Es hat mehrere Vorteile, Ihr Dokument mit einer DOMImplementation zu erzeugen, statt einen String manuell zu konstruieren. Zuerst fallen komplizierte Details wie die Vorbereitung einer DTD und Codierungsdeklarationen mit String-Zuweisungen weg. Es ist wesentlich leichter, den Code zu warten, wenn Sie die Codierung oder den Dokumenttyp programmatisch ändern können, ohne XML in String-Zuweisungen von Hand editieren zu müssen. Zweitens bekommen Sie eine größere Flexibilität, wenn Sie die Struktur des Dokuments ändern müssen. Auf lange Sicht ist es mit DOM einfacher, Knoten und deren Position im Dokument zu bearbeiten, als einen Text-String zu parsen und zu bearbeiten. Es kommt jedoch oft vor, daß Sie Teile eines XML-Strings verketten und ein neues DOM erzeugen möchten (in diesem Fall landen Sie wieder bei einer programmatisch zugänglichen DOM-Darstellung des XML-Codes).

Zurückgeben eines DOM statt eines Strings

Die Methode getProfileAsDom funktioniert genauso wie getProfile, gibt Ihnen jedoch eine DOM-Instanz statt eines XML-Strings zurück.

def getProfileAsDom(self, strId):    
    """
    Diese Methode ruft getProfile mit einer gesetzten dom-Option auf,
    was bewirkt, daß getProfile sein erzeugtes DOM-Objekt zurückgibt
    statt eines XML-Strings.
    """
    return self.getProfile(strId, 1)

Dieser Ansatz wurde gewählt, weil es oft einfacher ist, intuitive Hilfsfunktionen anzubieten, als eine Methode mit bedingten Parametern zu überladen, die ein Benutzer lernen muß.

Einfügen und Löschen von Profilen

Die Methode insertProfile ist das andere Arbeitspferd in der CustomerProfile-Klasse. Während getProfile ein DOM aufbaut, nimmt insertProfile ein DOM auseinander, um dessen Werte in der Datenbank abzulegen. Profile zu löschen ist relativ einfach, da nur eine einzige SQL-Anweisung mit der angegebenen Kunden-ID direkt an die Datenbank geschickt wird.

Mit der Methode insertProfile werden neue CustomerProfile-XML-Dokumente in die Datenbank hinzugefügt. Diese Schnittstelle bewahrt Client-Anwendungen davor, sich mit der Datenbank verbinden oder die Tabellenstruktur verstehen zu müssen. Man kann mit Recht behaupten, daß es in verteilten Anwendungen leichter ist, gemeinsam die gleiche Struktur von XML-Dokumenten als die von Datenbanken zu verwenden, wo man möglicherweise noch eine Unterstützung für proprietäre Datentypen bräuchte.

Benutzen Sie insertProfile mit einem XML-String wie z. B. dem folgenden:

cp = CustomerProfile( )
cp.insertProfile("<String mit XML>")

Die Methode gibt true bei Erfolg und false bei Mißerfolg zurück. Zusätzlich können Ausnahmen weitergegeben werden, wenn sie bei der Behandlung des Codes auftreten.

Einfügen eines Profils

Der Code für insertProfile ist ebenfalls einfach, verwendet jedoch eine kompliziertere DOM-Bearbeitung. Er beginnt wie getProfile damit, daß einige simple Überprüfungen stattfinden und eine DOM-Instanz erzeugt wird (im Gegensatz zu einer DOM-Implementation in getProfile):

def insertProfile(self, strXML):   
   """
   insertProfile nimmt einen XML-String-Happen, parst
   dessen Felder und fügt ihn in die Profil-Datenbank
   ein. Eine Ausnahme wird ausgelöst, falls XML nicht
   wohlgeformt ist oder die Kunden-ID fehlt oder falls der
   SQL-Aufruf schiefgeht. Liefert 1 bei Erfolg, sonst 0.
   """
   if not strXML:
      raise Exception("Kein XML-String angegeben.")
   
   # Beginne Parsen von XML
   try:
      doc = FromXml(strXML)
   except:
      print "Fehler beim Parsen des Dokuments."
      return 0
   
   # Normalisiere Leerraum und hole Wurzelelement
   doc.normalize()
   elem = doc.documentElement

Die wahrscheinlich wichtigsten Teile im vorausgehenden Code sind die Aufrufe von FromXml und der Aufruf der normalize-Methode des instanziierten Dokumentobjekts. Die FromXml-Methode ist Teil des Sax2-Reader-Pakets und ermöglicht die Konstruktion eines DOM-Objekts aus Daten in einem rohen XML-String.

Der Aufruf der doc.normalize-Methode ist ebenfalls wichtig. Die Struktur eines CustomerProfile-XML-Stückchens ist ziemlich einfach. Die Zeichendaten in den Elementen sind kurz und brauchen keinen umgebenden Leerraum. Das heißt, falls ein Webformular oder eine GUI Carriage-Return-Zeichen in die Elemente eingeschleust hat, können diese gefahrlos eliminiert werden. Dieser Schritt ist entscheidend dafür, wie die Elemente in insertProfile verarbeitet werden. Ohne den Normalisierungsaufruf wäre es möglich, daß der Text im Element in mehreren Knoten enthalten ist und das firstChild-Attribut nur den ersten angibt; der Aufruf von normalize garantiert, daß alle benachbarten Textknoten in einem einzigen Knoten zusammenfallen.

Als nächstes werden die Werte aus dem frischen XML-DOM extrahiert, um damit eine SQL-Anweisung zu füllen.

# Extrahiere Werte aus XML-Paket
customerId = elem.getAttributeNS(None, 'id')
firstname = self.extractNodeValue(elem, "firstname")
lastname = self.extractNodeValue(elem, "lastname")
address1 = self.extractNodeValue(elem, "address1")
address2 = self.extractNodeValue(elem, "address2")
city = self.extractNodeValue(elem, "city")
state = self.extractNodeValue(elem, "state")
zip = self.extractNodeValue(elem, "zip")

# Bereite SQL-Anweisung vor          
strSQL = ("insert into Customer values ("
          "'" + firstname + "', "
          "'" + lastname + "', "
          "'" + address1 + "', "
          "'" + address2 + "', "
          "'" + city + "', "
          "'" + state + "', "
          "'" + zip + "', "
          "'" + customerId + "')")

Hierbei wird das customerId-Attribut aus dem Wurzelelement extrahiert, und dann wird eine Folge von Aufrufen zur Hilfsmethode extractNodeValue angestoßen. Diese kleine Methode (die sogleich gezeigt wird) nimmt das aktuelle Element und versucht, den Textwert eines Ziels unter sich selbst zu extrahieren. Da dieser Schritt für jedes Element wiederholt wird, das Sie brauchen, ist es einfacher, ihn in einer internen Funktion unterzubringen, statt den Code in jeder Methode zu duplizieren, die ihn verwendet. Zur Vorbereitung der SQL-Anweisung nimmt man die Ergebnisse der Aufrufe von extractNodeValue und setzt eine insert-Anweisung in SQL zusammen. Die Arbeit, die extractNodeValue verrichtet, ist folgende:

def extractNodeValue(self, elem, elemName):   
   """
   Interne Methode zum Parsen von UNIQUE-Elementen
   für Textwerte oder zum Ersetzen von leeren Strings.
   """
   e = elem.getElementsByTagName(elemName)[0].firstChild
   if e is None:
      return ""
   else:
      return e.nodeValue

Diese Methode versucht, ein Kindelement aus dem ihr angegebenen Element zu extrahieren, und testet auch dessen Zeichendaten-Inhalt. Falls nicht vorhanden, wird ein leerer String zurückgegeben.

Nun, da die SQL-Anweisung vorbereitet ist, kann sie an die Datenbank übergeben werden:

# Stelle Verbindung her
conn = odbc.odbc(CONNECTION_STRING)
cmd = conn.cursor()
# Führe SQL-Anweisung aus
if (not cmd.execute(strSQL)):
   raise Exception("SQL-Anweisung fehlgeschlagen.")

conn.close()
return 1

Wenn die Kommunikation mit der Datenbank wie erwartet funktioniert, gibt die Methode eine positive 1 an den Aufrufer zurück.

Löschen eines Profils

Der Code zum Löschen eines Profils aus der Datenbank ist einfach und besteht hauptsächlich daraus, die angegebene Kunden-ID zu nehmen und als Parameter in einer delete-SQL-Anweisung zu verwenden:

def deleteProfile(self, strId):   
   """
   deleteProfile erwartet eine Kundenprofil-ID und löscht den entsprechenden
   Satz in der Datenbank. Liefert 1 bei Erfolg, sonst 0.
   """
   if not strId:
      return 0 
   
   # Stelle Datenbankverbindung her
   conn = odbc.odbc(CONNECTION_STRING)
   cmd = conn.cursor()
   ok = cmd.execute("delete from customer where customerId = '"
                      + strId + "'")):
   conn.close()
   return ok and 1 or 0

Das Ergebnis der SQL-Operation wird dem Aufrufer durch einen Rückgabewert von 1 oder 0 mitgeteilt.

Aktualisieren von Profilen

Der Vorgang einer Aktualisierung eines Profils mit der CustomerProfile-Klasse ist simpel. Beim Aufruf von updateProfile übergeben Sie ein neues Stück XML-Daten. Dessen customerId muß einer existierenden ID in der Datenbank entsprechen. In diesem Fall wird der alte Datensatz gelöscht und der neue eingefügt.

Wie schon vorher erwähnt wurde, benutzt die Methode updateProfile intern insertProfile und deleteProfile, ist aber dennoch vorhanden, um die Klasse CustomerProfile leichter benutzbar zu machen.

Um die customerId aus dem XML-Stück zu extrahieren, wird schnell ein DOM-Objekt instanziiert, um die Daten zu parsen:

def updateProfile(self, strXML):   
   """
   Diese Hilfsfunktion erwartet ein neues Kundenprofil als
   XML-Paket. Sie extrahiert die Kunden-ID und ruft dann
   deleteProfile und insertProfile mit Hilfe des neuen XML auf.
   Der Rückgabewert für das Einfügen wird an den Aufrufer
   weitergegeben, außer das Löschen schlägt fehl, wobei er dann
   zurückgegeben wird, aber das Einfügen nicht stattfindet.
   """
   # Parse Dokument nach Kunden-ID
   try:
      doc = FromXml(strXML)
      customerId = doc.documentElement.getAttributeNS('','id')
   except:
      print "Fehler beim Parsen des Dokuments."
      return 0

Wie der vorangehende Code zeigt, wird FromXml erneut benutzt, um die stringbasierten XML-Daten in ein DOM-Objekt zu konvertieren. Die customerId wird dann mit einem Aufruf von getAttributeNS extrahiert. Da das Paket reader.Sax2 verwendet wird, müssen Sie eine namensraumorientierte DOM-Methode statt der normalen getAttribute-Methode benutzen (dies ist ein heiß diskutierter Fehler in der Implementierung, der hoffentlich nach Erscheinen dieses Buches behoben sein wird, so daß dann getAttribute ebenfalls funktionieren wird. Um an den lebhaften Kommentaren teilzunehmen, können Sie in die XML-SIG eintreten). Mit dieser customerId können Sie von den existierenden Einfüge- und Löschmethoden Gebrauch machen:

# Versuche zu löschen und füge basierend auf customerId ein
if self.deleteProfile(customerId):
   return self.insertProfile(strXML)
else:
   return 1

Die Rückgabe dieser Funktionen wird als Ergebnis direkt an den Aufrufer zurückgeliefert.

Die komplette CustomerProfile-Klasse

Die CustomerProfile-Klasse ist ein ziemliches Arbeitstier. Mit ihr können Kundeninformationen im Netzwerk als XML gespeichert werden. Alle verteilten Anwendungen, die Zugang zur CustomerProfile-Klasse haben, können ihre Funktionalität benutzen, ohne irgend etwas über die zugrundeliegende Datenbank oder das Speichermedium zu wissen. Außerdem ist es möglich, XML an eine Anwendung weiterzuleiten, die die CustomerProfile-Klasse beherbergt, um eine Aktualisierung vorzunehmen. In diesem bestmöglichen Szenario braucht die aufrufende Anwendung nicht einmal die CustomerProfile-Klasse zu kennen, sondern muß nur das passende SOAP- oder XML-Nachrichtenformat konstruieren und im Netzwerk abschicken. Wir führen diese Operation später in diesem Kapitel durch, nachdem der XML-Switch steht, und zwar in den Abschnitten Der XML-Switch-Client und Die CGI-Funktionalität.

Das komplette Listing der Klasse CustomerProfile ist im folgenden Beispiel abgebildet.

Beispiel: CustomerProfile.py

"""
CustomerProfile.py
"""
import dbi
import odbc
import StringIO

from xml.dom import implementation
from xml.dom.ext import PrettyPrint
from xml.dom.ext.reader.Sax2 import FromXml

# Definiere einige globale Variablen für die Klasse
CONNECTION_STRING = "Profiles/webuser/w3bus3r"
FIRSTNAME = 0
LASTNAME = 1
ADDRESS1 = 2
ADDRESS2 = 3
CITY = 4
STATE = 5
ZIP = 6
CUSTOMER_ID = 7

class CustomerProfile:   
   """
   CustomerProfile - verwaltet das Speichern und Laden von
   XML-Kundenprofilen in/von einer relationalen Datenbank.
   """
   def getProfileAsDom(self, strId):
   """
   Diese Methode ruft getProfile mit einer gesetzten dom-Option auf,
   was bewirkt, daß getProfile sein erzeugtes DOM-Objekt zurückgibt
   statt eines XML-Strings.
   """
   return self.getProfile(strId, 1)
   
   def getProfile(self, strId, dom=0):
   """
   getProfile - gibt ein XML-Profil basierend auf der angegebenen ID
   zurück. Liefert Null, falls nichts gefunden wird.
   """
   if not strId:
   return None
   
   # Stelle Verbindung her
   conn = odbc.odbc(CONNECTION_STRING)
   cmd = conn.cursor()
   cmd.execute("select * from customer where " +
   "customerId = '" + strId + "'")
   conn.close()
   
   # Hole Datensatz
   prof_fields = cmd.fetchone()
   if prof_fields is None:
   return None
   
   # Erzeuge XML aus den Datenfeldern,
   # erzeuge CustomerProfile-doctype
   doctype = implementation.createDocumentType(
   "CustomerProfile", "", "CustomerProfile.dtd")
   
   # Erzeuge neues Dokument mit doctype
   newdoc = implementation.createDocument(
   "", "CustomerProfile", doctype)
   rootelem = newdoc.documentElement
   
   # Erzeuge ein id-Attribut im Wurzelelement
   rootelem.setAttribute("id", prof_fields[CUSTOMER_ID])
   
   # Erzeuge Dictionary mit Feldwerten
   fields = ["firstname", prof_fields[FIRSTNAME],
   "lastname", prof_fields[LASTNAME],
   "address1", prof_fields[ADDRESS1],
   "address2", prof_fields[ADDRESS2],
   "city", prof_fields[CITY],
   "state", prof_fields[STATE],
   "zip", prof_fields[ZIP],
   ]
   # Laufe durch Dictionary, füge Elemente und Elementtext hinzu
   for pos in range(0, len(fields), 2):
   # Erzeuge das Element
   thisElement = newdoc.createElement(fields[pos])
   # Teste auf leere Werte und konvertiere in weiche Nullen
   if fields[pos + 1] is None:
   fields[pos + 1] = ""
   # Füge Feldwert als Kindtextknoten zu Element hinzu
      thisElement.appendChild(newdoc.createTextNode(fields[pos+1]))
   rootelem.appendChild(thisElement)
   
   # Liefere DOM oder String, je nachdem, wie wir aufgerufen wurden...
   if dom:
   return newdoc.documentElement
   else:
   # Gib String zurück
   strXML = StringIO.StringIO()
   PrettyPrint(newdoc.documentElement, strXML)
   return strXML.getvalue()
   
   def insertProfile(self, strXML):
   """
   insertProfile nimmt einen XML-String-Happen, parst
   dessen Felder und fügt ihn in die Profil-Datenbank
   ein. Eine Ausnahme wird ausgelöst, falls XML nicht
   wohlgeformt ist oder die Kunden-ID fehlt oder falls der
   SQL-Aufruf schiefgeht. Liefert 1 bei Erfolg, sonst 0.
   """
   if not strXML:
   raise Exception("Kein XML-String angegeben.")
   
   # Beginne Parsen von XML
   try:
   doc = FromXml(strXML)
   except:
   print "Fehler beim Parsen des Dokuments."
   return 0
   
   # Normalisiere Leerraum und hole Wurzelelement
   doc.normalize()
   elem = doc.documentElement
   
   # Extrahiere Werte aus XML-Paket
   customerId = elem.getAttributeNS(None, 'id')
   firstname = self.extractNodeValue(elem, "firstname")
   lastname = self.extractNodeValue(elem, "lastname")
   address1 = self.extractNodeValue(elem, "address1")
   address2 = self.extractNodeValue(elem, "address2")
   city = self.extractNodeValue(elem, "city")
   state = self.extractNodeValue(elem, "state")
   zip = self.extractNodeValue(elem, "zip")
   
   # Bereite SQL-Anweisung vor           
   strSQL = ("insert into Customer values ("
              "'" + firstname + "', "
              "'" + lastname + "', "
              "'" + address1 + "', "
              "'" + address2 + "', "
              "'" + city + "', "
              "'" + state + "', "
              "'" + zip + "', "
              "'" + customerId + "')")
   
   # Stelle Verbindung her
   conn = odbc.odbc(CONNECTION_STRING)
   cmd = conn.cursor()
   # Führe SQL-Anweisung aus
   if (not cmd.execute(strSQL)):
   raise Exception("SQL-Anweisung fehlgeschlagen.")
   conn.close()
   return 1
   
   def extractNodeValue(self, elem, elemName):
   """
   Interne Methode zum Parsen von UNIQUE-Elementen
   für Textwerte oder zum Ersetzen von leeren Strings.
   """
   e = elem.getElementsByTagName(elemName)[0].firstChild
   if e is None:
   return ""
   else:
   return e.nodeValue
   
   def updateProfile(self, strXML):
   """
   Diese Hilfsfunktion erwartet ein neues Kundenprofil als
   XML-Paket. Sie extrahiert die Kunden-ID und ruft dann
   deleteProfile und insertProfile mit Hilfe des neuen XML auf.
   Der Rückgabewert für das Einfügen wird an den Aufrufer
   weitergegeben, außer das Löschen schlägt fehl, wobei er dann
   zurückgegeben wird, aber das Einfügen nicht stattfindet.
      """
   # Parse Dokument nach Kunden-ID
   try:
   doc = FromXml(strXML)
   customerId = doc.documentElement.getAttributeNS('','id')
   except:
   print "Fehler beim Parsen des Dokuments."
   return 0
   
   # Versuche zu löschen und füge basierend auf customerId ein
   if self.deleteProfile(customerId):
   return self.insertProfile(strXML)
   else:
   return 1
   
   def deleteProfile(self, strId):
   """
   deleteProfile erwartet eine Kundenprofil-ID und löscht den entsprechenden
   Satz in der Datenbank. Liefert 1 bei Erfolg, sonst 0.
   """
   if not strId:
   return 0

      # Stelle Datenbankverbindung her
      conn = odbc.odbc(CONNECTION_STRING)
   cmd = conn.cursor()
      ok = cmd.execute("delete from customer where customerId = '" + strId + "'"):
   conn.close()
   return ok and 1 or 0

Bemerkenswert in CustomerProfile.py ist die Verwendung von Konstanten, die zu Beginn der Datei als Feldmarker in den Datenbankdatensätzen definiert werden. Durch die Definition von Konstanten ist es wesentlich leichter, mit den verschiedenen Feldern in der Liste der Datensätze zu arbeiten.

  

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