IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction à Python 3 et au framework web Flask par l'exemple


précédentsommairesuivant

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 :

 
Sélectionnez
1.
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 :

Image non disponible

Le script [xml_01] est le suivant :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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 :

 
Sélectionnez
1.
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] :

 
Sélectionnez
1.
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] ;


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2020 Developpez.com.