如何将XML字符串转换为字典?

183

我有一个从套接字读取XML文档的程序。 我已将XML文档存储在字符串中,我希望能够直接将其转换为Python字典,就像Django的simplejson库中所做的那样。

以此为例:

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

那么dic_xml将会是这样的{'person' : { 'name' : 'john', 'age' : 20 } }


1
str存在一些语法错误。尝试:str ='<?xml version="1.0" ?><person><name>john</name><age>20</age></person>' - Keir
21个回答

393

xmltodict(完全透明:我是它的作者)正好可以做到这一点:

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

4
另外,对于未来的谷歌航天员 - 我能够在App Engine中使用它,而我之前被告知大多数Python中的xml库与其不兼容。 - LRE
2
u 只是表示它存储的是 Unicode 字符串,不会以任何方式影响字符串的值。 - Joshua Olson
3
好的。没错,@ypercube,有一个xmldict.unparse()函数来完成相反的操作。 - Duther
2
这太棒了。我无法表达我有多么不喜欢XML。我希望我早些年就发现了它(而不是ETree、XPATH和那个可怕的混乱)。顺便说一句,这可能会帮助其他人,我没有意识到无法pprint.pprint()一个OrderedDict(这是xmltodict.parse()的结果)。我使用json.loads(json.dumps("my-XML-string-object"))来使pprint.pprint()正常工作。再次感谢! - user2460464
4
这个模块在2022年还活跃吗?GitHub上有90个问题,其中包括安全问题,在大约两年左右没有更新... - shearn89
显示剩余14条评论

85
这是一个很棒的模块,有人创建了它。我已经使用过几次了。 http://code.activestate.com/recipes/410469-xml-as-dictionary/ 以下是网站上的代码,以防链接失效。
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})

示例用法:

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

//或者,如果你想使用XML字符串:

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

4
你可以使用"xmltodict"作为替代方案。 - mrash
8
我试过这个方法,比xmltodict快得多。对于解析一个80MB的xml文件,它只需要7秒,而使用xmltodict需要90秒。 - Eddy
12
您好,这段代码完美运行。为了那些找不到 cElementTree 的人,只需将第一行改为以下内容即可:from xml.etree import cElementTree as ElementTree - Rafael Aguilar
3
由于下面有更好的答案,特别是在处理具有相同名称的多个标签方面,因此进行了负投票。 - Maksym
2
顺便提一下,如果您不需要使用Python,只是想将XML导入为结构化对象进行操作,我发现使用R要容易得多,如此处此处所述。如果您只运行library("XML"); result <- xmlParse(file = "file.xml"); xml_data <- xmlToList(result),则会将XML作为嵌套列表导入。具有相同名称的多个标记很好,标记属性成为额外的列表项。 - user5359531
显示剩余5条评论

67
下面的XML转Python字典片段解析实体以及按照这个XML转JSON“规范”处理属性。它是处理所有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

它被使用:

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

此示例的输出(根据上面链接的“规范”)应该是:
{'root': {'e': [None,
                'text',
                {'@name': 'value'},
                {'#text': 'text', '@name': 'value'},
                {'a': 'text', 'b': 'text'},
                {'a': ['text', 'text']},
                {'#text': 'text', 'a': 'text'}]}}

不一定漂亮,但明确无误,简单的XML输入会导致简单的JSON。 :)

更新

