Skip to content

26. Van het woordenboek naar XML en vice versa

We gaan hier de module [xml2dict] verkennen, waarmee je:

  • een tekenreeks XML in een woordenboek:
  • een woordenboek in een tekenreeks XML;

Vóór de komst van jSON bestond het antwoord van webservices vaak uit XML (eXtended Markup Language). Bovendien was het protocol van deze webservices vaak SOAP (Simple Object Process Protocol). SOAP is een protocol dat is gebaseerd op het HTTP-protocol van het web. Momenteel (2020) zijn webservices meestal van het type REST (Representational State Transfer). De webservices die we hebben onderzocht, behoren tot geen van deze typen, maar lijken duidelijk meer op REST dan op SOAP. Toch geef ik er de voorkeur aan om te zeggen dat ze van het ‘vrije’ of ‘onbekende’ type zijn, omdat ze niet alle regels van het REST-type naleven.

We gaan laten zien hoe eenvoudig het is om onze jSON-client/serverarchitecturen om te zetten in XML-client/serverarchitecturen. Hiervoor volstaat het om de module [xmltodict] te gebruiken.

We beginnen met het te installeren in een 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

Zodra dit is gebeurd, gaan we aan de hand van een voorbeeld bekijken wat we met deze module kunnen doen:

Image

Het script [xml_01] ziet er als volgt uit:


from collections import OrderedDict

import xmltodict


# xmltodict.parse om van XML naar het woordenboek over te gaan. Het woordenboek moet een root hebben
# het gegenereerde woordenboek is van het type OrderedDict
# xmltodict.unparse om van het woordenboek naar XML te gaan

def ordereddict2dict(ordered_dictionary) -> dict:
    


def transform(message: str, dictionary: dict):
    # logbestanden
    print(f"\n{message}-------")
    print(f"dictionnaire={dictionary}")
    # woordenboek -> 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': {
    'fouten': ['Methode GET vereist met uitsluitend de parameters [marié, enfants, salaire]', 'parameter [marié] ontbreekt',
                'parameter [enfants] ontbreekt', 'parameter [salaire] ontbreekt']}})
# test 5
transform("test 5", {'réponse': {
    'resultaat': {'id': 0, 'getrouwd': 'ja', 'kinderen': 2, 'salaris': 50000, 'belasting': 1384, 'korting': 384, 'toeslag': 0,
               'korting': 347, 'tarief': 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]}]}})
  • regels 14-25: de functie [transform] ontvangt een tekst die moet worden geschreven [message] en een woordenboek [dictionary];
  • regel 16: het bericht wordt weergegeven;
  • regel 17: het ontvangen woordenboek wordt weergegeven;
  • regels 19-20: dit woordenboek wordt omgezet in de tekenreeks XML en deze wordt weergegeven. De methode die dit kan uitvoeren is [xmltodict.unparse];
  • regels 21-23: de voorgaande tekenreeks XML wordt omgezet in een woordenboek en dit wordt weergegeven. De methode die dit kan doen is [xmltodict.parse]. Deze methode produceert geen woordenboek van het type [dict], maar van het type [OrderedDict] (regel 1);
  • regels 24-25: het verkregen type [OrderedDict] wordt omgezet naar het type [dict] met behulp van de (nog niet geschreven) methode [ordereddict2dict]. Deze methode werkt recursief. Als bepaalde waarden in het woordenboek van het type [OrderedDict, list] zijn, worden de waarden van deze verzamelingen onderzocht om na te gaan of zij ook van het type [OrderedDict] zijn. Als dat het geval is, worden ze omgezet naar het type [dict]. Opgemerkt moet worden dat de methode [xmltodict.parse] geen woordenboek van het type [dict] oplevert;

Voordat we de ontbrekende functies bekijken, laten we eerst de resultaten bekijken om te zien wat er wordt gezocht:

Test 1 (regels 28-29) levert de volgende resultaten op:


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é'}
  • regel 2: het geteste woordenboek. Er is één belangrijk punt waar u op moet letten: de methode [xml2dict.unparse] vereist dat het woordenboek de vorm {‘sleutel’: waarde} heeft, waarbij [valeur] vervolgens een woordenboek, een lijst of een eenvoudig type kan zijn;
  • regels 3-4: de tekenreeks XML afkomstig uit het woordenboek. Deze wordt voorafgegaan door de koptekst [<?xml version="1.0" encoding="utf-8"?>\n], die normaal gesproken de eerste regel is van een XML-bestand;
  • regel 5: het type [OrderedDict], verkregen door de methode [xml2dict.parse] toe te passen met als parameter de voorgaande tekenreeks XML;
  • regel 6: het woordenboek van het type [dict], verkregen door de methode [ordereddict2dict] toe te passen op het vorige type. Hier zien we het oorspronkelijke woordenboek uit regel 2 terug;

Alle andere tests zijn volgens hetzelfde schema opgebouwd en zouden u moeten helpen begrijpen hoe u van een woordenboek naar een tekenreeks XML kunt gaan en vervolgens van deze tekenreeks XML terug naar het oorspronkelijke woordenboek.

