【问题标题】:Repeating elements to new rows ElementTree将元素重复到新行 ElementTree
【发布时间】:2020-04-24 05:16:36
【问题描述】:

下面的代码获取一个 XML 文件目录并将它们解析为 CSV 文件。这仅适用于该社区中的用户。我学到了很多。

from xml.etree import ElementTree as ET
from collections import defaultdict
import csv
from pathlib import Path

directory = 'C:/Users/docs/FolderwithXMLs'

with open('output.csv', 'w', newline='') as f:
    writer = csv.writer(f)

    headers = ['id', 'service_code', 'rational', 'qualify', 'description_num', 'description_txt', 'set_data_xin', 'set_data_xax', 'set_data_value', 'set_data_x']

    writer.writerow(headers)

    xml_files_list = list(map(str,Path(directory).glob('**/*.xml')))
    for xml_file in xml_files_list:
        tree = ET.parse(xml_file)
        root = tree.getroot()

        start_nodes = root.findall('.//START')
        for sn in start_nodes:
            row = defaultdict(str)


            for k,v in sn.attrib.items():
                row[k] = v

            for rn in sn.findall('.//Rational'):
                row['rational'] = rn.text

            for qu in sn.findall('.//Qualify'):
                row['qualify'] = qu.text

            for ds in sn.findall('.//Description'):
                row['description_txt'] = ds.text
                row['description_num'] = ds.attrib['num']


            for st in sn.findall('.//SetData'):
                for k,v in st.attrib.items():
                    row['set_data_'+ str(k)] = v
                row_data = [row[i] for i in headers]
                writer.writerow(row_data)
                row = defaultdict(str)

另一方面,xml 文件具有这样的格式

<?xml version="1.0" encoding="utf-8"?>
<ProjectData>
<FINAL>
    <START id="ID0001" service_code="0x5196">
      <Docs Docs_type="START">
        <Rational>225196</Rational>
        <Qualify>6251960000A0DE</Qualify>
      </Docs>
      <Description num="1213f2312">The parameter</Description>
      <DataFile dg="12" dg_id="let">
        <SetData value="32" />
      </DataFile>
    </START>
    <START id="DG0003" service_code="0x517B">
      <Docs Docs_type="START">
        <Rational>23423</Rational>
        <Qualify>342342</Qualify>
      </Docs>
      <Description num="3423423f3423">The third</Description>
      <DataFile dg="55" dg_id="big">
        <SetData x="E1" value="21259" />
        <SetData x="E2" value="02" />
      </DataFile>
    </START>
    <START id="ID0048" service_code="0x5198">
      <RawData rawdata_type="ASDS">
        <Rational>225198</Rational>
        <Qualify>343243324234234</Qualify>
      </RawData>
      <Description num="434234234">The forth</Description>
      <DataFile unit="21" unit_id="FEDS">
        <FileX unit="eg" discrete="false" axis_pts="19" name="Vsome" text_id="bx5" unit_id="GDFSD" />
        <SetData xin="5" xax="233" value="323" />
        <SetData xin="123" xax="77" value="555" />
        <SetData xin="17" xax="65" value="23" />
      </DataFile>
    </START>
</FINAL>
</ProjectData>

结果如下图所示。

最近我一直在尝试修改代码,使结果看起来类似于下图。 以id=”ID0048”为例,代码只解析一次id、service_code,但如果有多行SetData,它会创建一个新行,但不会重复id、service_code等。努力实现如下图所示

