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 Answers2

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>
Jack Fleeting
  • 24,385
  • 6
  • 23
  • 45
  • Interesting, so you dont have to iterate the values? – InsaneRuffles Jun 12 '21 at 23:06
  • @InsaneRuffles You don't; the `for` loop does it for you... – Jack Fleeting Jun 12 '21 at 23:12
  • 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 Jun 12 '21 at 23:21
  • @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. – Jack Fleeting Jun 12 '21 at 23:28
  • 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." -> https://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. – InsaneRuffles Jun 13 '21 at 16:11
  • @InsaneRuffles Unfortunately, many times it comes down to the actual xml file structure, not the sample in the question. So unless you can post the whole file, you have to make sure that the sample in the question is as representative of the actual file as possible. – Jack Fleeting Jun 13 '21 at 17:09
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>
Yitzhak Khabinsky
  • 18,471
  • 2
  • 15
  • 21
  • 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. – InsaneRuffles Jun 13 '21 at 04:01
  • Check it out here: https://stackoverflow.com/questions/16698935/how-to-transform-an-xml-file-using-xslt-in-python – Yitzhak Khabinsky Jun 13 '21 at 04:22
  • Or here https://stackoverflow.com/questions/32066401/transforming-multiple-xml-files-using-xslt-in-python – Yitzhak Khabinsky Jun 13 '21 at 04:29