1

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 2

1

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>
Sign up to request clarification or add additional context in comments.

6 Comments

Interesting, so you dont have to iterate the values?
@InsaneRuffles You don't; the for loop does it for you...
Oh, so for p in root.findall('.//Property[@name]/Property'): isnt looping through each LodDistance node, but for the values inside one LodDistance node, correct? How would I got about doing that for each LodDistance node in an xml file? There are multiple per file.
@InsaneRuffles Generally speaking, it should work with multiple LodDistance nodes, but it's really hard to tell how it will work with your actual xml. Your sample xml contains 4 nested tiers of nodes, all of which are named Property so, depending on the complete xml structure, this may exclude some relevant nodes. So the only thing I can tell you for sure is that it works with your sample xml and probably with all of it. You should try it on your actual xml and see what happens.
This doesnt seem to work on the actual file, but I think I know why: "Element.findall() finds only elements with a tag which are direct children of the current element." -> docs.python.org/3/library/xml.etree.elementtree.html since the real XML file has this node nested pretty deep, it is not a direct child.
|
1

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>

3 Comments

I dont have any experience in XSLT, but this is pretty cool! One thing though, would I be able to implement this into a python program? I need to do this to hundreds of xml files.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.