web-dev-qa-db-fra.com

Conversion de XML en dictionnaire avec ElementTree

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.

19
OHLÁLÁ
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.)

24
Fred Foo

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. :)


Mettre à jour

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)))
33
K3---rnc

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}
2
s29

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.

1
albarji

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'}
0
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
0
luismartingil

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'
0
bloodrootfc

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
0
DaveL17