150 votes

Comment convertir une chaîne XML en dictionnaire ?

J'ai un programme qui lit un document XML à partir d'un socket. Le document XML est stocké dans une chaîne de caractères que j'aimerais convertir directement en un dictionnaire Python, de la même manière que dans le programme Django simplejson bibliothèque.

Prenons un exemple :

str ="<?xml version="1.0" ?><person><name>john</name><age>20</age></person"
dic_xml = convert_to_dic(str)

Puis dic_xml ressemblerait à {'person' : { 'name' : 'john', 'age' : 20 } }

1 votes

Str a quelques erreurs de syntaxe. try:str = '<?xml version="1.0" ?><personne><name>john</name><age>20</age></person>'.

335voto

Martin Blech Points 5031

xmltodict (divulgation complète : je l'ai écrit) fait exactement ça :

xmltodict.parse("""
<?xml version="1.0" ?>
<person>
  <name>john</name>
  <age>20</age>
</person>""")
# {u'person': {u'age': u'20', u'name': u'john'}}

32 votes

C'est un module fantastique.

4 votes

Vous venez de m'épargner beaucoup d'efforts. J'ai passé une excellente journée.

4 votes

Par ailleurs, pour les futurs googlenautes, j'ai pu utiliser ce système dans App Engine, dont on m'avait laissé croire qu'il n'était pas compatible avec la plupart des bibliothèques xml en Python.

73voto

James Points 937

C'est un excellent module que quelqu'un a créé. Je l'ai utilisé plusieurs fois. http://code.activestate.com/recipes/410469-xml-as-dictionary/

Voici le code du site web, au cas où le lien ne fonctionnerait pas.

from xml.etree import cElementTree as ElementTree

class XmlListConfig(list):
    def __init__(self, aList):
        for element in aList:
            if element:
                # treat like dict
                if len(element) == 1 or element[0].tag != element[1].tag:
                    self.append(XmlDictConfig(element))
                # treat like list
                elif element[0].tag == element[1].tag:
                    self.append(XmlListConfig(element))
            elif element.text:
                text = element.text.strip()
                if text:
                    self.append(text)

class XmlDictConfig(dict):
    '''
    Example usage:

    >>> tree = ElementTree.parse('your_file.xml')
    >>> root = tree.getroot()
    >>> xmldict = XmlDictConfig(root)

    Or, if you want to use an XML string:

    >>> root = ElementTree.XML(xml_string)
    >>> xmldict = XmlDictConfig(root)

    And then use xmldict for what it is... a dict.
    '''
    def __init__(self, parent_element):
        if parent_element.items():
            self.update(dict(parent_element.items()))
        for element in parent_element:
            if element:
                # treat like dict - we assume that if the first two tags
                # in a series are different, then they are all different.
                if len(element) == 1 or element[0].tag != element[1].tag:
                    aDict = XmlDictConfig(element)
                # treat like list - we assume that if the first two tags
                # in a series are the same, then the rest are the same.
                else:
                    # here, we put the list in dictionary; the key is the
                    # tag name the list elements all share in common, and
                    # the value is the list itself 
                    aDict = {element[0].tag: XmlListConfig(element)}
                # if the tag has attributes, add those to the dict
                if element.items():
                    aDict.update(dict(element.items()))
                self.update({element.tag: aDict})
            # this assumes that if you've got an attribute in a tag,
            # you won't be having any text. This may or may not be a 
            # good idea -- time will tell. It works for the way we are
            # currently doing XML configuration files...
            elif element.items():
                self.update({element.tag: dict(element.items())})
            # finally, if there are no child tags and no attributes, extract
            # the text
            else:
                self.update({element.tag: element.text})

Exemple d'utilisation :

tree = ElementTree.parse('your_file.xml')
root = tree.getroot()
xmldict = XmlDictConfig(root)

//Ou, si vous voulez utiliser une chaîne XML :

root = ElementTree.XML(xml_string)
xmldict = XmlDictConfig(root)

4 votes

Vous pouvez également utiliser 'xmltodict'.

7 votes

J'ai essayé ceci et c'est beaucoup plus rapide que xmltodict. Pour analyser un fichier xml de 80 Mo, il a fallu 7 secondes, contre 90 secondes avec xmltodict.

1 votes

Confirmé... Je n'ai pas testé cette méthode pour tous les cas de figure, mais pour mes chaînes XML plutôt simples, elle est assez rapide (environ 8 fois plus rapide que la méthode d'encodage de l'image). xmltodict bibliothèque). L'inconvénient est que vous devez l'héberger vous-même dans votre projet.

52voto

K3---rnc Points 395

L'extrait XML vers Python-dict suivant analyse les entités ainsi que les attributs suivants cette "spécification" XML-to-JSON . C'est la solution la plus générale pour traiter tous les cas de XML.

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

Il 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
pprint(etree_to_dict(e))

Le résultat de cet exemple (selon la "spécification" ci-dessus) devrait être le suivant :

{'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 forcément joli, mais c'est sans ambiguïté, et des entrées XML plus simples donnent un JSON plus simple :)


Mise à jour

Si vous voulez faire le inverser émettent un Chaîne XML à partir d'un JSON/dict vous pouvez l'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, basestring):
            root.text = d
        elif isinstance(d, dict):
            for k,v in d.items():
                assert isinstance(k, basestring)
                if k.startswith('#'):
                    assert k == '#text' and isinstance(v, basestring)
                    root.text = v
                elif k.startswith('@'):
                    assert isinstance(v, basestring)
                    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:
            raise TypeError('invalid type: ' + str(type(d)))
    assert isinstance(d, dict) and len(d) == 1
    tag, body = next(iter(d.items()))
    node = ET.Element(tag)
    _to_etree(body, node)
    return ET.tostring(node)

