26. Dal dizionario all'XML e viceversa
Qui esploreremo il modulo [xml2dict], che consente di convertire:
- una stringa XML in un dizionario:
- un dizionario in una stringa XML;
Prima dell'avvento di JSON, le risposte dei servizi web erano spesso in formato XML (eXtended Markup Language). Inoltre, il protocollo utilizzato per questi servizi web era spesso SOAP (Simple Object Access Protocol). SOAP è un protocollo basato sul protocollo HTTP del web. Attualmente (2020), i servizi web sono per lo più di tipo REST (Representational State Transfer). I servizi web che abbiamo studiato non sono di nessuno di questi tipi, ma sono sicuramente più vicini a REST che a SOAP. Tuttavia, preferisco dire che sono di tipo "libero" o "sconosciuto" perché non seguono tutte le regole di REST.
Mostreremo quanto sia facile trasformare le nostre architetture client/server JSON in architetture client/server XML. Tutto ciò che occorre fare è utilizzare il modulo [xmltodict].
Inizieremo 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
Ora che abbiamo finito, vediamo un esempio di cosa possiamo fare con questo modulo:

Lo script [xml_01] è il seguente:
- righe 14–25: la funzione [transform] accetta un testo da scrivere [message] e un dizionario [dictionary];
- riga 16: visualizza il messaggio;
- riga 17: viene visualizzato il dizionario ricevuto;
- righe 19–20: questo dizionario viene convertito in una stringa XML, che viene poi visualizzata. Il metodo che esegue questa operazione è [xmltodict.unparse];
- righe 21–23: la stringa XML precedente viene convertita in un dizionario, che viene poi visualizzato. Il metodo che esegue questa operazione è [xmltodict.parse]. Questo metodo non produce un dizionario di tipo [dict] ma di tipo [OrderedDict] (riga 1);
- righe 24–25: il tipo [OrderedDict] risultante viene convertito in [dict] utilizzando il metodo (non ancora scritto) [ordereddict2dict]. Questo metodo funziona in modo ricorsivo. Se alcuni valori nel dizionario sono di tipo [OrderedDict, list], i valori di queste collezioni vengono esaminati per determinare se anche loro sono di tipo [OrderedDict]. In tal caso, vengono convertiti in tipo [dict]. Si noti che il metodo [xmltodict.parse] non produce alcun dizionario di tipo [dict];
Prima di esaminare le funzioni mancanti, diamo un'occhiata ai risultati per vedere cosa stiamo 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 sottoposto a test. Nota importante: il metodo [xml2dict.unparse] richiede che il dizionario sia nella forma {‘key’: value}, dove [value] può essere un dizionario, una lista o un tipo semplice;
- Righe 3–4: la stringa XML generata dal dizionario. È preceduta dall'intestazione [<?xml version="1.0" encoding="utf-8"?>\n], che normalmente è la prima riga di un file XML;
- riga 5: il tipo [OrderedDict] ottenuto tramite il metodo [xml2dict.parse], che accetta come parametro la stringa XML precedente;
- riga 6: il dizionario di tipo [dict] ottenuto applicando il metodo [ordereddict2dict] al tipo precedente. Si tratta del dizionario originale della riga 2;
Tutti gli altri test seguono lo stesso schema e dovrebbero aiutarti a capire come convertire un dizionario in una stringa XML e poi da quella stringa XML di nuovo nel dizionario originale.
Gli altri test producono 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. Questa è una limitazione della libreria [xmltodict]. Il suo metodo [parse] produce solo stringhe. Questo è facilmente comprensibile:
- riga 25: la stringa XML da cui viene generato il dizionario. In questa stringa, non vi è alcuna indicazione del tipo di dati incapsulato all'interno dei tag XML. [xmltodict.parse] fa ciò che ha più senso: lascia tutto come stringhe nel dizionario risultante. Esistono altre librerie simili a [xmltodict] in cui il tipo dei dati incapsulati è specificato nei tag. Ad esempio, si potrebbe trovare il tag [<children type='int'>2</children>];
- la conseguenza di ciò è che quando si utilizza un dizionario prodotto dal modulo [xmltodict], è necessario conoscere il tipo dei dati che incapsula per poter convertire il tipo ‘str’ nel tipo di dati effettivo;
Diamo ora un'occhiata più da vicino al metodo [ordereddict2dict], che converte un tipo [OrderedDict] in un tipo [dict]:
- riga 30: la funzione [ordereddict2dict] accetta un tipo [OrderedDict] come parametro;
- riga 32: il dizionario di tipo [dict] che verrà restituito alla riga 37 dalla funzione;
- riga 33: si esegue un'iterazione su tutte le tuple (chiave, valore) nel 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 convertirli nel tipo [dict];
Il metodo [check] è definito alle righe 5–16:
- riga 5: non conosciamo il tipo di [value], quindi non potremmo scrivere [value: type];
- righe 7–8: se [value] è di tipo [OrderedDict], allora chiamiamo ricorsivamente la funzione [ordereddict2dict] che abbiamo appena commentato;
- righe 9–11: un altro caso possibile è che [value] sia una lista. In questo caso, alla riga 11, chiamiamo la funzione [list2list] delle righe 19–27;
- righe 12–14: il caso finale è che [value] non sia una collezione ma un tipo semplice. La funzione [check], come le funzioni [ordereddict2dict] e [list2list], è ricorsiva. Sappiamo che in questo caso dobbiamo sempre gestire la situazione in cui la ricorsione termina. Le righe 12–14 gestiscono questo caso;
- riga 16: la funzione [check], sia che venga chiamata in modo ricorsivo o meno, produce un valore [value2] che deve sostituire il parametro [value] nella riga 5;
Il metodo [list2list] definito nelle righe 19–27 elabora una lista passata come parametro. La percorrerà e sostituirà qualsiasi valore [OrderedDict] trovato al suo interno con un tipo [dict].
- riga 21: la nuova lista che la funzione creerà;
- Righe 23–25: tutti i valori [value] nella lista vengono attraversati e sostituiti con il valore [check(value)]. Questo [value] può a sua volta contenere elementi di tipo [list] o [OrderedDict]. Essi saranno gestiti correttamente dalla funzione ricorsiva [check];