26. Vom Wörterbuch zu XML und umgekehrt
Wir möchten hier das Modul [xml2dict] näher betrachten, mit dem sich Folgendes umwandeln lässt:
- eine Zeichenkette XML in ein Wörterbuch:
- ein Wörterbuch in eine Zeichenkette XML;
Vor der Einführung von jSON lautete die Antwort von Webdiensten häufig XML (eXtended Markup Language). Außerdem war das Protokoll dieser Webdienste häufig SOAP (Simple Object Process Protocol). SOAP ist ein Protokoll, das auf dem Webprotokoll HTTP basiert. Derzeit (2020) sind Webdienste eher vom Typ REST (Representational State Transfer). Die von uns untersuchten Webdienste gehören zu keinem dieser Typen, liegen aber eindeutig näher an REST als an SOAP. Dennoch würde ich sie eher als vom Typ „frei“ oder „unbekannt“ bezeichnen, da sie nicht alle Regeln von REST einhalten.
Wir werden zeigen, wie einfach es ist, unsere Client-Server-Architekturen vom Typ jSON in Client-Server-Architekturen vom Typ XML umzuwandeln. Dazu muss lediglich das Modul [xmltodict] verwendet werden.
Zunächst installieren wir es in einem Python-Terminal:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install xmltodict
Collecting xmltodict
Using cached xmltodict-0.12.0-py2.py3-none-any.whl (9.2 kB)
Installing collected packages: xmltodict
Successfully installed xmltodict-0.12.0
Anschließend werden wir anhand eines Beispiels untersuchen, was man mit diesem Modul machen kann:

