Python und XML

Willemers Informatik-Ecke


Bestellen bei Amazon
2015-07-31

Diese Seiten sind Grundlage meines Python-Buchs aus den ersten Recherchen.

Insbesondere für den Datenaustausch, aber durchaus auch für das Speichern von Anwendungsdaten hat sich das Format XML etabliert.

XML kann Daten strukturieren. Dazu verwendet XML sogenannte Tags. Ein Tag wird in spitze Klammern, also Kleiner- und Größerzeichen, eingeschlossen. Das Ende wird durch ein End-Tag definiert, das direkt nach dem Kleinerzeichen einen Schrägstrich besitzt.

Am einfachsten ist eine XML-Datei am Beispiel zu verstehen. Die folgende XML-Datei enthält eine Kundenliste. Diese wird unter dem Namen kundenliste abgelegt. In der Kundenliste können mehrere Kunden abgelegt werden. Jeder von denen wird durch das Tag kunde eingeschlossen. Jeder der Kunden hat die Felder kdnr, name und name. Die vorliegende XML-Datei ist also beispielsweise dazu geeignet eine Datenbanktabelle für Kunden zu sichern und auch wieder zu füttern.

<?xml version="1.0" encoding="UTF-8"?>
<kundenliste>
  <kunde>
    <kdnr>1234</kdnr>
    <name>Reiner Zufall</name>
    <anschrift>Auf dem Holzweg, Buxtehude</anschrift>
  </kunde>
  <kunde>
    <kdnr>1235</kdnr>
    <name>Maria Klug</name>
    <anschrift>Einsteinring 2, Gintoft</anschrift>
  </kunde>
</kundenliste>
Die erste Zeile ist sehr speziell, weil sie den Standard und die Zeichenkodierung festlegt. Aber sie zeigt auch, dass Tags Attribute haben können und wie sie gesetzt werden. Hier beispielsweise hat xml zwei Attribute, nämlich version und encoding, denen durch ein Gleichheitszeichen Werte zugewiesen werden.
XML-Deklaration
Die erste Zeile bestimmt den Standard, nach dem sich die XML-Datei richtet. Dabei ist für die internationalen Sonderzeichen vor allem der Encoding-Standard wichtig, wie hier UTF-8.
<?xml version="1.0" encoding="UTF-8"?>
Ein Tag
Ein Tag umschließt einen Bereich und kann verschachtelt werden.
<aussen>
   <innen></innen>
   <nochwas></nochwas>
</aussen>
Die Einrückung und Anordnung der Tags ist grundsätzlich frei gestaltbar. Da aber von Zeit zu Zeit auch Menschen diese Dateien lesen müssen, ist eine übersichtliche Einrückung schon hilfreich. Tags können Attribute haben. Hier hat das Gehalt das Attribut typ, das mit dem Wert float besetzt ist.
<gehalt typ=float>
1200.00
</gehalt>
Eine Kurzform ist möglich, vor allem, wenn das Tag nur Attribute enthält.
<gehalt typ=float wert=1200.00 />
Da es sich bei XML um reine Textdateien handelt, wäre es natürlich möglich, die Dateien mit den Standarddateizugriffen zu lesen und den Inhalt auszuwerten. Eine solche Auswertung würde dann auch schnell zu einer Rekursion führen und die Gebiete des Compilerbaus berühren. Das ist nicht jedermanns Sache. Aus diesem Grund haben sich Bibliotheken entwickeln die den Umgang mit XML beherrschen. Sie werden hier DOM und SAX kennenlernen.

XML-Dateien auslesen mit DOM

DOM steht für Document Object Model. Dieses Verfahren liest die XML-Datei komplett in den Speicher, um sie dann auswerten zu können. Das hat bei großen XML-Dateien den Nachteil, dass sehr viel Speicher belegt wird.
Kurz und knapp
Um den Umgang mit DOM an einem einfachen Beispiel zu demonstrieren, verwenden wir die folgende relativ unsinnige XML-Datei, die unter dem Namen items.xml vorliegen soll.
<data>
    <items>
        <item name="item1">Inhalt1</item>
        <item name="item2">Inhalt2</item>
        <item name="item3">Inhalt3</item>
        <item name="item4">Inhalt4</item>
    </items>
</data>
Das folgende Programm liest die Datei ein und sucht dann das Tag item. Aus der Liste wird dann das jeweilige Attribut name ausgelesen und über childnotes der eigentliche Inhalt. Beides wird auf dem Bildschirm angezeigt.
from xml.dom import minidom
xmldoc = minidom.parse('items.xml')
itemlist = xmldoc.getElementsByTagName('item') 
print(len(itemlist))
print(itemlist[0].attributes['name'].value)
for s in itemlist :
    print(s.attributes['name'].value+":",
            s.childNodes[0].nodeValue)
Die Kundenliste
Nachdem die einfache Auswertung geklappt hat, wenden wir und dem Beispiel mit der bereits vorgestellten Kundenliste zu. Vor dem Auslesen der XML-Datei soll erst einmal eine Klasse für den Kunden definiert werden. Das ist zwar prinzipiell nicht erforderlich, ist aber für die Weiterverarbeitung sinnvoll.
class Kunde:
    def __init__(self):
        __kdnr = 0
        __name = ""
        __anschrift = ""
    def setKdnr(self, kdnr):
        self.__kdnr = kdnr
    def setName(self, name):
        self.__name = name
    def setAnschrift(self, anschrift):
        self.__anschrift = anschrift
    def __str__(self):  # um per print ausgeben zu koennen
        return str(self.__kdnr)+"|"+self.__name \
                +"|"+self.__anschrift