如果你想进行反向操作,即从 JSON/dict 发出一个XML字符串,你可以使用以下方法:

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
谢谢这段代码! 额外信息:如果您使用的是Python 2.5,那么您无法使用字典推导式,因此您需要更改以下行: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() ) } - M--
2
我已经测试了近10个片段/Python模块等,这是我发现的最好的一个。根据我的测试,它比https://github.com/martinblech/xmltodict(基于XML SAX api)快得多,比https://github.com/mcspring/XML2Dict更好,当几个子元素具有相同名称时,它会出现一些小问题,比http://code.activestate.com/recipes/410469-xml-as-dictionary/更好,因为前面的代码都比较短!感谢@K3---rnc。 - Basj
这绝对是最全面的答案,适用于> 2.6版本,并且相当灵活。 我唯一的问题是,文本可能会根据是否存在属性而改变其所在位置。我还发布了一个更小、更严格的解决方案。 - Erik Aronesty
1
如果您需要从XML文件获取有序字典,请使用此示例进行少量修改(请参见下面的我的回复):https://dev59.com/IXI95IYBdhLWcg3wzRTv - serfer2
这也非常巧妙和快速,特别是与cElementTreelxml.etree一起使用时。请注意,在使用Python 3时,所有的.iteritems()都必须改为.items()(行为相同,但关键字从Python 2到3发生了变化)。 - Dirk
显示剩余2条评论

46

这个轻量级版本虽然不可配置,但很容易根据需要进行调整,并且适用于旧版 Python。此外,它是刚性的——这意味着结果不受属性存在与否的影响。

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

所以:

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

dictify(root)

结果为:

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

7
我喜欢这个解决方案。简单且不需要外部库。 - MattK
1
我也喜欢这个答案,因为它都在我的面前(没有外部链接)。干杯! - MrMas
我也喜欢它。对于复杂的XML,它可以产生良好的结果,而对于上面的XmlListConfig类则不是这种情况。 - Eric H.

8

免责声明:这个改进的XML解析器受到Adam Clark的启发。原始的XML解析器适用于大多数简单情况。然而,对于一些复杂的XML文件,它不能正常工作。我逐行调试代码,最终解决了一些问题。如果您发现一些错误,请告诉我,我很乐意修复。

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)    

6
最新版本的PicklingTools库(1.3.0和1.3.1)支持将XML转换为Python字典的工具。
下载链接在这里:PicklingTools 1.3.1 有相当多的文档可以用于转换器 这里:文档详细描述了在XML和Python字典之间转换时会出现的所有决策和问题(有许多边缘情况:属性、列表、匿名列表、匿名字典、eval等,大多数转换器都无法处理)。总的来说,转换器很容易使用。如果“example.xml”包含以下内容:
<top>
  <a>1</a>
  <b>2.2</b>
  <c>three</c>
</top>

然后将其转换为字典:
>>> 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'}}

有两种转换工具可用于C++和Python:C++和Python的转换效果相同,但C++的速度大约快60倍。


当然,如果有两个a,则这不是一个好的格式。 - Erik Aronesty
1
看起来很有趣,但我还没有弄清楚PicklingTools的使用方式 - 这只是源代码文件的tarball,我必须从中找到适合我的工作的正确文件,然后将它们复制到我的项目中吗?没有要加载的模块或更简单的东西吗? - Dirk
我得到了以下错误信息:在_peekIntoNextNWSChar中, c = self.is_.read(1) AttributeError: 'str'对象没有'read'属性。 - sqp_125

6

您可以使用lxml轻松完成此操作。首先安装它:

[sudo] pip install lxml

这里是我编写的递归函数,可以为您完成繁重的工作:
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)

下面的变体保留了父键/元素:
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)}

如果你只想返回一个子树并将其转换为字典格式,可以使用 Element.find() 获取该子树然后进行转换:

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

请参阅 此处 的 lxml 文档。希望这能有所帮助!


5
我写了一个简单的递归函数来完成这个任务:
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()))}

迄今为止最简单的解决方案! - Michael_Scharf

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

3
http://code.activestate.com/recipes/410469-xml-as-dictionary/的代码效果不错,但如果在层次结构中有多个相同元素,则仅覆盖它们。 我添加了一个shim来查看元素是否已经存在,然后再进行self.update()。如果存在,则弹出现有条目并创建一个包含现有和新的列表。任何后续重复项都将添加到列表中。 不确定是否有更好的方法来处理这种情况,但它可以正常工作。
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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接