Hoe converteer ik een XML-tekenreeks naar een woordenboek?

Ik heb een programma dat een XML-document uit een socket leest. Ik heb het XML-document opgeslagen in een string die ik rechtstreeks naar een Python-woordenboek wil converteren, op dezelfde manier als in de simplejson-bibliotheek van Django.

Neem als voorbeeld:

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

Dan zou dic_xmler zo uitzien {'person' : { 'name' : 'john', 'age' : 20 } }


Antwoord 1, autoriteit 100%

Dit is een geweldige module die iemand heeft gemaakt. Ik heb het meerdere keren gebruikt.
http://code.activestate.com/recipes/410469-xml-as- woordenboek/

Hier is de code van de website voor het geval de link niet goed werkt.

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})

Voorbeeld Gebruik:

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

// of, als u een XML-string wilt gebruiken:

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

Antwoord 2, Autoriteit 470%

xmltodict (volledige openbaarmaking: ik heb het geschreven) doet precies dat:

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

Antwoord 3, Autoriteit 70%

De volgende XML-To-Python-DiCT-fragment PARSES-entiteiten, evenals attributen na Deze XML-to-JSON” -specificatie “. Het is de meest algemene oplossing die alle gevallen van XML afhandelt.

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

het wordt gebruikt:

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

De uitvoer van dit voorbeeld (zoals hierboven-gekoppelde “specificatie”) zou moeten zijn:

{'root': {'e': [None,
                'text',
                {'@name': 'value'},
                {'#text': 'text', '@name': 'value'},
                {'a': 'text', 'b': 'text'},
                {'a': ['text', 'text']},
                {'#text': 'text', 'a': 'text'}]}}

Niet noodzakelijk mooi, maar het is ondubbelzinnig en eenvoudiger XML-ingangen resulteren in eenvoudiger JSON. 🙂


Update

Als u de omgekeerd wilt doen , uitsteken een XML-string van een JSON / DICT , u kunt gebruiken:

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

Antwoord 4, autoriteit 49%

Deze lichtgewicht versie, hoewel niet configureerbaar, is vrij eenvoudig aan te passen als dat nodig is, en werkt in oude pythons. Het is ook rigide – wat betekent dat de resultaten hetzelfde zijn, ongeacht het bestaan van attributen.

import xml.etree.ElementTree as ET
from copy import copy
def dictify(r,root=True):
    if root:
        return {r.tag : dictify(r, False)}
    d=copy(r.attrib)
    if r.text:
        d["_text"]=r.text
    for x in r.findall("./*"):
        if x.tag not in d:
            d[x.tag]=[]
        d[x.tag].append(dictify(x,False))
    return d

Dus:

root = ET.fromstring("<erik><a x='1'>v</a><a y='2'>w</a></erik>")
dictify(root)

Resulteert in:

{'erik': {'a': [{'x': '1', '_text': 'v'}, {'y': '2', '_text': 'w'}]}}

Antwoord 5, autoriteit 9%

De meest recente versies van de PicklingTools-bibliotheken (1.3.0 en 1.3.1) ondersteunen hulpmiddelen voor het converteren van XML naar een Python-dict.

De download is hier beschikbaar: PicklingTools 1.3.1

Er is nogal wat documentatie voor de converters hier : de documentatie beschrijft in detail alle beslissingen en problemen die zullen ontstaan ​​bij het converteren tussen XML- en Python-woordenboeken (er zijn een aantal randzaken: attributen, lijsten, anonieme lijsten, anonieme dictten, eval, enz. Die de meeste converters niet verwerken). In het algemeen echter,
De converters zijn gemakkelijk te gebruiken. Als een ‘example.xml’ bevat:

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

Om het vervolgens om te zetten naar een woordenboek:

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

Er zijn hulpmiddelen voor het omzetten in zowel C++ als Python: de C++ en Python doen indentische conversie, maar de C++ is ongeveer 60x sneller


