Alecbalec
Alecbalec

Reputation: 119

Set ET.SubElement.text to dict.value if dict.key equals other XML node within same parent

So, I'm creating a new subelement with ElementTree where the text of the new node should be a dict value IF dict key of the corresponding value equals the text of another XML node within the same parent Node.

XML example:

<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 example:

name;value
abc;10012
def;20025

Python code now:

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

Expected outcome:

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


I tried to put the for loop that creates the alternativeExportValue node in my_Function, but ended up getting the same value in newNode.text or getting stuck in an endless loop.

As you can see in the expected outcome, I want the dict.value as text for the newly created Node if it matches the
<name id="0"> innerText within the same parent <scale>.

Upvotes: 1

Views: 255

Answers (1)

Daniel Haley
Daniel Haley

Reputation: 52878

I'm not exactly sure what my_Function is supposed to be doing, but consider the following logic:

  • Read/process the CSV data. (You're already doing this, but consider DictReader instead. This will map the values to a dict using the keys from the first row.)
  • Process each scale element.
  • Create a new alternativeExportValue element with the "value" value.
  • Check to see if the name element with the id attribute value "0" matches the current "name" entry.
  • If it does, append the new alternativeExportValue element.

Example...

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

This works but isn't very efficient because you have to process every scale element that precedes the actual scale element you want modify.

Even worse, if you remove the break it would process every scale element in the XML (for every row of the CSV!).

If you can switch to lxml, you can use a slightly more complex XPath* that will only process the scale element that needs to be modified...

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

*The XPath support in ElementTree is limited.

Upvotes: 1

Related Questions