Python - XML ​​- LXML - Как использовать ту же древовидную структуру XML из образца XML-файла для создания другого XML-файла - PullRequest
1 голос
/ 05 апреля 2019

У меня есть XML-файл с некоторой древовидной структурой, элементами, атрибутами, текстом. Мне нужно использовать этот XML в качестве шаблона (древовидную структуру и теги) и создать другой XML, который может не иметь одинакового количества элементов (т. Е. В шаблоне ниже есть два элемента 'column', но один, который я хочу создать, имеет три элементы 'столбец').

Ниже приведен XML-код, который я хочу использовать в качестве шаблона

<custom-data>
    <schema>SCHEMA</schema>
    <columns>
      <column>
        <name>ORGANIZATION</name>
        <datatype>NUMBER</datatype>
        <length/>
        <precision>18</precision>
        <not-null>Yes</not-null>
      </column>
      <column>
        <name>LOCATION</name>
        <datatype>NUMBER</datatype>
        <length/>
        <precision>18</precision>
        <not-null>Yes</not-null>
      </column>
    </columns>
</custom-data>

Вместо того, чтобы определять похожее дерево, используя lxml, определяя каждый элемент один за другим, как показано ниже. Например, если 'df' - мой фрейм данных для панд с данными. который имеет столбцы как (целевой столбец, тип данных, длина, масштаб, обнуляемый ._

Target Column   Data Type   Length  Scale   Nullable
COLUMN1          NUMBER       38    0   N
COLUMN2          NUMBER       38    0   N
COLUMN3          NUMBER       38    0   N

Ниже приведен пример кода Python, который я использую

from lxml import etree as et
root = et.Element('custom-data')
schema= et.SubElement(root, 'schema')
schema.text='SCHEMA'
columns= et.SubElement(root, 'columns')

for row in df.iterrows():
    column = et.SubElement(columns, 'columns')
    name = et.SubElement(column , 'name')
    datatype = et.SubElement(column , 'datatype')
    length = et.SubElement(column , 'length')
    precision = et.SubElement(column , 'precision')
    notnull = et.SubElement(column , 'not-null')

    name.text = str(row[1]['Target Column'])
    datatype.text = str(row[1]['Data Type'])
    length.text = str(row[1]['Length'])
    precision.text = str(row[1]['Scale'])
    notnull.text = str(row[1]['Nullable'])

xml_test=et.tostring(root, pretty_print=True).decode('utf-8')
f=open("xml_test.xml", "w")
f.write(xml_test)

Ожидаемый результат -

<custom-data>
    <schema>SCHEMA</schema>
    <columns>
      <column>
        <name>COLUMN1</name>
        <datatype>NUMBER</datatype>
        <length>38</length>
        <precision>0</precision>
        <not-null>N</not-null>
      </column>
      <column>
        <name>COLUMN2</name>
        <datatype>NUMBER</datatype>
        <length>38</length>
        <precision>0</precision>
        <not-null>N</not-null>
      </column>
      <column>
        <name>COLUMN3</name>
        <datatype>NUMBER</datatype>
        <length>38</length>
        <precision>0</precision>
        <not-null>N</not-null>
      </column>
    </columns>
</custom-data>

Как я могу использовать структуру, приведенную в примере XML, чтобы мне не нужно было снова определять ее в моем коде. Какой-нибудь более простой метод?

1 Ответ

0 голосов
/ 05 апреля 2019

Вы должны быть в состоянии проанализировать шаблон XML и использовать копию элемента "column", чтобы сделать новые копии заполненными данными из вашего DataFrame.

Шаблон должен быть очищен и упрощен, чтобы содержать только статические значения и один элемент «столбец» для использования в качестве шаблона.

Вот пример ...

Шаблон XML (template.xml)

<custom-data>
    <schema>SCHEMA</schema>
    <columns>
        <column>
            <name/>
            <datatype/>
            <length/>
            <precision/>
            <not-null/>
        </column>
    </columns>
</custom-data>

Python

import pandas as pd
from copy import deepcopy
from lxml import etree as et

# Mock up DataFrame for testing.
data = {"Target Column": ["COLUMN1", "COLUMN2", "COLUMN3"],
        "Data Type": ["NUMBER", "NUMBER", "NUMBER"],
        "Length": [38, 38, 38],
        "Scale": [0, 0, 0],
        "Nullable": ["N", "N", "N"]}
df = pd.DataFrame(data=data)

# Create ElementTree from template XML.
tree = et.parse("template.xml")

# Map column names to element names.
name_map = {"Target Column": "name",
            "Data Type": "datatype",
            "Length": "length",
            "Scale": "precision",
            "Nullable": "not-null"}

# Select "columns" element so we can append/remove children.
columns_elem = tree.xpath("/custom-data/columns")[0]
# Select "column" element to use as a template for new column elements.
column_template = columns_elem.xpath("column")[0]

for row in df.iterrows():
    # Create a new copy of the column template.
    new_column = deepcopy(column_template)

    # Populate elements in column template based on name map.
    for col_name, elem_name in name_map.items():
        new_column.xpath(elem_name)[0].text = str(row[1][col_name])

    # Append the new column element to "columns" element.
    columns_elem.append(new_column)

# Remove original empty column template.
columns_elem.remove(column_template)

# Write tree to file.
# Note: I could've just done tree.write("output.xml"), but I used
#       XMLParser(), tostring(), and fromstring() to get the indents
#       all the same.
parser = et.XMLParser(remove_blank_text=True)
et.ElementTree(et.fromstring(et.tostring(tree), 
                             parser=parser)).write("output.xml", 
                                                   pretty_print=True)

Вывод XML ("output.xml")

<custom-data>
  <schema>SCHEMA</schema>
  <columns>
    <column>
      <name>COLUMN1</name>
      <datatype>NUMBER</datatype>
      <length>38</length>
      <precision>0</precision>
      <not-null>N</not-null>
    </column>
    <column>
      <name>COLUMN2</name>
      <datatype>NUMBER</datatype>
      <length>38</length>
      <precision>0</precision>
      <not-null>N</not-null>
    </column>
    <column>
      <name>COLUMN3</name>
      <datatype>NUMBER</datatype>
      <length>38</length>
      <precision>0</precision>
      <not-null>N</not-null>
    </column>
  </columns>
</custom-data>
...