26. Do dicionário para XML e vice-versa
Propomos aqui explorar o módulo [xml2dict], que permite transformar:
- uma cadeia XML num dicionário:
- um dicionário numa cadeia XML;
Antes do advento do jSON, a resposta dos serviços web era frequentemente em XML (eXtended Markup Language). Além disso, o protocolo destes serviços web era frequentemente o SOAP (Simple Object Process Protocol). O SOAP é um protocolo que se baseia no protocolo HTTP da web. Atualmente (2020), os serviços web são, na sua maioria, do tipo REST (Representational State Transfer). Os serviços web que analisámos não se enquadram em nenhum destes 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», uma vez que não cumprem todas as regras do REST.
Vamos mostrar como é fácil transformar as nossas arquiteturas cliente/servidor jSON em arquiteturas cliente/servidor XML. Basta utilizar o módulo [xmltodict].
Começamos por instalá-lo num 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 isto, vamos analisar, através de um exemplo, o que é possível fazer com este 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 resultante é do tipo OrderedDict
# xmltodict.unparse para passar do dicionário para o XML
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# registos
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é] em falta',
'«Falta o parâmetro [enfants]», 'Falta o parâmetro [salaire]']}})
# 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 escrever [message] e um dicionário [dictionary];
- linha 16: exibição da mensagem;
- linha 17: exibe-se o dicionário recebido;
- linhas 19-20: este dicionário é transformado numa cadeia de caracteres XML e esta é apresentada. O método responsável por esta transformação é o [xmltodict.unparse];
- linhas 21-23: a cadeia XML anterior é convertida num dicionário e este é apresentado. O método responsável por esta operação é o [xmltodict.parse]. Este 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 é convertido para o tipo [dict] com a ajuda do método (ainda não escrito) [ordereddict2dict]. Este método funciona de forma recursiva. Se alguns valores do dicionário forem do tipo [OrderedDict, list], os valores dessas coleções são analisados para verificar se também são do tipo [OrderedDict]. Se for esse o caso, são transformados no tipo [dict]. Note-se que o método [xmltodict.parse] não produz nenhum dicionário do tipo [dict];
Antes de analisarmos as funções em falta, vamos examinar os resultados para ver o que se procura:
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 referir 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 cadeia XML resultante do dicionário. É precedida pelo cabeçalho [<?xml version="1.0" encoding="utf-8"?>\n], que normalmente constitui a primeira linha de um ficheiro XML;
- linha 5: o tipo [OrderedDict] obtido pelo método [xml2dict.parse], que recebe como parâmetro a cadeia 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 deverão permitir-lhe compreender como passar de um dicionário para uma cadeia XML e, em seguida, dessa cadeia XML para o dicionário original.
Os restantes 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 revelam 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. Trata-se de uma limitação da biblioteca [xmltodict]. O seu método [parse] produz apenas cadeias de caracteres. Isto é fácil de compreender:
- linha 25: a cadeia XML a partir da qual é gerado o dicionário. Nesta cadeia de caracteres, não há qualquer indicação do tipo de dados encapsulados nas balizas XML. A [xmltodict.parse] faz o que é mais lógico: mantém tudo como cadeias de caracteres no dicionário produzido. Existem outras bibliotecas semelhantes à [xmltodict], nas quais o tipo dos dados encapsulados é indicado nas etiquetas. Poderíamos encontrar, por exemplo, a etiqueta [<enfants type=’int’>2</enfants>];
- a consequência disto é que, ao utilizar um dicionário produzido pelo módulo [xmltodict], é necessário conhecer o tipo de dados que este encapsula para poder passar do tipo «str» para o tipo real dos dados;
Vejamos agora o método [ordereddict2dict], que transforma um tipo [OrderedDict] num 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, transforma-se
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# se o valor for do tipo lista, transforma-se
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 = []
# processam-se os elementos da lista de parâmetros
for value in liste:
# adiciona-se «value» à nova lista
newliste.append(check(value))
# retorna-se 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():
# armazena-se 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á devolvido na linha 37 pela função;
- linha 33: exploram-se 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], mas sim [check(value)]. A função [check(value)] tem por função, caso [value] seja uma coleção, encontrar todos os elementos do tipo [OrderedDict] e transformá-los no tipo [dict];
O método [check] está definido nas linhas 5-16:
- linha 5: não se conhece o tipo de [value], pelo que 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 acabámos de comentar;
- linhas 9-11: outro caso possível é que [value] seja uma lista. Neste 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 sim um tipo simples. A função [check], tal 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 correspondem a 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-27, processa uma lista passada como parâmetro. Irá percorrê-la e substituir qualquer valor do tipo [OrderedDict] encontrado na mesma por um do tipo [dict].
- linha 21: a nova lista que a função irá criar;
- linhas 23-25: todos os valores [value] da lista são analisados e substituídos pelo valor [check(value)]. Este valor [value] pode, por sua vez, conter elementos do tipo [list] ou [OrderedDict]. Estes serão tratados corretamente pela função recursiva [check];