pprint(dict_to_etree(d))

1 votes

Merci pour ce code ! Info supplémentaire : si vous utilisez python 2.5 vous ne pouvez pas utiliser la compréhension par dictionnaire, donc vous devez changer la ligne d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} à d = { t.tag: dict( (k, v[0] if len(v) == 1 else v) for k, v in dd.iteritems() ) }

2 votes

J'ai testé près de 10 snippets / modules python / etc. pour cela. Celui-ci est le meilleur que j'ai trouvé. D'après mes tests, il est : 1) beaucoup plus rapide que github.com/martinblech/xmltodict (basé sur l'api XML SAX) 2) mieux que github.com/mcspring/XML2Dict qui a quelques petits problèmes lorsque plusieurs enfants ont le même nom 3) mieux que code.activestate.com/recipes/410469-xml-as-dictionary qui avait aussi de petits problèmes et plus important : 4) un code beaucoup plus court que tous les précédents ! Merci @K3---rnc

0 votes

C'est, de loin, la réponse la plus complète, et elle fonctionne sur > 2.6, et elle est assez flexible. mon seul problème est que le texte peut changer d'emplacement selon qu'il y a un attribut ou non). j'ai également posté une solution encore plus petite et plus rigide.

6voto

rts1 Points 41

Les versions les plus récentes des bibliothèques PicklingTools (1.3.0 et 1.3.1) prennent en charge les outils de conversion de XML en dictée Python.

Le téléchargement est disponible ici : PicklingTools 1.3.1

Il existe une documentation assez complète sur les convertisseurs. ici La documentation décrit en détail toutes les décisions et les problèmes qui se posent lors de la conversion entre XML et les dictionnaires Python (il existe un certain nombre de cas limites : attributs, listes, listes anonymes, dicts anonymes, eval, etc. que la plupart des convertisseurs ne gèrent pas). En général, cependant, les convertisseurs sont faciles à utiliser. Si un 'exemple.xml' contient :

<top>
  <a>1</a>
  <b>2.2</b>
  <c>three</c>
</top>

Puis de le convertir en dictionnaire :

>>> from xmlloader import *
>>> example = file('example.xml', 'r')   # A document containing XML
>>> xl = StreamXMLLoader(example, 0)     # 0 = all defaults on operation
>>> result = xl.expect XML()
>>> print result
{'top': {'a': '1', 'c': 'three', 'b': '2.2'}}

Il existe des outils de conversion en C++ et en Python : le C++ et le Python effectuent une conversion identique, mais le C++ est environ 60 fois plus rapide.

0 votes

Bien sûr, s'il y a deux A, ce n'est pas un bon format.

1 votes

Cela semble intéressant, mais je n'ai pas encore compris comment les PicklingTools sont censés être utilisés - s'agit-il simplement d'une archive de fichiers de code source à partir de laquelle je dois trouver les bons pour mon travail et les copier dans mon projet ? Pas de modules à charger ou quelque chose de plus simple ?

0 votes

Je reçois : dans peekIntoNextNWSChar c = self.is .read(1) AttributeError : L'objet 'str' n'a pas d'attribut 'read'.

1voto

Jarrod Roberson Points 32263

L'analyseur XML le plus facile à utiliser pour Python est ElementTree (à partir de la version 2.5x, il se trouve dans la bibliothèque standard xml.etree.ElementTree). Je ne pense pas qu'il y ait quelque chose qui fasse exactement ce que vous voulez. Il serait assez trivial d'écrire quelque chose pour faire ce que vous voulez en utilisant ElementTree, mais pourquoi convertir en dictionnaire, et pourquoi ne pas simplement utiliser ElementTree directement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X