26. Du dictionnaire à XML et vice-versa▲
Nous nous proposons ici de découvrir le module [xml2dict] qui permet de transformer :
-
une chaîne XML en dictionnaire :
-
un dictionnaire en chaîne XML ;
Avant l’avènement du jSON, la réponse des services web était souvent du XML (eXtended Markup Language). Par ailleurs, le protocole de ces services web était souvent SOAP (Simple Object Process Protocol). SOAP est un protocole qui s’appuie sur le protocole HTTP du web. Actuellement (2020), les services web sont plutôt de type REST (Representational State Transfer). Les services web que nous avons étudiés ne sont d’aucun de ces types mais sont définitivement plus proches de REST que de SOAP. Néanmoins je préfère dire qu’ils sont de type ‘libre’ ou ‘inconnu’ car ils ne respectent pas toutes les règles du REST.
Nous allons montrer combien il est facile de transformer nos architectures client / serveur jSON en architectures client / serveur XML. Il suffit d’utiliser le module [xmltodict].
Nous commençons par l’installer dans un terminal Python :
2.
3.
4.
5.
(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
Ceci fait, nous allons étudier sur un exemple ce qu’on peut faire avec ce module :

Le script [xml_01] est le suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
from
collections import
OrderedDict
import
xmltodict
# xmltodict.parse pour passer du XML au dictionnaire. Le dictionnaire doit avoir une racine
# le dictionnaire produit est de type OrderedDict
# xmltodict.unparse pour passer du dictionnaire au 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
]}]}})
-
lignes 14-25 : la fonction [transform] reçoit un texte à écrire [message] et un dictionnaire [dictionary] ;
-
ligne 16 : affichage du message ;
-
ligne 17 : on affiche le dictionnaire reçu ;
-
lignes 19-20 : ce dictionnaire est transformé en chaîne XML et celle-ci est affichée. La méthode qui sait faire cela est [xmltodict.unparse] ;
-
lignes 21-23 : la chaîne XML précédente est transformée en dictionnaire et celui-ci est affiché. La méthode qui sait faire cela est [xmltodict.parse]. Cette méthode ne produit pas un dictionnaire de type [dict] mais de type [OrderedDict] (ligne 1) ;
-
lignes 24-25 : on transforme le type [OrderedDict] obtenu en type [dict] à l’aide de la méthode (non encore écrite) [ordereddict2dict]. Cette méthode travaille de façon récursive. Si certaines valeurs du dictionnaire sont de type [OrderedDict, list], les valeurs de ces collections sont examinées pour savoir si elles-aussi sont de type [OrderedDict]. Si c’est le cas, elles sont transformées en type [dict]. On remarquera que la méthode [xmltodict.parse] ne produit aucun dictionnaire de type [dict] ;
Avant d’examiner les fonctions manquantes, examinons les résultats pour voir ce qui est cherché :
Le test 1 (lignes 28-29) produit les résultats suivants :
2.
3.
4.
5.
6.
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é'}
-
ligne 2 : le dictionnaire testé. Il faut noter un point important : la méthode [xml2dict.unparse] exisge que le dictionnaire soit de la forme {'clé' : valeur} où [valeur] peut être ensuite un dictionnaire, une liste, un type simple ;
-
lignes 3-4 : la chaîne XML issue du dictionnaire. Elle est précédée de l’entête [<?xml version="1.0" encoding="utf-8"?>\n] qui est normalement la 1ère ligne d’un fichier XML ;
-
ligne 5 : le type [OrderedDict] obtenu par la méthode [xml2dict.parse] recevant pour paramètre la chaîne XML précédente ;
-
ligne 6 : le dictionnaire de type [dict] obtenu en appliquant la méthode [ordereddict2dict] au type précédent. On retrouve le dictionnaire d’origine de la ligne 2 ;
Tous les autres tests sont construits sur le même schéma et devraient vous permettre de comprendre comment passer d’un dictionnaire à une chaîne XML puis de cette chaîne XML au dictionnaire d’origine.
Les autres tests donnent les résultats suivants :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
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
-
les lignes 23 et 27 montrent un point important :
-
ligne 23 : les valeurs associées aux clés du dictionnaire [result] sont des nombres ;
-
ligne 26 : les valeurs associées aux clés du dictionnaire [ordereddict_dictionary1] sont des chaînes de caractères. C’est une faiblesse de la bibliothèque [xmltodict]. Sa méthode [parse] ne produit que des chaînes de caractères. Cela peut se comprendre aisément :
-
ligne 25 : la chaîne XML à partir de laquelle est produit le dictionnaire. Dans cette chaîne, il n’y a aucune indication du type des données encapsulées dans les balises XML. [xmltodict.parse] fait ce qu’il y a de plus logique : elle laisse tout en chaîne de caractères dans le dictionnaire produit. On trouve d’autres bibliothèques similaires à [xmltodict] où le type des données encapsulées est indiqué dans les balises. On pourrait trouver par exemple la balise [<enfants type='int'>2</enfants>] ;
-
la conséquence de ceci est que lorsqu’on exploite un dictionnaire produit par le module [xmltodict] on doit connaître le type des données qu’il encapsule pour pouvoir passer du type ‘str’ au type réel de la donnée ;
-
-
Attardons-nous maintenant sur la méthode [ordereddict2dict] qui transforme un type [OrderedDict] en type [dict] :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
# xmltodict.parse pour passer du XML au dictionnaire. Le dictionnaire doit avoir une racine
# le dictionnaire produit est de type OrderedDict
# xmltodict.unparse pour passer du dictionnaire au XML
def
check
(
value):
# si la valeur est de type OrderedDict, on la transforme
if
isinstance(
value, OrderedDict):
value2 =
ordereddict2dict
(
value)
# si la valeur est de type list, on la transforme
elif
isinstance(
value, list):
value2 =
list2list
(
value)
else
:
# on est en présence d'un type simple pas d'une collection
value2 =
value
# on rend la nouvelle valeur
return
value2
def
list2list
(
liste: list) ->
list:
# la nouvelle liste
newliste =
[]
# on exploite les éléments de la liste paramètre
for
value in
liste:
# on ajoute value à la nouvelle liste
newliste.append
(
check
(
value))
# on rend la nouvelle liste
return
newliste
def
ordereddict2dict
(
ordered_dictionary: OrderedDict) ->
dict:
# OrderedDict -> dict de façon récursive
newdict =
{}
for
key, value in
ordered_dictionary.items
(
):
# on mémorise la valeur dans le nouveau dictionnaire
newdict[key] =
check
(
value)
# on rend le dictionnaire
return
newdict
-
ligne 30 : la fonction [ordereddict2dict] reçoit un type [OrderedDict] comme paramètre ;
-
ligne 32 : le dictionnaire de type [dict] qui sera rendu ligne 37 par la fonction ;
-
ligne 33 : on explore tous les tuples (clé, valeur) du dictionnaire [ordered_dictionary] ;
-
ligne 35 : dans le nouveau dictionnaire, la clé [key] est gardée mais la valeur associée n’est pas [value] mais [check(value)]. La fonction [check(value)] est chargée de trouver, si [value] est une collection, tous les éléments de type [OrderedDict] et de les transformer en type [dict] ;
La méthode [check] est définie aux lignes 5-16 :
-
ligne 5 : on ne connaît pas le type de [value], aussi n’a-t-on pu écrire [value : type] ;
-
lignes 7-8 : si [value] est de type [OrderedDict] alors on appelle de façon récursive la fonction [ordereddict2dict] qu’on vient de commenter ;
-
lignes 9-11 : un autre cas possible est que [value] soit une liste. Dans ce cas, ligne 11, on appelle la fonction [list2list] des 19-27 ;
-
lignes 12-14 : le dernier cas est que [value] n’est pas une collection mais un type simple. La fonction [check], comme les fonctions [ordereddict2dict] et [list2list] sont récursives. On sait qu’alors il faut toujours prévoir le cas où la récursion s’arrête. Les lignes 12-14 sont ce cas ;
-
ligne 16 : la fonction [check] appelée récursivement ou pas produit une valeur [valeur2] qui doit remplacer le paramètre [value] de la ligne 5 ;
La méthode [list2list] définie aux lignes 19-27 exploite une liste passée en paramètre. Elle va l’explorer et remplacer toute valeur de type [OrderedDict] trouvée dedans en un type [dict].
-
ligne 21 : la nouvelle liste que va créer la fonction ;
-
lignes 23-25 : toutes les valeurs [value] de la liste sont explorées et remplacées par la valeur [check(value)]. Cette valeur [value] peut elle-même contenir des éléments de type [list] ou [OrderedDict]. Ils seront traités correctement par la fonction récursive [check] ;