Antwoord 6, Autoriteit 7%

U kunt dit vrij gemakkelijk met LXML doen. Installeer het eerst:

[sudo] pip install lxml

Hier is een recursieve functie die ik heb geschreven die het zware tillen voor u doet:

from lxml import objectify as xml_objectify
def xml_to_dict(xml_str):
    """ Convert xml to dict, using lxml v3.4.2 xml processing library """
    def xml_to_dict_recursion(xml_object):
        dict_object = xml_object.__dict__
        if not dict_object:
            return xml_object
        for key, value in dict_object.items():
            dict_object[key] = xml_to_dict_recursion(value)
        return dict_object
    return xml_to_dict_recursion(xml_objectify.fromstring(xml_str))
xml_string = """<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp>
<IndustryType>Test</IndustryType><SomeData><SomeNestedData1>1234</SomeNestedData1>
<SomeNestedData2>3455</SomeNestedData2></SomeData></NewOrderResp></Response>"""
print xml_to_dict(xml_string)

De onderstaande variant behoudt de bovenliggende sleutel / het element:

def xml_to_dict(xml_str):
    """ Convert xml to dict, using lxml v3.4.2 xml processing library, see http://lxml.de/ """
    def xml_to_dict_recursion(xml_object):
        dict_object = xml_object.__dict__
        if not dict_object:  # if empty dict returned
            return xml_object
        for key, value in dict_object.items():
            dict_object[key] = xml_to_dict_recursion(value)
        return dict_object
    xml_obj = objectify.fromstring(xml_str)
    return {xml_obj.tag: xml_to_dict_recursion(xml_obj)}

Als u alleen een substructuur wilt retourneren en deze naar dict wilt converteren, kunt u Element.find()gebruiken om de substructuur op te halen en deze vervolgens te converteren:

xml_obj.find('.//')  # lxml.objectify.ObjectifiedElement instance

Bekijk de lxml-documenten hier. Ik hoop dat dit helpt!


Antwoord 7, autoriteit 7%

Disclaimer:
Deze aangepaste XML-parser is geïnspireerd door Adam Clark
De originele XML-parser werkt voor de meeste eenvoudige gevallen. Het werkte echter niet voor sommige gecompliceerde XML-bestanden. Ik debugde de code regel voor regel en loste uiteindelijk een aantal problemen op. Als je een aantal bugs vindt, laat het me dan weten. Ik ben blij om het te repareren.

