Skip to content

26. Vom Wörterbuch zu XML und umgekehrt

Hier sehen wir uns das Modul [xml2dict] an, mit dem Sie Folgendes konvertieren können:

  • einen XML-String in ein Wörterbuch:
  • ein Wörterbuch in eine XML-Zeichenkette;

Vor dem Aufkommen von JSON lagen die Antworten von Webdiensten häufig im XML-Format (eXtended Markup Language) vor. Darüber hinaus war das Protokoll für diese Webdienste oft SOAP (Simple Object Access Protocol). SOAP ist ein Protokoll, das auf dem HTTP-Protokoll des Webs basiert. Derzeit (2020) sind Webdienste meist vom Typ REST (Representational State Transfer). Die von uns untersuchten Webdienste gehören zu keinem dieser Typen, sind aber definitiv näher an REST als an SOAP. Dennoch ziehe ich es vor, sie als vom Typ „frei“ oder „unbekannt“ zu bezeichnen, da sie nicht alle Regeln von REST befolgen.

Wir werden zeigen, wie einfach es ist, unsere JSON-Client/Server-Architekturen in XML-Client/Server-Architekturen umzuwandeln. Dazu müssen Sie lediglich das Modul [xmltodict] verwenden.

Wir beginnen damit, es in einem Python-Terminal zu installieren:


