Повторение элементов в новых строках ElementTree - PullRequest
0 голосов
/ 06 января 2020

Код ниже берет каталог файлов XML и анализирует их в CSV f ie. Это было возможно только для пользователя в этом сообществе. Я так много узнал.

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>

Результаты выглядят как на рисунке ниже. enter image description here

Недавно я пытался изменить код, чтобы результаты выглядели примерно так, как показано на рисунке ниже. Давайте возьмем id = ”ID0048”, код анализирует id, service_code только один раз, но если в SetData несколько строк, он создаст новую строку, но не будет повторять id, service_code и другие. Пытаясь достичь чего-то похожего на картинку ниже

enter image description here

Ответы [ 2 ]

0 голосов
/ 06 января 2020

Рассмотрим язык специального назначения, 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 logi c)

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

Онлайн-демонстрация

CSV Output


К л oop в папке XML файлов, просто интегрируйте выше в al oop. Здесь все процессы обработки 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)
0 голосов
/ 06 января 2020

Этот код должен делать:

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 самый повторяемый узел, который мы можем использовать для определения количества раз, когда нам нужно записывать другие данные.

Этот подход не будет работать, если это предположение неверно.

...