Die Bibliothek xml.dom enthält den Parser minidom, der für die meisten Anwendungen ausreichend ist.

In der Importzeile wird minidom mit dem Befehl as umbenannt, damit der Parser im Programm einfach als dom angesprochen werden kann. Das vereinfacht einen später vielleicht doch notwendigen Wechsel des Parsers.

Die Funktion leseKunde() erledigt die eigentliche Arbeit.

import xml.dom.minidom as dom

def leseKunde(dateiname):
    KundenDict = {}
    baum = dom.parse(dateiname)
    for eintrag in baum.firstChild.childNodes:
        if eintrag.nodeName == "kunde":
            kunde = Kunde()
            for knoten in eintrag.childNodes:
                if knoten.nodeName == "kdnr":
                    kdnr = knoten.firstChild.data
                    kunde.setKdnr(kdnr)
                elif knoten.nodeName == "name":
                    kunde.setName(
                            knoten.firstChild.data.strip())
                elif knoten.nodeName == "anschrift":
                    kunde.setAnschrift(
                            knoten.firstChild.data.strip())
            KundenDict[kdnr] = kunde
    return KundenDict

kundenliste = leseKunde("kundenliste.xml")
for i in kundenliste:
    print.kundenliste[i]

XML erzeugen mit DOM

Wenn aus internen Daten XML-Dateien erzeugt werden sollen, kann DOM ebenfalls helfen. Das folgende Beispielprogramm erzeugt aus einer Kundenliste, die zunächst vom Programm erzeugt wird, eine XML-Datei. Dazu wird mit der Funktion createDocument() eine DOM-Struktur erzeugt und Element für Element über die Funktion createElement() erzeugt und dann per appendChild() eingehängt.

Nachdem die Struktur fertig ist, wird sie mit der Funktion writexml() weggeschrieben.

import xml.dom

def schreibeKunden(kundenliste, dateiname):
    domImpl = xml.dom.getDOMImplementation()
    doc = domImpl.createDocument(None, "kundenliste", None)
    for kdnr, kunde in kundenliste.iteritems():
        # Kundennummer-Eintrag
        kdnrElem = doc.createElement("kdnr")
        kdnrElem.appendChild(doc.createTextNode(str(kdnr)))
        # Namens-Eintrag
        nameElem = doc.createElement("name")
        nameElem.appendChild( doc.createTextNode(
                kunde.getName()))
        # Anschrift-Eintrag
        anschriftElem = doc.createElement("anschrift")
        anschriftElem.appendChild( doc.createTextNode(
                kunde.getAnschrift()))
        # Baue daraus einen Kunden
        kundeElem = doc.createElement("kunde")
        kundeElem.appendChild(kdnrElem)
        kundeElem.appendChild(nameElem)
        kundeElem.appendChild(anschriftElem)
        # Haenge den Kunden ans Dokument
        doc.documentElement.appendChild(kundeElem)

    datei = open(dateiname, "w")
    doc.writexml(datei, "\n", "  ")
    datei.close()

# Einen neuen Eintrag 
kunde = Kunde()
kunde.setKdnr(5607)
kunde.setName("Heike Schmidt")
kunde.setAnschrift("Irgendwo")
kundenliste[5607] = kunde
schreibeKunden(kundenliste, "kundenliste.xml")

SAX

SAX steht für Simple API for XML. SAX geht die XML-Datei Stück für Stück durch und meldet das Eintreten von Ereignissen. Darum eignet sich SAX vor allem für sehr große XML-Dateien. Da SAX nicht die gesamte Struktur im Speicher hält, ist die Arbeitsweise grundlegend anders.

Zunächst wird eine eigene Klasse KundenHandler von dem ContentHandler abgeleitet. Diese implementiert die Funktionen startElement(), endElement() und characters(). Ähnlich wie bei Callbacks der grafischen Oberflächen werden die ersten beiden Funktionen aufgerufen, wenn ein Element betreten oder verlassen wird. Die Funktion characters() dient dagegen nur zur Sammlung der Zeichen in den lokalen Variablen.

import xml.sax as sax
class KundenHandler(sax.handler.ContentHandler):

    def __init__(self):
        self.kdnr = ""
        self.name = ""
        self.anschrift = ""
        self.aktiv = None
        self.typ = None

    def startElement(self, name, attrs):
        if name == "kunde":
            # Initialisieren eines neuen Kunden
            self.kdnr = ""
            self.name = ""
            self.anschrift = ""
        elif name == "kdnr" or name == "name" \
                or name == "anschrift":
            self.aktiv = name

    def endElement(self, name):
        if name == "kunde":
            # Ein Kunde ist erfasst, Ergebnis auswerfen
            print(self.kdnr+"|"+self.name+"|"+self.anschrift)
        elif name == "kdnr" or name == "name" \
                or name == "anschrift":
            self.aktiv = None

    def characters(self, content):
        if self.aktiv == "kdnr":
            self.kdnr += content
        elif self.aktiv == "name":
            self.name += content
        elif self.aktiv == "anschrift":
            self.anschrift += content

def leseKunde(dateiname):
    handler = KundenHandler()
    parser = sax.make_parser()
    parser.setContentHandler(handler)
    parser.parse(dateiname)

leseKunde("kundenliste.xml")
Anstatt wie im DOM-Beispiel die Ergebnisse zu sammeln, werden sie hier gleich an Ort und Stelle bei endElement() auf dem Bildschirm ausgegeben. An dieser Stelle könnte auch genauso gut eine Funktion aufgerufen werden, die die Daten in einer Datenbank ablegt.

Homepage (C) Copyright 2014, 2015 Arnold Willemer