(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

Nachdem dies nun erledigt ist, sehen wir uns ein Beispiel an, was wir mit diesem Modul machen können:

Image

Das Skript [xml_01] sieht wie folgt aus:

from collections import OrderedDict

import xmltodict


#  xmltodict.parse to pass from XML to the dictionary. The dictionary must have a root
#  the dictionary produced is of type OrderedDict
#  xmltodict.unparse to go from the dictionary to XML

def ordereddict2dict(ordered_dictionary) -> dict:
    


def transform(message: str, dictionary: dict):
    #  logs
    print(f"\n{message}-------")
    print(f"dictionnaire={dictionary}")
    #  dict -> xml
    xml1 = xmltodict.unparse(dictionary)
    print(f"xml={xml1}")
    #  xml -> OrderedDict
    ordereddict_dictionary1 = xmltodict.parse(xml1)
    print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
    #  OrderedDict -> dict
    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': {
    '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
transform("test 5", {'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
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] nimmt einen zu schreibenden Text [message] und ein Wörterbuch [dictionary] entgegen;
  • Zeile 16: zeigt die Nachricht an;
  • Zeile 17: Das empfangene Wörterbuch wird angezeigt;
  • Zeilen 19–20: Dieses Wörterbuch wird in eine XML-Zeichenkette umgewandelt, die anschließend angezeigt wird. Die Methode, die diesen Vorgang ausführt, ist [xmltodict.unparse];
  • Zeilen 21–23: Die vorstehende XML-Zeichenkette wird in ein Wörterbuch umgewandelt, das anschließend angezeigt wird. Die Methode, die diese Aufgabe ausführt, ist [xmltodict.parse]. Diese Methode erzeugt kein Wörterbuch vom Typ [dict], sondern vom Typ [OrderedDict] (Zeile 1);
  • Zeilen 24–25: Der resultierende Typ [OrderedDict] wird mithilfe der (noch nicht geschriebenen) Methode [ordereddict2dict] in [dict] umgewandelt. Diese Methode arbeitet rekursiv. Sind bestimmte Werte im Wörterbuch vom Typ [OrderedDict, list], werden die Werte dieser Sammlungen untersucht, um festzustellen, ob auch sie vom Typ [OrderedDict] sind. Ist dies der Fall, werden sie in den Typ [dict] konvertiert. Beachten Sie, dass die Methode [xmltodict.parse] keine Wörterbücher vom Typ [dict] erzeugt;

Bevor wir uns die fehlenden Funktionen ansehen, werfen wir einen Blick auf die Ergebnisse, um zu sehen, wonach wir suchen:

Test 1 (Zeilen 28–29) liefert die folgenden 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 zu testende Wörterbuch. Ein wichtiger Hinweis: Die Methode [xml2dict.unparse] erfordert, dass das Wörterbuch die Form {‘key’: value} hat, wobei [value] ein Wörterbuch, eine Liste oder ein einfacher Typ sein kann;
  • Zeilen 3–4: Die aus dem Wörterbuch generierte XML-Zeichenkette. Ihr ist der Header [<?xml version="1.0" encoding="utf-8"?>\n] vorangestellt, der normalerweise die erste Zeile einer XML-Datei bildet;
  • Zeile 5: Der Typ [OrderedDict], der durch die Methode [xml2dict.parse] erhalten wird, die die vorangehende XML-Zeichenkette als Parameter verwendet;
  • Zeile 6: das Wörterbuch vom Typ [dict], das durch Anwendung der Methode [ordereddict2dict] auf den vorherigen Typ erhalten wird. Dies ist das ursprüngliche Wörterbuch aus Zeile 2;

Alle anderen Tests folgen dem gleichen Muster und sollen Ihnen helfen zu verstehen, wie man von einem Wörterbuch in eine XML-Zeichenkette und dann von dieser XML-Zeichenkette zurück zum ursprünglichen Wörterbuch konvertiert.

Die anderen Tests liefern die folgenden 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 Werte, die den Schlüsseln des Wörterbuchs [result] zugeordnet sind, sind Zahlen;
    • Zeile 26: Die Werte, die den Schlüsseln des Wörterbuchs [ordereddict_dictionary1] zugeordnet sind, sind Zeichenfolgen. Dies ist eine Einschränkung der Bibliothek [xmltodict]. Ihre Methode [parse] erzeugt ausschließlich Zeichenfolgen. Dies ist leicht nachvollziehbar:
      • Zeile 25: die XML-Zeichenkette, aus der das Wörterbuch generiert wird. In dieser Zeichenkette gibt es keinen Hinweis auf den Datentyp, der in den XML-Tags gekapselt ist. [xmltodict.parse] tut das, was am sinnvollsten ist: Es belässt alles als Zeichenketten im resultierenden Wörterbuch. Es gibt andere Bibliotheken ähnlich wie [xmltodict], bei denen der Typ der gekapselten Daten in den Tags angegeben wird. Man könnte beispielsweise den Tag [<children type='int'>2</children>] finden;
      • Dies hat zur Folge, dass man bei der Verwendung eines vom Modul [xmltodict] erzeugten Wörterbuchs den Typ der darin enthaltenen Daten kennen muss, um vom Typ „str“ in den tatsächlichen Datentyp zu konvertieren;

Sehen wir uns nun die Methode [ordereddict2dict] genauer an, die einen Typ [OrderedDict] in einen Typ [dict] konvertiert:

#  xmltodict.parse to pass from XML to the dictionary. The dictionary must have a root
#  the dictionary produced is of type OrderedDict
#  xmltodict.unparse to go from the dictionary to XML

def check(value):
    #  if the value is of type OrderedDict, we transform it
    if isinstance(value, OrderedDict):
        value2 = ordereddict2dict(value)
    #  if the value is of type list, we transform it
    elif isinstance(value, list):
        value2 = list2list(value)
    else:
        #  we're dealing with a simple type, not a collection
        value2 = value
    #  we return the new value
    return value2


def list2list(liste: list) -> list:
    #  the new list
    newliste = []
    #  the elements of the parameter list are used
    for value in liste:
        #  add value to the new list
        newliste.append(check(value))
    #  return the new list
    return newliste


def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
    #  OrderedDict -> recursive dictation
    newdict = {}
    for key, value in ordered_dictionary.items():
        #  store the value in the new dictionary
        newdict[key] = check(value)
    #  we return the dictionary
    return newdict
  • Zeile 30: Die Funktion [ordereddict2dict] nimmt einen Typ [OrderedDict] als Parameter entgegen;
  • Zeile 32: das Wörterbuch vom Typ [dict], das in Zeile 37 von der Funktion zurückgegeben wird;
  • Zeile 33: Wir durchlaufen alle (Schlüssel, Wert)-Tupel im Wörterbuch [ordered_dictionary];
  • Zeile 35: Im neuen Wörterbuch wird der Schlüssel [key] beibehalten, aber der zugehörige Wert ist nicht [value], sondern [check(value)]. Die Funktion [check(value)] ist dafür zuständig, falls [value] eine Sammlung ist, alle Elemente vom Typ [OrderedDict] zu finden und sie in den Typ [dict] zu konvertieren;

Die Methode [check] ist in den Zeilen 5–16 definiert:

  • Zeile 5: Wir kennen den Typ von [value] nicht, daher konnten wir nicht [value: type] schreiben;
  • Zeilen 7–8: Wenn [value] vom Typ [OrderedDict] ist, rufen wir rekursiv die Funktion [ordereddict2dict] auf, die wir gerade auskommentiert haben;
  • Zeilen 9–11: Ein weiterer möglicher Fall ist, dass [value] eine Liste ist. In diesem Fall rufen wir in Zeile 11 die Funktion [list2list] aus den Zeilen 19–27 auf;
  • Zeilen 12–14: Der letzte Fall ist, dass [value] keine Sammlung, sondern ein einfacher Typ ist. Die Funktion [check] ist, wie die Funktionen [ordereddict2dict] und [list2list], rekursiv. Wir wissen, dass wir in diesem Fall immer die Situation behandeln müssen, in der die Rekursion endet. Die Zeilen 12–14 behandeln diesen Fall;
  • Zeile 16: Die Funktion [check] erzeugt, unabhängig davon, ob sie rekursiv aufgerufen wird oder nicht, einen Wert [value2], 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 alle darin gefundenen [OrderedDict]-Werte durch einen Typ [dict].

  • Zeile 21: die neue Liste, die die Funktion erstellen wird;
  • Zeilen 23–25: Alle [value]-Werte in der Liste werden durchlaufen und durch den Wert [check(value)] ersetzt. Dieser [value] kann selbst Elemente vom Typ [list] oder [OrderedDict] enthalten. Diese werden von der rekursiven Funktion [check] korrekt verarbeitet;