【问题标题】:How parse XML into list of dicts?如何将 XML 解析为字典列表?
【发布时间】:2021-12-31 19:57:04
【问题描述】:

给定下面的示例 xml:

<_Document>
  <_Data1> 'foo'
    <_SubData1> 'bar1' </_SubData1>
    <_SubData2> 'bar2' </_SubData2>
    <_SubData3> 'bar3' </_SubData3>
  </_Data1>
</_Document>

我想捕获每个 SubData 值并使用字典中的 Data1 值对其进行更新,然后将该值附加到列表中。这样输出看起来像:

[{Data1: 'foo', SubData1: 'bar1'}, {Data1: 'foo', SubData2: 'bar2'}, {Data1: 'foo', SubData3: 'bar3'}]

我的代码是:

from lxml import etree
import re

new_records = []
   
for child in root.iter('_Document'): #finding all children with each 'Document' string
    for top_data in child.iter(): #iterating through the entirety of each 'Document' sections tags and text. 
        
        if "Data" in top_data.tag:
            for data in top_data:
                rec = {}
                if data.text is not None and data.text.isspace() is False: #avoiding NoneTypes and empty data.
                    g = data.tag.strip("_") #cleaning up the tag
                    rec[g] = data.text.replace("\n", " ") #cleaning up the value
                 
            for b in re.finditer(r'^_SubData', data.tag): #searching through each 'SubData' contained in a given tag. 
                for subdata in data:
                    subdict = {}
                    if subdata.text is not None: #again preventing NoneTypes
                        z = subdata.tag.strip("_") #tag cleaning
                        subdict[z] = subdata.text.replace("\n", " ") #text cleaning
                    rec.update(subdict) #update the data record dictionary with the subdata
                new_records.append(rec) #appending to the list

不幸的是,这会输出:

[{Data1: 'foo', SubData3: 'bar3'}]

因为它只更新和追加字典的最终更新。

我尝试了不同的变体,包括在第二个 for 循环中的第一个“if”语句之后初始化一个列表,以便在每个循环通过后追加,但这需要在最后进行相当多的清理才能通过嵌套它会导致。 我还尝试在循环之外初始化空字典以更新以保留以前的更新并以这种方式追加。

我很好奇我是否遗漏了 lxml 的某些功能,或者是否有更 Python 的方法来获得所需的输出。

【问题讨论】:

    标签: python python-3.x xml dictionary lxml


    【解决方案1】:

    我在another solution 中提供了我认为的声明性 方法。如果您更愿意用循环显式定义结构,这里有一个必要的 方法:

    from xml.etree import ElementTree as ET
    import pprint
    
    new_records = []
    
    document = ET.parse('input.xml').getroot()
    
    for elem in document:
        if elem.tag.startswith('_Data'):
            data = elem
            data_name = data.tag[1:]  # skip leading '_'
            data_val = data.text.strip()
    
            for elem in data:
                if elem.tag.startswith('_SubData'):
                    subdata = elem
                    subdata_name = subdata.tag[1:]
                    subdata_val = subdata.text.strip()
    
                    new_records.append(
                        {data_name: data_val, subdata_name: subdata_val}
                    )
    
    pprint.pprint(new_records)
    

    输入输出和我的其他方案一样。

    【讨论】:

      【解决方案2】:

      您可以使用 Python 的内置 ElementTree 类及其 iterparse() 方法来执行此操作,该方法遍历 XML 树并生成一对 event元素 通过树的每一步。我们监听它何时开始解析一个元素,如果它的_Data..._SubData... 我们采取行动。

      这是一种声明性方法,并且依赖于_SubData 只是_Data 的一个孩子这一事实,也就是说,您的非常小而简单的样本完全代表了您'实际上正在处理。

      您需要为_Data 元素管理一些状态,仅此而已:

      from xml.etree import ElementTree as ET
      import pprint
      
      new_records = []
      data_name = None
      data_val = None
      
      for event, elem in ET.iterparse('input.xml', ['start']):
          tag_name = elem.tag[1:]  # skip possible leading '_'
      
          if event == 'start' and tag_name.startswith('Data'):
              data_name = tag_name
              data_val = elem.text.strip()
      
          if event == 'start' and tag_name.startswith('SubData'):
              subdata_name = tag_name
              subdata_val = elem.text.strip()
              record = {
                  data_name: data_val, subdata_name: subdata_val
              }
              new_records.append(record)
      
      pprint.pprint(new_records)
      

      我修改了你的示例,我的 input.xml

      <_Document>
          <_Data1>foo
            <_SubData1>bar1</_SubData1>
            <_SubData2>bar2</_SubData2>
            <_SubData3>bar3</_SubData3>
          </_Data1>
          <_Data2>FOO
            <_SubData1>BAR1</_SubData1>
            <_SubData2>BAR2</_SubData2>
            <_SubData3>BAR3</_SubData3>
          </_Data2>
        </_Document>
      

      当我在那个输入上运行我的脚本时,我得到:

      [{'Data1': 'foo', 'SubData1': 'bar1'},
       {'Data1': 'foo', 'SubData2': 'bar2'},
       {'Data1': 'foo', 'SubData3': 'bar3'},
       {'Data2': 'FOO', 'SubData1': 'BAR1'},
       {'Data2': 'FOO', 'SubData2': 'BAR2'},
       {'Data2': 'FOO', 'SubData3': 'BAR3'}]
      

      【讨论】:

        【解决方案3】:

        考虑使用dictionary merge 的字典理解:

        new_records = [
            {
                **{doc.tag.replace('_', ''): doc.text.strip().replace("'", "")},
                **{data.tag.replace('_', ''): data.text.strip().replace("'", "")}
            }
            
            for doc in root.iterfind('*')
            for data in doc.iterfind('*')
        ]           
        
        new_records
        [{'Data1': 'foo', 'SubData1': 'bar1'},
         {'Data1': 'foo', 'SubData2': 'bar2'},
         {'Data1': 'foo', 'SubData3': 'bar3'}]
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-02-11
          • 1970-01-01
          • 1970-01-01
          • 2013-07-14
          • 2014-10-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多