【问题标题】:Converting xml to dictionary using ElementTree使用 ElementTree 将 xml 转换为字典
【发布时间】:2011-10-07 07:38:23
【问题描述】:

我正在寻找使用 ElementTree 的 XML 到字典解析器,我已经找到了一些,但它们不包括属性,在我的情况下,我有很多属性。

【问题讨论】:

    标签: python xml dictionary elementtree


    【解决方案1】:

    以下 XML-to-Python-dict sn-p 解析实体以及 this XML-to-JSON "specification" 之后的属性:

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

    这个例子的输出(根据上面链接的“规范”)应该是:

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

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


    更新

    如果你想做 reverse,从 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, 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)))
    

    【讨论】:

    • 如果一个节点没有文本(例如第一个&lt;e&gt;节点——你得到AttributeError: 'NoneType' object has no attribute 'strip'
    • 有没有逆向(dict -> xml)转换的例子?
    • 这是我尝试过的最好的 xml -> dict 之一(有很多:xmltodict,几个网站上的几个食谱等)
    • 正如@Basj 所说 - 这是我尝试过的最好的 XML-> dict 实现。我已经尝试了很多。
    • 根据我的经验,这个解决方案没有考虑到文档根目录的 xmlns。简单的解决方案是将其剥离。我发现this question 是一个有用的补充
    【解决方案2】:
    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
    

    调用为

    tree = etree.parse("some_file.xml")
    etree_to_dict(tree.getroot())
    

    只要您实际上没有属性text,它就可以工作;如果这样做,则将函数体中的第三行更改为使用不同的键。此外,您无法使用此处理混合内容。

    (在 LXML 上测试。)

    【讨论】:

    • 我在 iterchildren 中遇到了错误,所以我改为 getchildren,在这个例子中,我得到了属性,但节点值为空,例如:{'Tag': 'Lidars', ' lidars_list': [{'positive_towards_LOS': 'false', 'scanner_3D': 'true', 'lidar': [{'name': []},名称是 LNAC,但我得到的是一个空字典
    • @OHLÁLÁ 您好,您是否修改了将 XML 转换为 Dictionary 的代码?谢谢
    • 这会返回一个map(不管它是什么)作为字典的第一个键的值,而不是嵌套字典。
    【解决方案3】:

    基于@larsmans,如果您不需要属性,这将为您提供更严格的字典--

    def etree_to_dict(t):
        return {t.tag : map(etree_to_dict, t.iterchildren()) or t.text}
    

    【讨论】:

      【解决方案4】:

      对于将 XML 从/转换为 python 字典,xmltodict 非常适合我:

      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 现在看起来像

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

      如果您的 XML 数据不是原始字符串/字节形式,而是在某些 ElementTree 对象中,您只需将其打印为字符串并再次使用 xmldict.parse。例如,如果您使用 lxml 来处理 XML 文档,那么

      from lxml import etree
      e = etree.XML(xml)
      xmltodict.parse(etree.tostring(e))
      

      将产生与上述相同的字典。

      【讨论】:

        【解决方案5】:

        这是一个简单的xml数据结构(另存为file.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>
        

        这是从中创建字典对象列表的代码。

        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 现在包含:

        [{'First': 'John', 'Last': 'Smith'},{'First': 'Jane', 'Last': 'Doe'}]
        

        并且可以像这样访问:

        datadict[0]['First']
        'John'
        datadict[1]['Last']
        'Doe'
        

        【讨论】:

        • 如果有子标签怎么办?
        • 这样考虑:JohnSmithyesrichJaneDoe yesrichyes数据>
        【解决方案6】:

        你可以用这个sn-p直接把xml转成字典

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

        【讨论】:

        • 工作正常。谢谢
        【解决方案7】:

        lxml文档带来了how to map an XML tree into a dict of dicts的例子:

        def recursive_dict(element):
            return element.tag, dict(map(recursive_dict, element)) or element.text
        

        请注意,这个漂亮的又快又脏的转换器希望孩子拥有唯一的标签名称,并且会默默地覆盖之前同名兄弟姐妹中包含的任何数据。对于任何 xml 到 dict 转换的实际应用,您最好编写自己的更长版本。

        您可以创建一个自定义字典来处理前面同名的兄弟姐妹被覆盖:

        from collections import UserDict, namedtuple
        from lxml.etree import QName
        
        class XmlDict(UserDict):
            """Custom dict to avoid preceding siblings with the same name being overwritten."""
        
            __ROOTELM = namedtuple('RootElm', ['tag', 'node'])
        
            def __setitem__(self, key, value):
                if key in self:
                    if type(self.data[key]) is list:
                        self.data[key].append(value)
                    else:
                        self.data[key] = [self.data[key], value]
                else:
                    self.data[key] = value
        
            @staticmethod
            def xml2dict(element):
                """Converts an ElementTree Element to a dictionary."""
                elm = XmlDict.__ROOTELM(
                    tag=QName(element).localname,
                    node=XmlDict(map(XmlDict.xml2dict, element)) or element.text,
            )
            return elm
        

        用法

        from lxml import etree
        from pprint import pprint
        
        xml_f = b"""<?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>"""
        
        elm = etree.fromstring(xml_f)
        d = XmlDict.xml2dict(elm)
        

        输出

        In [3]: pprint(d)
        RootElm(tag='Data', node={'Person': [{'First': 'John', 'Last': 'Smith'}, {'First': 'Jane', 'Last': 'Doe'}]})
        
        In [4]: pprint(d.node)
        {'Person': [{'First': 'John', 'Last': 'Smith'},
                    {'First': 'Jane', 'Last': 'Doe'}]}
        

        【讨论】:

        • 对我来说这返回一个tuple,而不是dictionary
        • 当然可以改进。
        【解决方案8】:

        已经有几个答案了,但这里有一个紧凑的解决方案,它使用 dict-comprehension 映射属性、文本值和子项:

        def etree_to_dict(t):
            if type(t) is ET.ElementTree: return etree_to_dict(t.getroot())
            return {
                **t.attrib,
                'text': t.text,
                **{e.tag: etree_to_dict(e) for e in t}
            }
        

        【讨论】:

          【解决方案9】:

          使用 python3 增强了接受的答案,并在所有孩子都具有相同标签时使用 json 列表。还提供了一个选项是否用根标签包装字典。

          from collections import OrderedDict
          from typing import Union
          from xml.etree.ElementTree import ElementTree, Element
          
          def etree_to_dict(root: Union[ElementTree, Element], include_root_tag=False):
              root = root.getroot() if isinstance(root, ElementTree) else root
              result = OrderedDict()
              if len(root) > 1 and len({child.tag for child in root}) == 1:
                  result[next(iter(root)).tag] = [etree_to_dict(child) for child in root]
              else:
                  for child in root:
                      result[child.tag] = etree_to_dict(child) if len(list(child)) > 0 else (child.text or "")
              result.update(('@' + k, v) for k, v in root.attrib.items())
              return {root.tag: result} if include_root_tag else result
          
          d = etree_to_dict(etree.ElementTree.parse('data.xml'), True)
          

          【讨论】:

          • 有点复杂,但很好。没有检查 ElementTree,但对于 lxml,元素对象可以被排序并且已经有长度。例如,您可以使用if len(root) &gt; 1 and len({child.tag for child in root}) == 1而不是children = list(root) if len(children) &gt; 1 and len({child.tag for child in children}) == 1
          • 感谢@SergeyNudnov!更新了我的代码
          【解决方案10】:
          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
          

          【讨论】:

            【解决方案11】:

            基于@larsmans,如果生成的键包含 xml 命名空间信息,您可以在写入字典之前将其删除。设置一个变量xmlns等于命名空间,并去掉它的值。

            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
            

            【讨论】:

              猜你喜欢
              • 2016-02-22
              • 1970-01-01
              • 2016-08-17
              • 1970-01-01
              • 1970-01-01
              • 2021-03-28
              • 1970-01-01
              • 2012-12-06
              • 2010-12-15
              相关资源
              最近更新 更多