【问题讨论】:

    标签: python xml dataframe elementtree


    【解决方案1】:

    这段代码应该这样做:

    from xml.etree import ElementTree as ET
    from collections import defaultdict
    import csv
    from pathlib import Path
    
    directory = '.'
    
    with open('output.csv', 'w', newline='') as f:
        writer = csv.writer(f)
    
        headers = ['id', 'service_code', 'rational', 'qualify', 'description_num', 'description_txt', 'set_data_xin', 'set_data_xax', 'set_data_value', 'set_data_x']
    
        writer.writerow(headers)
    
        xml_files_list = list(map(str, Path(directory).glob('**/*.xml')))
        print(xml_files_list)
        for xml_file in xml_files_list:
            tree = ET.parse(xml_file)
            root = tree.getroot()
    
            start_nodes = root.findall('.//START')
            for sn in start_nodes:
                row = defaultdict(str)
    
                repeated_values = dict()
                for k,v in sn.attrib.items():
                    repeated_values[k] = v
    
                for rn in sn.findall('.//Rational'):
                    repeated_values['rational'] = rn.text
    
                for qu in sn.findall('.//Qualify'):
                    repeated_values['qualify'] = qu.text
    
                for ds in sn.findall('.//Description'):
                    repeated_values['description_txt'] = ds.text
                    repeated_values['description_num'] = ds.attrib['num']
    
    
                for st in sn.findall('.//SetData'):
                    for k,v in st.attrib.items():
                        row['set_data_'+ str(k)] = v
                    for key in repeated_values.keys():
                        row[key] = repeated_values[key]
                    row_data = [row[i] for i in headers]
                    writer.writerow(row_data)
                    row = defaultdict(str)
    

    说明

    问题是问题代码中 XML 文档的所有节点都只写入 CSV 一次。假设我们有SetData 最可重复的节点,我们可以使用它来确定我们必须写入其他数据的次数。

    如果这个假设不正确,这种方法将不起作用。

    【讨论】:

    • 您能否提供结果的屏幕截图,因为使用与上面相同的 xml 文件和您的代码,我只得到标题,没有其他内容
    • @ebe 我明天可以发布 output.csv 内容。对我来说效果很好。你使用什么版本的 Python?还请确保您指定正确的路径 - 在我的示例中,它会在当前目录中查找 XML 文件,这不一定是您想要的。
    • 如果您查看 xml 文件,您可以看到 Xaxis。我一直在尝试添加它,但它的一些元素给了我一个 KeyError。你可以看到哪些给我一个错误。 pasteboard.co/IP0gCnK.png
    • 你能看看上面的评论吗?已经被这个问题困了几个小时了。会很感激的
    • @ebe 我建议将此作为一个单独的问题发布,就好像我没有弄错它已经是一个不同的问题一样。
    【解决方案2】:

    考虑专用语言XSLT,使用Python 的第三方模块lxml 直接将XML 转换为CSV 输出。具体来说,让 XSLT 从较低级别拉取 SetData 并使用 ancestor 检索较高级别信息。

    XSLT (另存为.xsl文件,特殊的.xml文件)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output indent="yes" method="text"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:variable name="delim">,</xsl:variable>
      <xsl:template match="/ProjectData">
          <!------------------------------- HEADERS ------------------------------->
          <xsl:text>id,service_code,rational,qualify,description_num,description,</xsl:text>
          <xsl:text>data_file_dg,data_file_dg_id,data_file_unit,data_file_unit_id,</xsl:text>
          <xsl:text>set_data_x,set_data_xin,set_data_xat,set_data_value&#xa;</xsl:text>
          <!-----------------------------------------------------------------------> 
          <xsl:apply-templates select="descendant::SetData"/>
      </xsl:template>
    
      <xsl:template match="SetData">
          <xsl:value-of select="concat(ancestor::START/@id, $delim,
                                       ancestor::START/@service_code, $delim,
                                       ancestor::START/*[1]/Rational, $delim,
                                       ancestor::START/*[1]/Qualify, $delim,
                                       ancestor::START/Description/@num, $delim,
                                       ancestor::START/Description, $delim,
                                       ancestor::START/DataFile/@dg, $delim,
                                       ancestor::START/DataFile/@dg_id, $delim,
                                       ancestor::START/DataFile/@unit, $delim,
                                       ancestor::START/DataFile/@unit_id, $delim,
                                       @x, $delim,
                                       @xin, $delim,
                                       @xat, $delim,
                                       @value)"/>
          <xsl:text>&#xa;</xsl:text>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Python (没有for 循环或if/else 逻辑)

    import lxml.etree as et
    
    # LOAD XML AND XSL FILES
    xml = et.parse('Input.xml')
    xsl = et.parse('Script.xsl')
    
    # INITIALIZE TRANSFORMER
    transform = et.XSLT(xsl)
    
    # TRANSFORM INPUT
    result = transform(xml)
    
    print(str(result))
    # id,service_code,rational,qualify,description_num,description,data_file_dg,data_file_dg_id,data_file_unit,data_file_unit_id,set_data_x,set_data_xin,set_data_xat,set_data_value
    # ID0001,0x5196,225196,6251960000A0DE,1213f2312,The parameter,12,let,,,,,,32
    # DG0003,0x517B,23423,342342,3423423f3423,The third,55,big,,,E1,,,21259
    # DG0003,0x517B,23423,342342,3423423f3423,The third,55,big,,,E2,,,02
    # ID0048,0x5198,225198,343243324234234,434234234,The forth,,,21,FEDS,,5,,323
    # ID0048,0x5198,225198,343243324234234,434234234,The forth,,,21,FEDS,,123,,555
    # ID0048,0x5198,225198,343243324234234,434234234,The forth,,,21,FEDS,,17,,23
    
    # SAVE XML TO CSV
    with open('Output.csv', 'wb') as f:
        f.write(str(result))
    

    Online Demo


    要遍历 XML 文件的文件夹,只需将上述集成到一个循环中即可。这里将所有 XML 处理包装到一个方法中,以通过列表理解构建结果列表,并最终迭代地写入 CSV。 注意:对于一组标头,仅将标头放在 CSV 中并从 XSLT 中删除,如上所示。

    import lxml.etree as et
    from pathlib import Path
    
    # LOAD XSL SCRIPT
    xsl = et.parse('Script.xsl')   # LOAD XML FILE ONCE (REMOVE HEADERS)
    
    def proc_xml(xml_file):     
        xml = et.parse(xml_file)   # LOAD XML FILE  
        transform = et.XSLT(xsl)   # INITIALIZE TRANSFORMER
        result = transform(xml)    # TRANSFORM INPUT    
        return str(result)
    
    xml_files_list = list(map(str,Path(directory).glob('**/*.xml')))
    results = [proc_xml(x) for x in xml_files_list]
    
    with open('Output.csv', 'w', newline='') as f:
        f.write('id,service_code,rational,qualify,description_num,description,'
                'data_file_dg,data_file_dg_id,data_file_unit,data_file_unit_id,'
                'set_data_x,set_data_xin,set_data_xat,set_data_value\n')
    
        # SAVE XML TO CSV
        for r in results:
            f.write(r)
    

    【讨论】:

    • 如果您可以编辑代码,我将不胜感激,因为它需要一个目录,其中有很多 xml 文件,所有这些都被解析为一个 csv
    • 我确实这样做了。请参阅底部的扩展答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 2014-05-24
    相关资源
    最近更新 更多