26. Dal dizionario a XML e viceversa
In questa sede ci proponiamo di scoprire il modulo [xml2dict] che permette di trasformare:
- una stringa XML in un dizionario:
- un dizionario in una stringa XML;
Prima dell’avvento del jSON, la risposta dei servizi web era spesso in formato XML (eXtended Markup Language). Inoltre, il protocollo di questi servizi web era spesso SOAP (Simple Object Process Protocol). SOAP è un protocollo basato sul protocollo web HTTP. Attualmente (2020), i servizi web sono prevalentemente di tipo REST (Representational State Transfer). I servizi web che abbiamo studiato non rientrano in nessuna di queste categorie, ma sono decisamente più vicini al REST che al SOAP. Tuttavia, preferisco definirli di tipo «libero» o «sconosciuto», poiché non rispettano tutte le regole del REST.
Mostreremo quanto sia facile trasformare le nostre architetture client/server jSON in architetture client/server XML. È sufficiente utilizzare il modulo [xmltodict].
Iniziamo installandolo in un terminale Python:
(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
Fatto ciò, vedremo con un esempio cosa si può fare con questo modulo:

Lo script [xml_01] è il seguente:
from collections import OrderedDict
import xmltodict
# xmltodict.parse per passare da XML al dizionario. Il dizionario deve avere una radice
# il dizionario generato è di tipo OrderedDict
# xmltodict.unparse per passare dal dizionario a XML
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# log
print(f"\n{message}-------")
print(f"dictionnaire={dictionary}")
# dizionario -> xml
xml1 = xmltodict.unparse(dictionary)
print(f"xml={xml1}")
# xml -> OrderedDict
ordereddict_dictionary1 = xmltodict.parse(xml1)
print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
# OrderedDict -> dizionario
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': {
'errori': ['È richiesto il metodo GET con i soli parametri [marié, enfants, salaire]', 'parametro [marié] mancante',
'parametro [enfants] mancante', 'parametro [salaire] mancante']}})
# test 5
transform("test 5", {'réponse': {
'risultato': {'id': 0, 'coniugato': 'sì', 'figli': 2, 'stipendio': 50000, 'imposta': 1384, 'riduzione': 384, 'maggiorazione': 0,
'riduzione': 347, 'aliquota': 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]}]}})
- righe 14-25: la funzione [transform] riceve un testo da scrivere [message] e un dizionario [dictionary];
- riga 16: visualizzazione del messaggio;
- riga 17: viene visualizzato il dizionario ricevuto;
- righe 19-20: questo dizionario viene trasformato nella stringa XML e questa viene visualizzata. Il metodo che esegue questa operazione è [xmltodict.unparse];
- righe 21-23: la stringa precedente XML viene trasformata in un dizionario e quest'ultimo viene visualizzato. Il metodo che esegue questa operazione è [xmltodict.parse]. Questo metodo non produce un dizionario di tipo [dict], bensì di tipo [OrderedDict] (riga 1);
- righe 24-25: il tipo [OrderedDict] ottenuto viene convertito in tipo [dict] utilizzando il metodo (non ancora scritto) [ordereddict2dict]. Questo metodo opera in modo ricorsivo. Se alcuni valori del dizionario sono di tipo [OrderedDict, list], i valori di queste collezioni vengono esaminati per verificare se anch’essi siano di tipo [OrderedDict]. In tal caso, vengono trasformati nel tipo [dict]. Si noti che il metodo [xmltodict.parse] non produce alcun dizionario di tipo [dict];
Prima di esaminare le funzioni mancanti, analizziamo i risultati per capire cosa si sta cercando:
Il test 1 (righe 28-29) produce i seguenti risultati:
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é'}
- riga 2: il dizionario testato. È importante notare che il metodo [xml2dict.unparse] richiede che il dizionario sia nella forma {‘chiave’: valore}, dove [valeur] può essere a sua volta un dizionario, una lista o un tipo semplice;
- righe 3-4: la stringa XML ricavata dal dizionario. È preceduta dall’intestazione [<?xml version="1.0" encoding="utf-8"?>\n], che normalmente costituisce la prima riga di un file XML;
- riga 5: il tipo [OrderedDict] ottenuto tramite il metodo [xml2dict.parse] che riceve come parametro la stringa XML precedente;
- riga 6: il dizionario di tipo [dict] ottenuto applicando il metodo [ordereddict2dict] al tipo precedente. Si ritrova il dizionario originale della riga 2;
Tutti gli altri test sono strutturati secondo lo stesso schema e dovrebbero consentirvi di comprendere come passare da un dizionario a una stringa XML e poi da questa stringa XML al dizionario originale.
Gli altri test danno i seguenti risultati:
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
- le righe 23 e 27 evidenziano un punto importante:
- riga 23: i valori associati alle chiavi del dizionario [result] sono numeri;
- riga 26: i valori associati alle chiavi del dizionario [ordereddict_dictionary1] sono stringhe di caratteri. Si tratta di un punto debole della libreria [xmltodict]. Il suo metodo [parse] produce solo stringhe di caratteri. Ciò è facilmente comprensibile:
- riga 25: la stringa XML da cui viene generato il dizionario. In questa stringa non vi è alcuna indicazione del tipo di dati incapsulati nei tag XML. [xmltodict.parse] agisce nel modo più logico: lascia tutto come stringa di caratteri nel dizionario generato. Esistono altre librerie simili a [xmltodict] in cui il tipo dei dati incapsulati è indicato nei tag. Si potrebbe trovare, ad esempio, il tag [<enfants type=’int’>2</enfants>];
- la conseguenza di ciò è che quando si utilizza un dizionario generato dal modulo [xmltodict] è necessario conoscere il tipo dei dati che incapsula per poter passare dal tipo ‘str’ al tipo reale del dato;
Soffermiamoci ora sul metodo [ordereddict2dict] che trasforma un tipo [OrderedDict] in un tipo [dict]:
# xmltodict.parse per passare da XML al dizionario. Il dizionario deve avere una radice
# il dizionario generato è di tipo OrderedDict
# xmltodict.unparse per passare dal dizionario a XML
def check(value):
# se il valore è di tipo OrderedDict, lo si trasforma
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# se il valore è di tipo lista, lo si trasforma
elif isinstance(value, list):
value2 = list2list(value)
else:
# si tratta di un tipo semplice, non di una collezione
value2 = value
# si restituisce il nuovo valore
return value2
def list2list(liste: list) -> list:
# la nuova lista
newliste = []
# si utilizzano gli elementi della lista parametro
for value in liste:
# si aggiunge value alla nuova lista
newliste.append(check(value))
# si restituisce la nuova lista
return newliste
def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
# OrderedDict -> dizionario in modo ricorsivo
newdict = {}
for key, value in ordered_dictionary.items():
# si memorizza il valore nel nuovo dizionario
newdict[key] = check(value)
# si restituisce il dizionario
return newdict
- riga 30: la funzione [ordereddict2dict] riceve come parametro un tipo [OrderedDict];
- riga 32: il dizionario di tipo [dict] che verrà restituito alla riga 37 dalla funzione;
- riga 33: vengono esaminate tutte le tuple (chiave, valore) del dizionario [ordered_dictionary];
- riga 35: nel nuovo dizionario, la chiave [key] viene mantenuta, ma il valore associato non è [value] bensì [check(value)]. La funzione [check(value)] ha il compito di individuare, se [value] è una collezione, tutti gli elementi di tipo [OrderedDict] e di trasformarli nel tipo [dict];
Il metodo [check] è definito alle righe 5-16:
- riga 5: non si conosce il tipo di [value], quindi non è stato possibile scrivere [value : type];
- righe 7-8: se [value] è di tipo [OrderedDict], allora si chiama in modo ricorsivo la funzione [ordereddict2dict] che è stata appena commentata;
- righe 9-11: un altro caso possibile è che [value] sia una lista. In questo caso, alla riga 11, si chiama la funzione [list2list] delle righe 19-27;
- righe 12-14: l'ultimo caso è quello in cui [value] non è una collezione, ma un tipo semplice. La funzione [check], così come le funzioni [ordereddict2dict] e [list2list], è ricorsiva. È noto che in tal caso occorre sempre prevedere il caso in cui la ricorsione si interrompa. Le righe 12-14 rappresentano proprio questo caso;
- riga 16: la funzione [check], chiamata in modo ricorsivo o meno, produce un valore [valeur2] che deve sostituire il parametro [value] della riga 5;
Il metodo [list2list], definito alle righe 19-27, utilizza una lista passata come parametro. Esplorerà tale lista e sostituirà ogni valore di tipo [OrderedDict] in essa contenuto con un valore di tipo [dict].
- riga 21: il nuovo elenco che verrà creato dalla funzione;
- righe 23-25: tutti i valori [value] dell’elenco vengono esaminati e sostituiti con il valore [check(value)]. Questo valore [value] può a sua volta contenere elementi di tipo [list] o [OrderedDict]. Essi saranno trattati correttamente dalla funzione ricorsiva [check];