Je cherche un analyseur de dictionnaire XML à l'aide de ElementTree, j'en ai déjà trouvé mais ils excluent les attributs, et dans mon cas, j'ai beaucoup d'attributs.
def etree_to_dict(t):
d = {t.tag : map(etree_to_dict, t.iterchildren())}
d.update(('@' + k, v) for k, v in t.attrib.iteritems())
d['text'] = t.text
return d
Appeler comme
tree = etree.parse("some_file.xml")
etree_to_dict(tree.getroot())
Cela fonctionne tant que vous n'avez pas d'attribut text
; Si vous le faites, modifiez la troisième ligne du corps de la fonction pour utiliser une clé différente. En outre, vous ne pouvez pas gérer un contenu mixte avec cela.
(Testé sur LXML.)
L'extrait de code XML-à-Python-dict suivant analyse les entités ainsi que les attributs suivants cette "spécification" XML-à-JSON :
from collections import defaultdict
def etree_to_dict(t):
d = {t.tag: {} if t.attrib else None}
children = list(t)
if children:
dd = defaultdict(list)
for dc in map(etree_to_dict, children):
for k, v in dc.items():
dd[k].append(v)
d = {t.tag: {k: v[0] if len(v) == 1 else v
for k, v in dd.items()}}
if t.attrib:
d[t.tag].update(('@' + k, v)
for k, v in t.attrib.items())
if t.text:
text = t.text.strip()
if children or t.attrib:
if text:
d[t.tag]['#text'] = text
else:
d[t.tag] = text
return d
C'est utilisé:
from xml.etree import cElementTree as ET
e = ET.XML('''
<root>
<e />
<e>text</e>
<e name="value" />
<e name="value">text</e>
<e> <a>text</a> <b>text</b> </e>
<e> <a>text</a> <a>text</a> </e>
<e> text <a>text</a> </e>
</root>
''')
from pprint import pprint
d = etree_to_dict(e)
pprint(d)
Le résultat de cet exemple (selon la "spécification" ci-dessus liée) devrait être:
{'root': {'e': [None,
'text',
{'@name': 'value'},
{'#text': 'text', '@name': 'value'},
{'a': 'text', 'b': 'text'},
{'a': ['text', 'text']},
{'#text': 'text', 'a': 'text'}]}}
Ce n'est pas nécessairement joli, mais c'est sans ambiguïté, et des entrées XML plus simples se traduisent par un JSON plus simple. :)
Si vous voulez faire le reverse , émettre une chaîne XML à partir d'un JSON/dict , vous pouvez utiliser:
try:
basestring
except NameError: # python3
basestring = str
def dict_to_etree(d):
def _to_etree(d, root):
if not d:
pass
Elif isinstance(d, str):
root.text = d
Elif isinstance(d, dict):
for k,v in d.items():
assert isinstance(k, str)
if k.startswith('#'):
assert k == '#text' and isinstance(v, str)
root.text = v
Elif k.startswith('@'):
assert isinstance(v, str)
root.set(k[1:], v)
Elif isinstance(v, list):
for e in v:
_to_etree(e, ET.SubElement(root, k))
else:
_to_etree(v, ET.SubElement(root, k))
else:
assert d == 'invalid type', (type(d), d)
assert isinstance(d, dict) and len(d) == 1
tag, body = next(iter(d.items()))
node = ET.Element(tag)
_to_etree(body, node)
return node
print(ET.tostring(dict_to_etree(d)))
Basé sur @larsmans, si vous n'avez pas besoin d'attributs, cela vous donnera un dictionnaire plus serré -
def etree_to_dict(t):
return {t.tag : map(etree_to_dict, t.iterchildren()) or t.text}
Pour transformer le langage XML de/en dictionnaires python, xmltodict a très bien fonctionné pour moi:
import xmltodict
xml = '''
<root>
<e />
<e>text</e>
<e name="value" />
<e name="value">text</e>
<e> <a>text</a> <b>text</b> </e>
<e> <a>text</a> <a>text</a> </e>
<e> text <a>text</a> </e>
</root>
'''
xdict = xmltodict.parse(xml)
xdict va maintenant ressembler
OrderedDict([('root',
OrderedDict([('e',
[None,
'text',
OrderedDict([('@name', 'value')]),
OrderedDict([('@name', 'value'),
('#text', 'text')]),
OrderedDict([('a', 'text'), ('b', 'text')]),
OrderedDict([('a', ['text', 'text'])]),
OrderedDict([('a', 'text'),
('#text', 'text')])])]))])
Si vos données XML ne sont pas sous forme brute de chaîne/octets mais dans un objet ElementTree, il vous suffit de les imprimer sous forme de chaîne et d'utiliser à nouveau xmldict.parse. Par exemple, si vous utilisez lxml pour traiter les documents XML, alors
from lxml import etree
e = etree.XML(xml)
xmltodict.parse(etree.tostring(e))
produira le même dictionnaire que ci-dessus.
Vous pouvez utiliser cet extrait qui le convertit directement du xml en dictionnaire
import xml.etree.ElementTree as ET
xml = ('<xml>' +
'<first_name>Dean Christian</first_name>' +
'<middle_name>Christian</middle_name>' +
'<last_name>Armada</last_name>' +
'</xml>')
root = ET.fromstring(xml)
x = {x.tag: root.find(x.tag).text for x in root._children}
# returns {'first_name': 'Dean Christian', 'last_name': 'Armada', 'middle_name': 'Christian'}
from lxml import etree, objectify
def formatXML(parent):
"""
Recursive operation which returns a tree formated
as dicts and lists.
Decision to add a list is to find the 'List' Word
in the actual parent tag.
"""
ret = {}
if parent.items(): ret.update(dict(parent.items()))
if parent.text: ret['__content__'] = parent.text
if ('List' in parent.tag):
ret['__list__'] = []
for element in parent:
ret['__list__'].append(formatXML(element))
else:
for element in parent:
ret[element.tag] = formatXML(element)
return ret
Voici une structure de données simple en XML (enregistrer en tant que fichier.xml):
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<Person>
<First>John</First>
<Last>Smith</Last>
</Person>
<Person>
<First>Jane</First>
<Last>Doe</Last>
</Person>
</Data>
Voici le code pour créer une liste d'objets de dictionnaire à partir de celui-ci.
from lxml import etree
tree = etree.parse('file.xml')
root = tree.getroot()
datadict = []
for item in root:
d = {}
for elem in item:
d[elem.tag]=elem.text
datadict.append(d)
datadict contient maintenant:
[{'First': 'John', 'Last': 'Smith'},{'First': 'Jane', 'Last': 'Doe'}]
et peut être consulté comme suit:
datadict[0]['First']
'John'
datadict[1]['Last']
'Doe'
En vous appuyant sur @larsmans, si les clés résultantes contiennent des informations d'espace de noms xml, vous pouvez les supprimer avant d'écrire dans le dictionnaire. Définissez une variable xmlns
égale à l'espace de noms et supprimez sa valeur.
xmlns = '{http://foo.namespaceinfo.com}'
def etree_to_dict(t):
if xmlns in t.tag:
t.tag = t.tag.lstrip(xmlns)
if d = {t.tag : map(etree_to_dict, t.iterchildren())}
d.update(('@' + k, v) for k, v in t.attrib.iteritems())
d['text'] = t.text
return d