De overige tests leveren de volgende resultaten op:


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
  • de regels 23 en 27 laten een belangrijk punt zien:
    • regel 23: de waarden die aan de sleutels van het woordenboek [result] zijn gekoppeld, zijn getallen;
    • regel 26: de waarden die bij de sleutels van het woordenboek [ordereddict_dictionary1] horen, zijn tekenreeksen. Dit is een zwak punt van de bibliotheek [xmltodict]. De methode [parse] genereert uitsluitend tekenreeksen. Dit is gemakkelijk te begrijpen:
      • regel 25: de tekenreeks XML op basis waarvan het woordenboek wordt gegenereerd. In deze tekenreeks is er geen enkele indicatie van het type gegevens dat in de tags XML is ingekapseld. [xmltodict.parse] doet wat het meest logisch is: het laat alles als tekenreeks in het gegenereerde woordenboek staan. Er zijn andere bibliotheken die vergelijkbaar zijn met [xmltodict], waarbij het type van de ingekapselde gegevens in de tags wordt aangegeven. Men zou bijvoorbeeld de tag [<enfants type=’int’>2</enfants>] kunnen aantreffen;
      • het gevolg hiervan is dat wanneer men een woordenboek gebruikt dat is gegenereerd door de module [xmltodict], men het type van de ingekapselde gegevens moet kennen om van het type ‘str’ naar het werkelijke type van de gegevens te kunnen gaan;

Laten we nu eens kijken naar de methode [ordereddict2dict], die een type [OrderedDict] omzet in het type [dict]:


# xmltodict.parse om van XML naar het woordenboek te gaan. Het woordenboek moet een stam hebben
# het gegenereerde woordenboek is van het type OrderedDict
# xmltodict.unparse om van het woordenboek naar XML te gaan

def check(value):
    # als de waarde van het type OrderedDict is, wordt deze omgezet
    if isinstance(value, OrderedDict):
        value2 = ordereddict2dict(value)
    # als de waarde van het type lijst is, wordt deze omgezet
    elif isinstance(value, list):
        value2 = list2list(value)
    else:
        # het gaat om een eenvoudig type, geen verzameling
        value2 = value
    # de nieuwe waarde wordt weergegeven
    return value2


def list2list(liste: list) -> list:
    # de nieuwe lijst
    newliste = []
    # de elementen van de parameterlijst worden verwerkt
    for value in liste:
        # we voegen value toe aan de nieuwe lijst
        newliste.append(check(value))
    # de nieuwe lijst wordt geretourneerd
    return newliste


def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
    # OrderedDict -> woordenboek op recursieve wijze
    newdict = {}
    for key, value in ordered_dictionary.items():
        # de waarde wordt opgeslagen in het nieuwe woordenboek
        newdict[key] = check(value)
    # we genereren het woordenboek
    return newdict
  • regel 30: de functie [ordereddict2dict] ontvangt een type [OrderedDict] als parameter;
  • regel 32: het woordenboek van het type [dict] dat in regel 37 door de functie wordt geretourneerd;
  • regel 33: alle (sleutel, waarde)-tuples van het woordenboek [ordered_dictionary] worden doorlopen;
  • regel 35: in het nieuwe woordenboek wordt de sleutel [key] behouden, maar de bijbehorende waarde is niet [value], maar [check(value)]. De functie [check(value)] heeft tot taak om, indien [value] een verzameling is, alle elementen van het type [OrderedDict] te vinden en deze om te zetten naar het type [dict];

De methode [check] wordt gedefinieerd in de regels 5-16:

  • regel 5: het type van [value] is onbekend, dus kon [value : type] niet worden geschreven;
  • regels 7-8: als [value] van het type [OrderedDict] is, dan wordt de functie [ordereddict2dict], die zojuist is uitgecommentarieerd, recursief aangeroepen;
  • regels 9-11: een ander mogelijk geval is dat [value] een lijst is. In dat geval wordt in regel 11 de functie [list2list] uit de regels 19-27 aangeroepen;
  • regels 12-14: het laatste geval is dat [value] geen verzameling is, maar een eenvoudig type. De functie [check] is, net als de functies [ordereddict2dict] en [list2list], recursief. We weten dat we in dat geval altijd rekening moeten houden met het geval waarin de recursie stopt. Regels 12-14 beschrijven dit geval;
  • regel 16: de functie [check], al dan niet recursief aangeroepen, levert een waarde [valeur2] op die de parameter [value] op regel 5 moet vervangen;

De methode [list2list], gedefinieerd in de regels 19-27, verwerkt een lijst die als parameter wordt doorgegeven. Deze methode doorloopt de lijst en vervangt elke waarde van het type [OrderedDict] die daarin wordt aangetroffen door een waarde van het type [dict].

  • regel 21: de nieuwe lijst die de functie gaat aanmaken;
  • regels 23-25: alle waarden van het type [value] in de lijst worden doorlopen en vervangen door de waarde [check(value)]. Deze waarde [value] kan zelf elementen van het type [list] of [OrderedDict] bevatten. Deze worden correct verwerkt door de recursieve functie [check];