class XmlDictConfig(dict):  
    '''   
    Note: need to add a root into if no exising    
    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.updateShim( dict(parent_element.items()) )
        for element in parent_element:
            if len(element):
                aDict = XmlDictConfig(element)
            #   if element.items():
            #   aDict.updateShim(dict(element.items()))
                self.updateShim({element.tag: aDict})
            elif element.items():    # items() is specialy for attribtes
                elementattrib= element.items()
                if element.text:           
                    elementattrib.append((element.tag,element.text ))     # add tag:text if there exist
                self.updateShim({element.tag: dict(elementattrib)})
            else:
                self.updateShim({element.tag: element.text})
    def updateShim (self, aDict ):
        for key in aDict.keys():   # keys() includes tag and attributes
            if key in self:
                value = self.pop(key)
                if type(value) is not list:
                    listOfDicts = []
                    listOfDicts.append(value)
                    listOfDicts.append(aDict[key])
                    self.update({key: listOfDicts})
                else:
                    value.append(aDict[key])
                    self.update({key: value})
            else:
                self.update({key:aDict[key]})  # it was self.update(aDict)    

Antwoord 8, autoriteit 6%

def xml_to_dict(node):
    u''' 
    @param node:lxml_node
    @return: dict 
    '''
    return {'tag': node.tag, 'text': node.text, 'attrib': node.attrib, 'children': {child.tag: xml_to_dict(child) for child in node}}

Antwoord 9, autoriteit 3%

De code van http://code.activestate.com/recipes/ 410469-xml-as-dictionary/werkt goed, maar als er meerdere elementen zijn die hetzelfde zijn op een bepaalde plaats in de hiërarchie, worden ze gewoon overschreven.

Ik heb een shim toegevoegd tussen die looks om te zien of het element al bestaat vóór self.update(). Als dat het geval is, wordt het bestaande item weergegeven en wordt een lijst gemaakt van het bestaande en het nieuwe. Alle volgende duplicaten worden aan de lijst toegevoegd.

Ik weet niet zeker of dit eleganter kan, maar het werkt:

import xml.etree.ElementTree as ElementTree
class XmlDictConfig(dict):
    def __init__(self, parent_element):
        if parent_element.items():
            self.updateShim(dict(parent_element.items()))
        for element in parent_element:
            if len(element):
                aDict = XmlDictConfig(element)
                if element.items():
                    aDict.updateShim(dict(element.items()))
                self.updateShim({element.tag: aDict})
            elif element.items():
                self.updateShim({element.tag: dict(element.items())})
            else:
                self.updateShim({element.tag: element.text.strip()})
    def updateShim (self, aDict ):
        for key in aDict.keys():
            if key in self:
                value = self.pop(key)
                if type(value) is not list:
                    listOfDicts = []
                    listOfDicts.append(value)
                    listOfDicts.append(aDict[key])
                    self.update({key: listOfDicts})
                else:
                    value.append(aDict[key])
                    self.update({key: value})
            else:
                self.update(aDict)

Antwoord 10, Autoriteit 3%

van @ k3 — rnc Respons (het beste voor mij) Ik heb een kleine aanpassingen toegevoegd om een ​​besteldeict te krijgen van een XML-tekst (sommige tijden bestelt zaken):

def etree_to_ordereddict(t):
d = OrderedDict()
d[t.tag] = OrderedDict() if t.attrib else None
children = list(t)
if children:
    dd = OrderedDict()
    for dc in map(etree_to_ordereddict, children):
        for k, v in dc.iteritems():
            if k not in dd:
                dd[k] = list()
            dd[k].append(v)
    d = OrderedDict()
    d[t.tag] = OrderedDict()
    for k, v in dd.iteritems():
        if len(v) == 1:
            d[t.tag][k] = v[0]
        else:
            d[t.tag][k] = v
if t.attrib:
    d[t.tag].update(('@' + k, v) for k, v in t.attrib.iteritems())
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

Volgens het voorbeeld van @K3—rnc kunt u het gebruiken:

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_ordereddict(e))

Hopelijk helpt het 😉


Antwoord 11

De gemakkelijkst te gebruiken XML-parser voor Python is ElementTree (vanaf 2.5x en hoger in de standaardbibliotheek xml.etree.ElementTree). Ik denk niet dat er iets is dat precies doet wat je wilt. Het zou vrij triviaal zijn om iets te schrijven om te doen wat je wilt met ElementTree, maar waarom omzetten naar een woordenboek en waarom ElementTree niet rechtstreeks gebruiken.


Antwoord 12

Hier is een link naar een ActiveState-oplossing– en de code voor het geval deze weer verdwijnt .

==================================================
xmlreader.py:
==================================================
from xml.dom.minidom import parse
class NotTextNodeError:
    pass
def getTextFromNode(node):
    """
    scans through all children of node and gathers the
    text. if node has non-text child-nodes, then
    NotTextNodeError is raised.
    """
    t = ""
    for n in node.childNodes:
    if n.nodeType == n.TEXT_NODE:
        t += n.nodeValue
    else:
        raise NotTextNodeError
    return t
def nodeToDic(node):
    """
    nodeToDic() scans through the children of node and makes a
    dictionary from the content.
    three cases are differentiated:
    - if the node contains no other nodes, it is a text-node
    and {nodeName:text} is merged into the dictionary.
    - if the node has the attribute "method" set to "true",
    then it's children will be appended to a list and this
    list is merged to the dictionary in the form: {nodeName:list}.
    - else, nodeToDic() will call itself recursively on
    the nodes children (merging {nodeName:nodeToDic()} to
    the dictionary).
    """
    dic = {} 
    for n in node.childNodes:
    if n.nodeType != n.ELEMENT_NODE:
        continue
    if n.getAttribute("multiple") == "true":
        # node with multiple children:
        # put them in a list
        l = []
        for c in n.childNodes:
            if c.nodeType != n.ELEMENT_NODE:
            continue
        l.append(nodeToDic(c))
            dic.update({n.nodeName:l})
        continue
    try:
        text = getTextFromNode(n)
    except NotTextNodeError:
            # 'normal' node
            dic.update({n.nodeName:nodeToDic(n)})
            continue
        # text node
        dic.update({n.nodeName:text})
    continue
    return dic
def readConfig(filename):
    dom = parse(filename)
    return nodeToDic(dom)
def test():
    dic = readConfig("sample.xml")
    print dic["Config"]["Name"]
    print
    for item in dic["Config"]["Items"]:
    print "Item's Name:", item["Name"]
    print "Item's Value:", item["Value"]
test()
==================================================
sample.xml:
==================================================
<?xml version="1.0" encoding="UTF-8"?>
<Config>
    <Name>My Config File</Name>
    <Items multiple="true">
    <Item>
        <Name>First Item</Name>
        <Value>Value 1</Value>
    </Item>
    <Item>
        <Name>Second Item</Name>
        <Value>Value 2</Value>
    </Item>
    </Items>
</Config>
==================================================
output:
==================================================
My Config File
Item's Name: First Item
Item's Value: Value 1
Item's Name: Second Item
Item's Value: Value 2

Antwoord 13

@dibrovsd: Oplossing werkt niet als de xml meer dan één tag met dezelfde naam heeft

In jouw gedachtengang heb ik de code een beetje aangepast en geschreven voor algemene node in plaats van root:

from collections import defaultdict
def xml2dict(node):
    d, count = defaultdict(list), 1
    for i in node:
        d[i.tag + "_" + str(count)]['text'] = i.findtext('.')[0]
        d[i.tag + "_" + str(count)]['attrib'] = i.attrib # attrib gives the list
        d[i.tag + "_" + str(count)]['children'] = xml2dict(i) # it gives dict
     return d

Antwoord 14

Op een gegeven moment moest ik XML ontleden en schrijven die alleen uit elementen zonder attributen bestond, dus een 1:1-mapping van XML naar dict was gemakkelijk mogelijk. Dit is wat ik bedacht voor het geval iemand anders ook geen attributen nodig heeft:

def xmltodict(element):
    if not isinstance(element, ElementTree.Element):
        raise ValueError("must pass xml.etree.ElementTree.Element object")
    def xmltodict_handler(parent_element):
        result = dict()
        for element in parent_element:
            if len(element):
                obj = xmltodict_handler(element)
            else:
                obj = element.text
            if result.get(element.tag):
                if hasattr(result[element.tag], "append"):
                    result[element.tag].append(obj)
                else:
                    result[element.tag] = [result[element.tag], obj]
            else:
                result[element.tag] = obj
        return result
    return {element.tag: xmltodict_handler(element)}
def dicttoxml(element):
    if not isinstance(element, dict):
        raise ValueError("must pass dict type")
    if len(element) != 1:
        raise ValueError("dict must have exactly one root key")
    def dicttoxml_handler(result, key, value):
        if isinstance(value, list):
            for e in value:
                dicttoxml_handler(result, key, e)
        elif isinstance(value, basestring):
            elem = ElementTree.Element(key)
            elem.text = value
            result.append(elem)
        elif isinstance(value, int) or isinstance(value, float):
            elem = ElementTree.Element(key)
            elem.text = str(value)
            result.append(elem)
        elif value is None:
            result.append(ElementTree.Element(key))
        else:
            res = ElementTree.Element(key)
            for k, v in value.items():
                dicttoxml_handler(res, k, v)
            result.append(res)
    result = ElementTree.Element(element.keys()[0])
    for key, value in element[element.keys()[0]].items():
        dicttoxml_handler(result, key, value)
    return result
def xmlfiletodict(filename):
    return xmltodict(ElementTree.parse(filename).getroot())
def dicttoxmlfile(element, filename):
    ElementTree.ElementTree(dicttoxml(element)).write(filename)
def xmlstringtodict(xmlstring):
    return xmltodict(ElementTree.fromstring(xmlstring).getroot())
def dicttoxmlstring(element):
    return ElementTree.tostring(dicttoxml(element))

Antwoord 15

Ik heb een van de antwoorden naar mijn smaak aangepast en om met meerdere waarden met dezelfde tag te werken, overweeg bijvoorbeeld de volgende xml-code die is opgeslagen in het XML.xml-bestand

    <A>
        <B>
            <BB>inAB</BB>
            <C>
                <D>
                    <E>
                        inABCDE
                    </E>
                    <E>value2</E>
                    <E>value3</E>
                </D>
                <inCout-ofD>123</inCout-ofD>
            </C>
        </B>
        <B>abc</B>
        <F>F</F>
    </A>

en in python

import xml.etree.ElementTree as ET
class XMLToDictionary(dict):
    def __init__(self, parentElement):
        self.parentElement = parentElement
        for child in list(parentElement):
            child.text = child.text if (child.text != None) else  ' '
            if len(child) == 0:
                self.update(self._addToDict(key= child.tag, value = child.text.strip(), dict = self))
            else:
                innerChild = XMLToDictionary(parentElement=child)
                self.update(self._addToDict(key=innerChild.parentElement.tag, value=innerChild, dict=self))
    def getDict(self):
        return {self.parentElement.tag: self}
    class _addToDict(dict):
        def __init__(self, key, value, dict):
            if not key in dict:
                self.update({key: value})
            else:
                identical = dict[key] if type(dict[key]) == list else [dict[key]]
                self.update({key: identical + [value]})
tree = ET.parse('./XML.xml')
root = tree.getroot()
parseredDict = XMLToDictionary(root).getDict()
print(parseredDict)

de uitvoer is

{'A': {'B': [{'BB': 'inAB', 'C': {'D': {'E': ['inABCDE', 'value2', 'value3']}, 'inCout-ofD': '123'}}, 'abc'], 'F': 'F'}}

Antwoord 16

Ik heb een eenvoudige recursieve functie geschreven om het werk te doen:

from xml.etree import ElementTree
root = ElementTree.XML(xml_to_convert)
def xml_to_dict_recursive(root):
    if len(root.getchildren()) == 0:
        return {root.tag:root.text}
    else:
        return {root.tag:list(map(xml_to_dict_recursive, root.getchildren()))}

Antwoord 17

Een alternatief (maakt een lijst voor dezelfde tags in de hiërarchie):

from xml.etree import cElementTree as ElementTree
def xml_to_dict(xml, result):
    for child in xml:
        if len(child) == 0:
            result[child.tag] = child.text
        else:
            if child.tag in result:
                if not isinstance(result[child.tag], list):
                    result[child.tag] = [result[child.tag]]
                result[child.tag].append(xml_to_dict(child, {}))
            else:
                result[child.tag] = xml_to_dict(child, {})
    return result
xmlTree = ElementTree.parse('my_file.xml')
xmlRoot = xmlTree.getroot()
dictRoot = xml_to_dict(xmlRoot, {})
result = {xmlRoot.tag: dictRoot}

Antwoord 18

Ik heb een recursieve methode om een woordenboek van een lxml-element te krijgen

   def recursive_dict(element):
        return (element.tag.split('}')[1],
                dict(map(recursive_dict, element.getchildren()),
                     **element.attrib))

Other episodes