Задайте для ET.SubElement.text значение dict.value, если dict.key равен другому узлу XML в том же родительском элементе.

Итак, я создаю новый подэлемент с ElementTree, где текст нового узла должен быть значением словаря, ЕСЛИ ключ словаря соответствующего значения равен тексту другого узла XML в том же родительском узле.

Пример XML:

<ns0:scaleType xmlns:ns0="http://someURL.com/">
  <scales>
    <scale>
        <names>
            <name id="0">abc</name>
            <name id="1" />
        </names>
        <alternativeExportValues>
        </alternativeExportValues>
    </scale>
    <scale>
        <names>
            <name id="0">def</name>
            <name id="1" />
        </names>
        <alternativeExportValues>
        </alternativeExportValues>
    </scale>
 </scales>
</ns0:scaleType>

Пример CSV:

name;value
abc;10012
def;20025

Код Python сейчас:

import xml.etree.ElementTree as ET

import csv

csvData = []

with open('myCSV.csv', 'r', encoding="utf8") as f:
    reader = csv.reader(f, delimiter=";")
    for row in reader:
        csvData.append({'name': row[0], 'value': row[1]})

tree = ET.parse('myXml.xml')
root = tree.getroot()

def my_Function():
    for p in csvData:
        for name in root.findall(".//name[@id='0']"):
            text = name.text
            if p['name'] == text:
                value = p['value']
                return value
my_Function()


for elem in root.iter('alternativeExportValues'):
    newNode = ET.SubElement(elem, 'alternativeExportValue')
    newNode.text = 

tree.write("myNewXML.xml", encoding="utf-8")

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

<ns0:scaleType xmlns:ns0="http://someURL.com/">
  <scales>
    <scale>
        <names>
            <name id="0">abc</name>
            <name id="1" />
        </names>
        <alternativeExportValues>
           <alternativeExportValue>10012</alternativeExportValue>
        </alternativeExportValues>
    </scale>
    <scale>
        <names>
            <name id="0">def</name>
            <name id="1" />
        </names>
        <alternativeExportValues>
           <alternativeExportValue>20025</alternativeExportValue>
        </alternativeExportValues>
    </scale>
 </scales>
</ns0:scaleType>


Я попытался поместить цикл for, создающий узел alternativeExportValue, в my_Function, но в итоге получил то же значение в newNode.text или застрял в бесконечном цикле.

Как видно из ожидаемого результата, я хочу, чтобы dict.value был текстом для вновь созданного узла, если он соответствует
<name id="0"> innerText внутри того же родителя <scale>.


person Alecbalec    schedule 11.12.2019    source источник


Ответы (1)


Я не совсем уверен, что должен делать my_Function, но рассмотрим следующую логику:

  • Чтение/обработка данных CSV. (Вы уже делаете это, но рассмотрите вместо этого DictReader. , Это сопоставит значения с dict, используя ключи из первой строки.)
  • Обработать каждый элемент scale.
  • Создайте новый элемент alternativeExportValue со значением "value".
  • Проверьте, соответствует ли элемент name со значением атрибута id "0" текущей записи "name".
  • Если это так, добавьте новый элемент alternativeExportValue.

Пример...

import xml.etree.ElementTree as ET
import csv

with open('myCSV.csv', 'r', encoding="utf8") as csvfile:
    tree = ET.parse('myXml.xml')

    for row in csv.DictReader(csvfile, delimiter=";"):
        name = row.get("name")
        new_aev_elem = ET.Element("alternativeExportValue")
        new_aev_elem.text = row.get("value")
        for scale in tree.findall(".//scale"):
            name0 = scale.find("names/name[@id='0']")
            if name0.text == name:
                aevs_elem = scale.find("alternativeExportValues")
                aevs_elem.append(new_aev_elem)
                break

    tree.write("myNewXML.xml", encoding="utf-8")

Это работает, но не очень эффективно, потому что вам нужно обрабатывать каждый элемент scale, который предшествует фактическому элементу scale, который вы хотите изменить.

Хуже того, если вы удалите break, он будет обрабатывать каждый элемент scale в XML (для каждой строки CSV!).

Если вы можете переключиться на lxml, вы можете использовать немного более сложный XPath*, который будет обрабатывать только scale элемент, который необходимо изменить...

from lxml import etree
import csv

with open('myCSV.csv', 'r', encoding="utf8") as csvfile:
    tree = etree.parse('myXml.xml')

    uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    lc = "abcdefghijklmnopqrstuvwxyz"

    for row in csv.DictReader(csvfile, delimiter=";"):
        name = row.get("name").lower()
        new_aev_elem = etree.Element("alternativeExportValue")
        new_aev_elem.text = row.get("value")
        aevs_elem = tree.xpath(f".//scale[translate(names/name[@id='0'],'{uc}','{lc}')='{name}']/alternativeExportValues")[0]
        aevs_elem.append(new_aev_elem)

    tree.write("myNewXML.xml", encoding="utf-8")

* поддержка XPath в ElementTree ограничена. .

person Daniel Haley    schedule 12.12.2019
comment
Спасибо @ Дэниел Хейли. Моя идея с my_Function заключалась в том, чтобы найти значение соответствующего ключа в .//name[@id="0"]. Я попытался переключиться на lxml, но aevs_elem = tree.xpath(f".//scale[names/name[@id='0']='{name}']/alternativeExportValues")[0] возвращает IndexError: list index out of range. Это означает, что xPath не существует. - person Alecbalec; 12.12.2019
comment
@Alecbalec - Вы также перешли на DictReader? Если нет, вы, вероятно, обрабатываете первую строку CSV, а XPath не работает из-за name[@id='0']='name'. Если вы не хотите переключаться на DictReader, вы можете либо пропустить первую строку CSV, либо использовать try/except. Если вы перешли на DictReader, в вашем CSV или XML должно быть что-то другое, потому что я проверил то, что у вас есть в вашем вопросе, и не получил никаких ошибок. - person Daniel Haley; 12.12.2019
comment
Прежде всего, большое спасибо! Первой проблемой была кодировка при чтении csv файла. В цикле for я запустил print(row), чтобы увидеть вывод, и он вернул '\ufeffname': вместо name:. Изменил кодировку на "utf-8-sig" и все стало выглядеть лучше. Я раскомментировал код и добавил print(get.('value'), чтобы увидеть, в какой строке в файле .csv код действительно ломается. После быстрого просмотра стало очевидно, что xPath чувствителен к регистру, и поэтому он возвращает IndexError: list index out of range - person Alecbalec; 12.12.2019
comment
@Alecbalec - Ах, да, XPath определенно чувствителен к регистру. Принуждение значения к верхнему или нижнему регистру - это своего рода боль в XPath 1.0 (это то, что поддерживает lxml), но я обновлю свой ответ lxml, чтобы он не был чувствителен к регистру. - person Daniel Haley; 12.12.2019
comment
Да, к счастью, было всего несколько строк, где один символ был строчным в исходном XML-файле и прописным в csv. Еще раз спасибо. - person Alecbalec; 12.12.2019