Python XML Changing Values of Specific Nodes

Viewed 67

I need to iterate through complex XML files (multiple thousands of lines) and change the children values of specific nodes:

        <Property name="QualityVariants">
          <Property value="GcObjectSpawnDataVariant.xml">
            <Property name="ID" value="STANDARD" />
            <Property name="Coverage" value="0.23" />
            <Property name="FlatDensity" value="0.01" />
            <Property name="SlopeDensity" value="0.01" />
            <Property name="SlopeMultiplier" value="1" />
            <Property name="MaxRegionRadius" value="3" />
            <Property name="MaxImposterRadius" value="99" />
            <Property name="FadeOutStartDistance" value="9999" />
            <Property name="FadeOutEndDistance" value="9999" />
            <Property name="FadeOutOffsetDistance" value="0" />
            <Property name="LodDistances">
              <Property value="0" />
              <Property value="50" />
              <Property value="100" />
              <Property value="300" />
              <Property value="1000" />
            </Property>
          </Property>
        </Property>

In this example, I need to multiply each value of "LodDistances" by 3. While "LodDistances" doesn't change in name or its values, its parental hierarchy does from file to file (and there are multiple LodDistance nodes). Im using xml.etree.ElementTree to change these xml files, but I have not found an example so far that does this. The following is my attempt at finding and replacing the node values, but Im lacking on knowledge here:

for child in root.findAll('LodDistances'):
   for value in root.iter(child):
      value.set('value', int(value.get('value')) * 3)

This isnt working however, but I feel like I am close. Any help is appreciated, thank you!

2 Answers

Try something along these lines:

for p in root.findall('.//Property[@name]/Property'):
    new_att = str(int(p.attrib['value'])*3)    
    p.set('value',new_att)
print(ET.tostring(root).decode())

Relevant portion of the output:

            <Property name="Coverage" value="0.23" />            
            <Property name="LodDistances">
              <Property value="0" />
              <Property value="150" />
              <Property value="300" />
              <Property value="900" />
              <Property value="3000" />
            </Property>

It is much better to use XSLT for such tasks.

XSLT has so called Identity Transform pattern.

It will find all the <Property name="LodDistances"> elements in the input XML regardless of their location, and apply multiplication to the value attribute value.

XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Property[@name='LodDistances']/Property/@value">
        <xsl:copy>
            <xsl:value-of select=". * 3"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Output XML

<Property name="QualityVariants">
  <Property value="GcObjectSpawnDataVariant.xml">
    <Property name="ID" value="STANDARD"/>
    <Property name="Coverage" value="0.23"/>
    <Property name="FlatDensity" value="0.01"/>
    <Property name="SlopeDensity" value="0.01"/>
    <Property name="SlopeMultiplier" value="1"/>
    <Property name="MaxRegionRadius" value="3"/>
    <Property name="MaxImposterRadius" value="99"/>
    <Property name="FadeOutStartDistance" value="9999"/>
    <Property name="FadeOutEndDistance" value="9999"/>
    <Property name="FadeOutOffsetDistance" value="0"/>
    <Property name="LodDistances">
      <Property value="0"/>
      <Property value="150"/>
      <Property value="300"/>
      <Property value="900"/>
      <Property value="3000"/>
    </Property>
  </Property>
</Property>
Related