pythonxmllxmlsld

Modifying an XML File - Dividing values


I am looking to modify certainly values with in a xml file by dividing them.

I need to divide the values for se:SvgParameter[name="stroke-width"] and se:Size by 3.6. I found this tutorial on Python.org, Modifying an XML File, but it doesn’t seem to work and I couldn’t find something similar on here. Any help or point would be greatly appreciated!

Here's some example XML, but I want to be able to output the script in a folder with multiple files and have it convert all of them.

XML:

<?xml version="1.0" encoding="ISO-8859-1"?>
<StyledLayerDescriptor version="1.1.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" xmlns:se="http://www.opengis.net/se">
  <NamedLayer>
    <se:Name>QGIS_SLD_ScaleProblem</se:Name>
    <se:Description>
      <se:Title>QGIS_SLD_ScaleProblem</se:Title>
      <se:Abstract>This SLD is in Pixels, but QGIS will read it as mm</se:Abstract>
    </se:Description>
    <UserStyle>
      <se:Name>QGIS_SLD_ScaleProblem</se:Name>
      <se:FeatureTypeStyle>
        <se:Rule>
          <se:Name>QGIS_SLD_ScaleProblem_Line</se:Name>
          <se:LineSymbolizer>
            <se:Name>Line</se:Name>
            <se:Stroke>
              <se:SvgParameter name="stroke">#FF0000</se:SvgParameter>
              <se:SvgParameter name="stroke-width">36</se:SvgParameter>
            </se:Stroke>
          </se:LineSymbolizer>
        </se:Rule>
        <se:Rule>
          <se:Name>QGIS_SLD_ScaleProblem_Point</se:Name>
          <se:PointSymbolizer>
            <se:Name>Point</se:Name>
            <se:Graphic>
             <se:Mark>
              <se:WellKnownName>circle</se:WellKnownName>
              <se:Fill>
               <se:SvgParameter name="fill">#FF0000</se:SvgParameter>
              </se:Fill>
             </se:Mark>
             <se:Size>36</se:Size>
            </se:Graphic>
          </se:PointSymbolizer>
        </se:Rule>
      </se:FeatureTypeStyle>
    </UserStyle>
  </NamedLayer>
</StyledLayerDescriptor>

Code:

import xml.etree.ElementTree as ET
from lxml import etree

tree = ET.parse('QGIS-SLD-ScaleProblem_Line.sld')
root = tree.getroot()

# To check that the script is reading the correct file
print("SLD file", root[0][0].text, "loaded.")

# Registers namespaces to prevent them being filled with defaults
ET.register_namespace('', "http://www.opengis.net/sld")
ET.register_namespace('ogc', "http://www.opengis.net/ogc")
ET.register_namespace('xsi', "http://www.w3.org/2001/XMLSchema-instance")
ET.register_namespace('xlink', "http://www.w3.org/1999/xlink")
ET.register_namespace('schemaLocation', "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd")
ET.register_namespace('se', "http://www.opengis.net/se")

# This section "should" search the XML for se:SvgParameter name="stroke-width" and se:Size and divide the value by 3.6
for SvgParameter in root.iter('SvgParameter'):
    new_SvgParameter = int(SvgParameter.text) / 3.6
    SvgParameter.text = str(new_SvgParameter)
for Size in root.iter('Size'):
    new_Size= int(Size.text) / 3.6
    Size.text = str(new_Size)

tree.write("GIS-SLD-ScaleFixed.sld", xml_declaration=True, encoding='ISO-8859-1')

Answers I’ve looked at:


Solution

  • Consider XSLT the special-purpose language designed to transform XML files. And Python's lxml module can run XSLT 1.0 scripts. Plus XSLT can be run outside Python, hence its portability.

    Simply call the identity transform to copy whole document as is and then run the division on selected nodes. No for loops or if logic needed with this approach.

    XSLT (save as .xsl file, a special well-formed XML file)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                                  xmlns:se="http://www.opengis.net/se">                               
      <xsl:output indent="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="se:SvgParameter[@name='stroke-width']|se:Size">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:value-of select="format-number(text(), '#') div 3.6"/>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Python (iterate below process in loop for multiple files)

    import lxml.etree as et
    
    # LOAD XML AND XSL
    doc = et.parse('Input.xml')
    xsl = et.parse('XSLTScript.xsl')
    
    # TRANSFORM
    transform = et.XSLT(xsl)
    result = transform(doc)
    
    # OUTPUT TO SCREEN    
    print(result)
    
    # OUTPUT TO FILE
    with open('Output.xml', 'wb') as f:
        f.write(result)
    

    Output

    <?xml version="1.0"?>
    <StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:se="http://www.opengis.net/se" version="1.1.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd">
      <NamedLayer>
        <se:Name>QGIS_SLD_ScaleProblem</se:Name>
        <se:Description>
          <se:Title>QGIS_SLD_ScaleProblem</se:Title>
          <se:Abstract>This SLD is in Pixels, but QGIS will read it as mm</se:Abstract>
        </se:Description>
        <UserStyle>
          <se:Name>QGIS_SLD_ScaleProblem</se:Name>
          <se:FeatureTypeStyle>
            <se:Rule>
              <se:Name>QGIS_SLD_ScaleProblem_Line</se:Name>
              <se:LineSymbolizer>
                <se:Name>Line</se:Name>
                <se:Stroke>
                  <se:SvgParameter name="stroke">#FF0000</se:SvgParameter>
                  <se:SvgParameter name="stroke-width">10</se:SvgParameter>
                </se:Stroke>
              </se:LineSymbolizer>
            </se:Rule>
            <se:Rule>
              <se:Name>QGIS_SLD_ScaleProblem_Point</se:Name>
              <se:PointSymbolizer>
                <se:Name>Point</se:Name>
                <se:Graphic>
                  <se:Mark>
                    <se:WellKnownName>circle</se:WellKnownName>
                    <se:Fill>
                      <se:SvgParameter name="fill">#FF0000</se:SvgParameter>
                    </se:Fill>
                  </se:Mark>
                  <se:Size>10</se:Size>
                </se:Graphic>
              </se:PointSymbolizer>
            </se:Rule>
          </se:FeatureTypeStyle>
        </UserStyle>
      </NamedLayer>
    </StyledLayerDescriptor>