Das Skript [xml_01] lautet wie folgt:
from collections import OrderedDict
import xmltodict
# xmltodict.parse, um von XML zum Dictionary zu wechseln. Das Dictionary muss eine Wurzel haben
# Das erzeugte Wörterbuch ist vom Typ OrderedDict
# xmltodict.unparse, um vom Wörterbuch zu XML zu wechseln
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# Protokolle
print(f"\n{message}-------")
print(f"dictionnaire={dictionary}")
# Wörterbuch -> XML
xml1 = xmltodict.unparse(dictionary)
print(f"xml={xml1}")
# XML -> OrderedDict
ordereddict_dictionary1 = xmltodict.parse(xml1)
print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
# OrderedDict -> Wörterbuch
print(f"dict_dictionary1={ordereddict2dict(ordereddict_dictionary1)}")
# Test 1
transform("test 1", {"nom": "séléné"})
# Test 2
transform("test 2", {"famille": {"père": {"prénom": "andré"}, "mère": {"prénom": "angèle"}, "nom": "séléné"}})
# Test 3
transform("test 3", {"famille": {"nom": "séléné", "père": {"prénom": "andré"}, "mère": {"prénom": "angèle"},
"hobbies": ["chant", "footing"]}})
# Test 4
transform("test 4", {'réponse': {
'Fehler: ['Methode GET ist nur mit den Parametern [marié, enfants, salaire] erforderlich', 'Parameter [marié] fehlt',
'Parameter [enfants] fehlt', 'Parameter [salaire] fehlt']}})
# Test 5
transform("test 5", {'réponse': {
'Ergebnis': {'id': 0, 'verheiratet': 'ja', 'Kinder': 2, 'Gehalt': 50000, 'Steuer': 1384, 'Abzug': 384, 'Zuschlag': 0,
'Ermäßigung': 347, 'Satz': 0,14}}})
# Test 6
transform("test 6", {"root": {'liste': ["un", "deux", "trois"]}})
# Test 7
transform("test 7", {"root": {'liste': [{"un": [10, 11]}, {"deux": [20, 21]}, {"trois": [30, 31]}]}})
- Zeilen 14–25: Die Funktion [transform] erhält einen zu schreibenden Text [message] und ein Wörterbuch [dictionary];
- Zeile 16: Anzeige der Meldung;
- Zeile 17: Das empfangene Wörterbuch wird angezeigt;
- Zeilen 19–20: Dieses Wörterbuch wird in die Zeichenkette XML umgewandelt und diese wird angezeigt. Die Methode, die dies ausführt, ist [xmltodict.unparse];
- Zeilen 21–23: Die vorherige Zeichenkette XML wird in ein Wörterbuch umgewandelt und dieses wird angezeigt. Die Methode, die dies bewerkstelligt, ist [xmltodict.parse]. Diese Methode erzeugt kein Wörterbuch vom Typ [dict], sondern vom Typ [OrderedDict] (Zeile 1);
- Zeilen 24–25: Der erhaltene Typ [OrderedDict] wird mithilfe der (noch nicht geschriebenen) Methode [ordereddict2dict] in den Typ [dict] umgewandelt. Diese Methode arbeitet rekursiv. Sind bestimmte Werte des Wörterbuchs vom Typ [OrderedDict, list], werden die Werte dieser Sammlungen daraufhin überprüft, ob sie ebenfalls vom Typ [OrderedDict] sind. Ist dies der Fall, werden sie in den Typ [dict] umgewandelt. Es ist zu beachten, dass die Methode [xmltodict.parse] kein Wörterbuch vom Typ [dict] erzeugt;
Bevor wir uns die fehlenden Funktionen ansehen, wollen wir die Ergebnisse prüfen, um festzustellen, wonach gesucht wird:
Test 1 (Zeilen 28–29) liefert folgende Ergebnisse:
test 1-------
dictionnaire={'nom': 'séléné'}
xml=<?xml version="1.0" encoding="utf-8"?>
<nom>séléné</nom>
ordereddict_dictionary1=OrderedDict([('nom', 'séléné')])
dict_dictionary1={'nom': 'séléné'}
- Zeile 2: Das getestete Wörterbuch. Dabei ist ein wichtiger Punkt zu beachten: Die Methode [xml2dict.unparse] erfordert, dass das Wörterbuch die Form {‘Schlüssel’: Wert} hat, wobei [valeur] anschließend ein Wörterbuch, eine Liste oder ein einfacher Typ sein kann;
- Zeilen 3–4: Die aus dem Wörterbuch stammende Zeichenkette XML. Ihr ist der Header [<?xml version="1.0" encoding="utf-8"?>\n] vorangestellt, der normalerweise die erste Zeile einer XML-Datei ist;
- Zeile 5: der Typ [OrderedDict], der durch die Methode [xml2dict.parse] erhalten wurde, wobei die vorangegangene Zeichenkette XML als Parameter übergeben wurde;
- Zeile 6: das Wörterbuch vom Typ [dict], das durch Anwendung der Methode [ordereddict2dict] auf den vorherigen Typ erhalten wurde. Hier findet sich das ursprüngliche Wörterbuch aus Zeile 2 wieder;
Alle weiteren Tests sind nach dem gleichen Schema aufgebaut und sollen Ihnen verdeutlichen, wie man von einem Wörterbuch zu einer Zeichenkette XML und anschließend von dieser Zeichenkette XML zum ursprünglichen Wörterbuch gelangt.
Die anderen Tests liefern folgende Ergebnisse:
test 2-------
dictionnaire={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><nom>séléné</nom></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('nom', 'séléné')]))])
dict_dictionary1={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
test 3-------
dictionnaire={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><nom>séléné</nom><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><hobbies>chant</hobbies><hobbies>footing</hobbies></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('nom', 'séléné'), ('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('hobbies', ['chant', 'footing'])]))])
dict_dictionary1={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
test 4-------
dictionnaire={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><erreurs>Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]</erreurs><erreurs>paramètre [marié] manquant</erreurs><erreurs>paramètre [enfants] manquant</erreurs><erreurs>paramètre [salaire] manquant</erreurs></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('erreurs', ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant'])]))])
dict_dictionary1={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
test 5-------
dictionnaire={'réponse': {'result': {'id': 0, 'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'décôte': 384, 'surcôte': 0, 'réduction': 347, 'taux': 0.14}}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><result><id>0</id><marié>oui</marié><enfants>2</enfants><salaire>50000</salaire><impôt>1384</impôt><décôte>384</décôte><surcôte>0</surcôte><réduction>347</réduction><taux>0.14</taux></result></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('result', OrderedDict([('id', '0'), ('marié', 'oui'), ('enfants', '2'), ('salaire', '50000'), ('impôt', '1384'), ('décôte', '384'), ('surcôte', '0'), ('réduction', '347'), ('taux', '0.14')]))]))])
dict_dictionary1={'réponse': {'result': {'id': '0', 'marié': 'oui', 'enfants': '2', 'salaire': '50000', 'impôt': '1384', 'décôte': '384', 'surcôte': '0', 'réduction': '347', 'taux': '0.14'}}}
test 6-------
dictionnaire={'root': {'liste': ['un', 'deux', 'trois']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste>un</liste><liste>deux</liste><liste>trois</liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', ['un', 'deux', 'trois'])]))])
dict_dictionary1={'root': {'liste': ['un', 'deux', 'trois']}}
test 7-------
dictionnaire={'root': {'liste': [{'un': [10, 11]}, {'deux': [20, 21]}, {'trois': [30, 31]}]}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste><un>10</un><un>11</un></liste><liste><deux>20</deux><deux>21</deux></liste><liste><trois>30</trois><trois>31</trois></liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', [OrderedDict([('un', ['10', '11'])]), OrderedDict([('deux', ['20', '21'])]), OrderedDict([('trois', ['30', '31'])])])]))])
dict_dictionary1={'root': {'liste': [{'un': ['10', '11']}, {'deux': ['20', '21']}, {'trois': ['30', '31']}]}}
Process finished with exit code 0
- Die Zeilen 23 und 27 verdeutlichen einen wichtigen Punkt:
- Zeile 23: Die den Schlüsseln des Wörterbuchs „[result]“ zugeordneten Werte sind Zahlen;
- Zeile 26: Die den Schlüsseln des Wörterbuchs [ordereddict_dictionary1] zugeordneten Werte sind Zeichenfolgen. Dies ist eine Schwäche der Bibliothek [xmltodict]. Ihre Methode [parse] erzeugt ausschließlich Zeichenfolgen. Dies lässt sich leicht nachvollziehen:
- Zeile 25: Die Zeichenkette XML, aus der das Wörterbuch generiert wird. In dieser Zeichenkette gibt es keinen Hinweis auf den Typ der in den Tags XML gekapselten Daten. [xmltodict.parse] verhält sich ganz logisch: Sie belässt alles als Zeichenkette im erzeugten Wörterbuch. Es gibt weitere Bibliotheken, die [xmltodict] ähneln, bei denen der Typ der gekapselten Daten in den Tags angegeben ist. Man könnte beispielsweise das Tag [<enfants type=’int’>2</enfants>] finden;
- Dies hat zur Folge, dass man bei der Verwendung eines vom Modul [xmltodict] erstellten Wörterbuchs den Typ der darin gekapselten Daten kennen muss, um vom Typ „str“ zum tatsächlichen Datentyp zu gelangen;
Wenden wir uns nun der Methode [ordereddict2dict] zu, die einen Typ [OrderedDict] in einen Typ [dict] umwandelt:
# xmltodict.parse, um von XML zum Wörterbuch zu wechseln. Das Wörterbuch muss eine Wurzel haben
# Das erzeugte Wörterbuch ist vom Typ OrderedDict
# xmltodict.unparse, um vom Wörterbuch zu XML zu wechseln
def check(value):
# Wenn der Wert vom Typ OrderedDict ist, wird er umgewandelt
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# Wenn der Wert vom Typ „Liste“ ist, wird er umgewandelt
elif isinstance(value, list):
value2 = list2list(value)
else:
# es handelt sich um einen einfachen Typ, nicht um eine Sammlung
value2 = value
# der neue Wert wird zurückgegeben
return value2
def list2list(liste: list) -> list:
# die neue Liste
newliste = []
# die Elemente der Parameterliste werden ausgewertet
for value in liste:
# „value“ wird zur neuen Liste hinzugefügt
newliste.append(check(value))
# gibt die neue Liste zurück
return newliste
def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
# OrderedDict -> als Wörterbuch zurück
newdict = {}
for key, value in ordered_dictionary.items():
# Der Wert wird im neuen Wörterbuch gespeichert
newdict[key] = check(value)
# Das Wörterbuch wird zurückgegeben
return newdict
- Zeile 30: Die Funktion [ordereddict2dict] erhält einen Typ [OrderedDict] als Parameter;
- Zeile 32: Das Wörterbuch vom Typ [dict], das in Zeile 37 von der Funktion zurückgegeben wird;
- Zeile 33: Es werden alle (Schlüssel, Wert)-Tupel des Dictionaries [ordered_dictionary] durchlaufen;
- Zeile 35: Im neuen Wörterbuch wird der Schlüssel [key] beibehalten, der zugehörige Wert ist jedoch nicht [value], sondern [check(value)]. Die Funktion [check(value)] hat die Aufgabe, sofern [value] eine Sammlung ist, alle Elemente vom Typ [OrderedDict] zu finden und sie in den Typ [dict] umzuwandeln;
Die Methode [check] ist in den Zeilen 5–16 definiert:
- Zeile 5: Der Typ von [value] ist unbekannt, daher konnte [value : type] nicht geschrieben werden;
- Zeilen 7–8: Wenn [value] vom Typ [OrderedDict] ist, wird die soeben kommentierte Funktion [ordereddict2dict] rekursiv aufgerufen;
- Zeilen 9–11: Ein weiterer möglicher Fall ist, dass [value] eine Liste ist. In diesem Fall wird in Zeile 11 die Funktion [list2list] aus den Zeilen 19–27 aufgerufen;
- Zeilen 12–14: Im letzten Fall handelt es sich bei [value] nicht um eine Sammlung, sondern um einen einfachen Typ. Die Funktion [check] ist ebenso wie die Funktionen [ordereddict2dict] und [list2list] rekursiv. Es ist bekannt, dass in diesem Fall immer der Fall berücksichtigt werden muss, in dem die Rekursion endet. Die Zeilen 12–14 behandeln diesen Fall;
- Zeile 16: Die Funktion [check], unabhängig davon, ob sie rekursiv aufgerufen wird oder nicht, liefert einen Wert [valeur2], der den Parameter [value] in Zeile 5 ersetzen muss;
Die in den Zeilen 19–27 definierte Methode [list2list] verarbeitet eine als Parameter übergebene Liste. Sie durchläuft diese und ersetzt jeden darin gefundenen Wert vom Typ [OrderedDict] durch einen Wert vom Typ [dict].
- Zeile 21: die neue Liste, die die Funktion erstellen wird;
- Zeilen 23–25: Alle Werte vom Typ „[value]“ in der Liste werden durchsucht und durch den Wert „[check(value)]“ ersetzt. Dieser Wert [value] kann seinerseits Elemente vom Typ [list] oder [OrderedDict] enthalten. Diese werden von der rekursiven Funktion [check] korrekt verarbeitet;