26. Do dicionário para XML e vice-versa
Propomos aqui explorar o módulo [xml2dict], que permite transformar:
- uma sequência XML em um dicionário:
- um dicionário em uma sequência XML;
Antes do surgimento do jSON, a resposta dos serviços web costumava ser em XML (eXtended Markup Language). Além disso, o protocolo desses serviços web costumava ser SOAP (Simple Object Process Protocol). O SOAP é um protocolo baseado no protocolo HTTP da web. Atualmente (2020), os serviços web são, em geral, do tipo REST (Representational State Transfer). Os serviços web que estudamos não se enquadram em nenhum desses tipos, mas estão definitivamente mais próximos do REST do que do SOAP. No entanto, prefiro dizer que são do tipo “livre” ou “desconhecido”, pois não seguem todas as regras do REST.
Vamos mostrar como é fácil transformar nossas arquiteturas cliente/servidor jSON em arquiteturas cliente/servidor XML. Basta usar o módulo [xmltodict].
Começamos instalando-o em um terminal 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
Feito isso, vamos ver, por meio de um exemplo, o que é possível fazer com esse módulo:

O script [xml_01] é o seguinte:
from collections import OrderedDict
import xmltodict
# xmltodict.parse para passar do XML para o dicionário. O dicionário deve ter uma raiz
# o dicionário gerado é do tipo OrderedDict
# xmltodict.unparse para passar do dicionário para o XML
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# logs
print(f"\n{message}-------")
print(f"dictionnaire={dictionary}")
# dicionário -> xml
xml1 = xmltodict.unparse(dictionary)
print(f"xml={xml1}")
# xml -> OrderedDict
ordereddict_dictionary1 = xmltodict.parse(xml1)
print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
# OrderedDict -> dicionário
print(f"dict_dictionary1={ordereddict2dict(ordereddict_dictionary1)}")
# teste 1
transform("test 1", {"nom": "séléné"})
# teste 2
transform("test 2", {"famille": {"père": {"prénom": "andré"}, "mère": {"prénom": "angèle"}, "nom": "séléné"}})
# teste 3
transform("test 3", {"famille": {"nom": "séléné", "père": {"prénom": "andré"}, "mère": {"prénom": "angèle"},
"hobbies": ["chant", "footing"]}})
# teste 4
transform("test 4", {'réponse': {
'erros': ['É necessário o método GET apenas com os parâmetros [marié, enfants, salaire]', 'parâmetro [marié] ausente',
'parâmetro [enfants] ausente', 'parâmetro [salaire] ausente']}})
# teste 5
transform("test 5", {'réponse': {
'resultado': {'id': 0, 'casado': 'sim', 'filhos': 2, 'salário': 50000, 'imposto': 1384, 'abatimento': 384, 'sobretaxa': 0,
'redução': 347, 'taxa': 0,14}}})
# teste 6
transform("test 6", {"root": {'liste': ["un", "deux", "trois"]}})
# teste 7
transform("test 7", {"root": {'liste': [{"un": [10, 11]}, {"deux": [20, 21]}, {"trois": [30, 31]}]}})
- linhas 14-25: a função [transform] recebe um texto a ser exibido [message] e um dicionário [dictionary];
- linha 16: exibição da mensagem;
- linha 17: exibe-se o dicionário recebido;
- linhas 19-20: esse dicionário é transformado na sequência XML, que é exibida. O método responsável por isso é [xmltodict.unparse];
- linhas 21-23: a sequência anterior XML é convertida em um dicionário, e este é exibido. O método responsável por isso é o [xmltodict.parse]. Esse método não produz um dicionário do tipo [dict], mas sim do tipo [OrderedDict] (linha 1);
- linhas 24-25: o tipo [OrderedDict] obtido é transformado no tipo [dict] por meio do método (ainda não escrito) [ordereddict2dict]. Esse método funciona de forma recursiva. Se alguns valores do dicionário forem do tipo [OrderedDict, list], os valores dessas coleções são examinados para verificar se também são do tipo [OrderedDict]. Se for esse o caso, eles são transformados no tipo [dict]. Observe-se que o método [xmltodict.parse] não produz nenhum dicionário do tipo [dict];
Antes de examinar as funções ausentes, vamos analisar os resultados para ver o que está sendo procurado:
O teste 1 (linhas 28-29) produz os seguintes resultados:
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é'}
- linha 2: o dicionário testado. É importante observar que o método [xml2dict.unparse] exige que o dicionário tenha o formato {‘chave’: valor}, em que [valeur] pode ser, por sua vez, um dicionário, uma lista ou um tipo simples;
- linhas 3-4: a sequência XML gerada a partir do dicionário. Ela é precedida pelo cabeçalho [<?xml version="1.0" encoding="utf-8"?>\n], que normalmente é a primeira linha de um arquivo XML;
- linha 5: o tipo [OrderedDict] obtido pelo método [xml2dict.parse], que recebe como parâmetro a sequência XML anterior;
- linha 6: o dicionário do tipo [dict] obtido ao aplicar o método [ordereddict2dict] ao tipo anterior. Encontramos aqui o dicionário original da linha 2;
Todos os outros testes seguem o mesmo esquema e devem permitir que você compreenda como passar de um dicionário para uma cadeia XML e, em seguida, dessa cadeia XML para o dicionário original.
Os demais testes apresentam os seguintes resultados:
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
- as linhas 23 e 27 mostram um ponto importante:
- linha 23: os valores associados às chaves do dicionário [result] são números;
- linha 26: os valores associados às chaves do dicionário [ordereddict_dictionary1] são cadeias de caracteres. Essa é uma limitação da biblioteca [xmltodict]. Seu método [parse] produz apenas cadeias de caracteres. Isso é facilmente compreensível:
- linha 25: a sequência XML a partir da qual o dicionário é gerado. Nessa string, não há nenhuma indicação do tipo dos dados encapsulados nas tags XML. A [xmltodict.parse] faz o que é mais lógico: mantém tudo como string de caracteres no dicionário gerado. Existem outras bibliotecas semelhantes à [xmltodict], nas quais o tipo dos dados encapsulados é indicado nas tags. Poderíamos encontrar, por exemplo, a tag [<enfants type=’int’>2</enfants>];
- a consequência disso é que, ao utilizar um dicionário gerado pelo módulo [xmltodict], é necessário conhecer o tipo de dados que ele encapsula para poder converter do tipo ‘str’ para o tipo real dos dados;
Vamos nos deter agora no método [ordereddict2dict], que transforma um tipo [OrderedDict] no tipo [dict]:
# xmltodict.parse para passar do XML para o dicionário. O dicionário deve ter uma raiz
# o dicionário gerado é do tipo OrderedDict
# xmltodict.unparse para passar do dicionário para o XML
def check(value):
# se o valor for do tipo OrderedDict, ele é transformado
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# se o valor for do tipo lista, ele é transformado
elif isinstance(value, list):
value2 = list2list(value)
else:
# trata-se de um tipo simples, não de uma coleção
value2 = value
# retorna-se o novo valor
return value2
def list2list(liste: list) -> list:
# a nova lista
newliste = []
# processamos os elementos da lista de parâmetros
for value in liste:
# adiciona-se value à nova lista
newliste.append(check(value))
# retorna a nova lista
return newliste
def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
# OrderedDict -> dicionário de forma recursiva
newdict = {}
for key, value in ordered_dictionary.items():
# armazenamos o valor no novo dicionário
newdict[key] = check(value)
# retornamos o dicionário
return newdict
- linha 30: a função [ordereddict2dict] recebe um tipo [OrderedDict] como parâmetro;
- linha 32: o dicionário do tipo [dict], que será retornado na linha 37 pela função;
- linha 33: são explorados todos os tuplos (chave, valor) do dicionário [ordered_dictionary];
- linha 35: no novo dicionário, a chave [key] é mantida, mas o valor associado não é [value], e sim [check(value)]. A função [check(value)] é responsável por localizar, caso [value] seja uma coleção, todos os elementos do tipo [OrderedDict] e transformá-los no tipo [dict];
O método [check] está definido nas linhas 5 a 16:
- linha 5: não se conhece o tipo de [value], portanto não foi possível escrever [value : type];
- linhas 7-8: se [value] for do tipo [OrderedDict], então chama-se recursivamente a função [ordereddict2dict], que acabamos de comentar;
- linhas 9-11: outro caso possível é que [value] seja uma lista. Nesse caso, na linha 11, chama-se a função [list2list] das linhas 19-27;
- linhas 12-14: o último caso é aquele em que [value] não é uma coleção, mas um tipo simples. A função [check], assim como as funções [ordereddict2dict] e [list2list], é recursiva. Sabe-se que, nesse caso, é sempre necessário prever a situação em que a recursão termina. As linhas 12-14 representam essa situação;
- linha 16: a função [check], chamada recursivamente ou não, produz um valor [valeur2] que deve substituir o parâmetro [value] da linha 5;
O método [list2list], definido nas linhas 19 a 27, utiliza uma lista passada como parâmetro. Ele irá percorrê-la e substituir qualquer valor do tipo [OrderedDict] encontrado nela por um do tipo [dict].
- linha 21: a nova lista que a função irá criar;
- linhas 23 a 25: todos os valores [value] da lista são analisados e substituídos pelo valor [check(value)]. Esse valor [value] pode, por sua vez, conter elementos do tipo [list] ou [OrderedDict]. Eles serão processados corretamente pela função recursiva [check];