1

I currently try to convert an SLD, a type of XML, from one version to another by writing a python script. I’ve been bang my head against it for nearly two weeks and I’m making little progress. I am really new to python and would appreciate any suggestions!
Basically, I need to turn this….

<?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>Custom_Landuse</se:Name>
<se:Description>
  <se:Title>Custom_Landuse</se:Title>
  <se:Abstract>A Land Use style</se:Abstract>
</se:Description>
<UserStyle>
  <se:Name>County Electoral Division</se:Name>
  <se:FeatureTypeStyle>
    <se:Rule>
      <se:Name>Woods</se:Name>
      <ogc:Filter>
        <ogc:PropertyIsEqualTo>
          <ogc:PropertyName>AREA_CODE</ogc:PropertyName>
          <ogc:Literal>CED</ogc:Literal>
        </ogc:PropertyIsEqualTo>
      </ogc:Filter>
      <se:MinScaleDenominator>65000</se:MinScaleDenominator>
      <se:MaxScaleDenominator>150000</se:MaxScaleDenominator>
      <se:PolygonSymbolizer>
        <se:Name>Woods</se:Name>
        <se:Fill>
          <se:SvgParameter name="fill">#228B22</se:SvgParameter>
          <se:SvgParameter name="fill-opacity">0</se:SvgParameter>
        </se:Fill>
        <se:Stroke>
          <se:SvgParameter name="stroke">#ADFF2F</se:SvgParameter>
          <se:SvgParameter name="stroke-opacity"></se:SvgParameter>
          <se:SvgParameter name="stroke-width">1.4</se:SvgParameter>
          <se:SvgParameter name="stroke-linejoin">round</se:SvgParameter>
          <se:SvgParameter name="stroke-linecap">round</se:SvgParameter>
        </se:Stroke>
      </se:PolygonSymbolizer>
      <se:TextSymbolizer>
        <se:Name>Woods</se:Name>
         <se:Label>
           <ogc:PropertyName>NAME</ogc:PropertyName>
         </se:Label>
         <se:Font>
           <se:SvgParameter name="font-family">Sans-Serif</se:SvgParameter>
           <se:SvgParameter name="font-style">normal</se:SvgParameter>
           <se:SvgParameter name="font-size">20</se:SvgParameter>
           <se:SvgParameter name="font-weight">bold</se:SvgParameter>
         </se:Font>
         <se:Fill>
           <se:SvgParameter name="fill">#ADFF2F</se:SvgParameter>
         </se:Fill>
      </se:TextSymbolizer>
    </se:Rule>
    <se:Rule>
      <se:Name>Grass</se:Name>
      <ogc:Filter>
        <ogc:PropertyIsEqualTo>
          <ogc:PropertyName>AREA_CODE</ogc:PropertyName>
          <ogc:Literal>CED</ogc:Literal>
        </ogc:PropertyIsEqualTo>
      </ogc:Filter>
      <se:MinScaleDenominator>65000</se:MinScaleDenominator>
      <se:MaxScaleDenominator>150000</se:MaxScaleDenominator>
      <se:PolygonSymbolizer>
        <se:Name>Grass</se:Name>
        <se:Fill>
          <se:SvgParameter name="fill">#90EE90</se:SvgParameter>
          <se:SvgParameter name="fill-opacity">0</se:SvgParameter>
        </se:Fill>
        <se:Stroke>
          <se:SvgParameter name="stroke">#6B8E23</se:SvgParameter>
          <se:SvgParameter name="stroke-opacity"></se:SvgParameter>
          <se:SvgParameter name="stroke-width">1.4</se:SvgParameter>
          <se:SvgParameter name="stroke-linejoin">round</se:SvgParameter>
          <se:SvgParameter name="stroke-linecap">round</se:SvgParameter>
        </se:Stroke>
      </se:PolygonSymbolizer>
      <se:TextSymbolizer>
        <se:Name>Grass</se:Name>
         <se:Label>
           <ogc:PropertyName>NAME</ogc:PropertyName>
         </se:Label>
         <se:Font>
           <se:SvgParameter name="font-family">Sans-Serif</se:SvgParameter>
           <se:SvgParameter name="font-style">normal</se:SvgParameter>
           <se:SvgParameter name="font-size">20</se:SvgParameter>
           <se:SvgParameter name="font-weight">bold</se:SvgParameter>
         </se:Font>
         <se:Fill>
           <se:SvgParameter name="fill">#6B8E23</se:SvgParameter>
         </se:Fill>
      </se:TextSymbolizer>
    </se:Rule>
  </se:FeatureTypeStyle>
</UserStyle>

into this

<?xml version="1.0" encoding="UTF-8"?> 
<StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" 
xmlns:ogc="http://www.opengis.net/ogc" 
xmlns:gml="http://www.opengis.net/gml"  
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.0.0/StyledLayerDescriptor.xsd">
<NamedLayer>
<Name>Custom_Landuse</Name>
<UserStyle>
  <Name>Land Use</Name>
  <FeatureTypeStyle>
    <Rule>
      <Name>Woods</Name>
      <Title>Woods - 1:65,000 to 1:150,000</Title>
      <Abstract>A Land Use style</Abstract>
      <ogc:Filter>
        <ogc:PropertyIsEqualTo>
          <ogc:PropertyName>AREA_CODE</ogc:PropertyName>
          <ogc:Literal>CED</ogc:Literal>
        </ogc:PropertyIsEqualTo>
      </ogc:Filter>
      <MinScaleDenominator>65000</MinScaleDenominator>
      <MaxScaleDenominator>150000</MaxScaleDenominator>
      <PolygonSymbolizer>
        <Fill>
          <CssParameter name="fill">#228B22</CssParameter>
          <CssParameter name="fill-opacity">1</CssParameter>
        </Fill>
        <Stroke>
          <CssParameter name="stroke">#ADFF2F</CssParameter>
          <CssParameter name="stroke-opacity"></CssParameter>
          <CssParameter name="stroke-width">1.4</CssParameter>
          <CssParameter name="stroke-linejoin">round</CssParameter>
          <CssParameter name="stroke-linecap">round</CssParameter>
        </Stroke>
      </PolygonSymbolizer>
      <TextSymbolizer>
         <Label>
           <ogc:PropertyName>NAME</ogc:PropertyName>
         </Label>
         <Font>
           <CssParameter name="font-family">Sans-Serif</CssParameter>
           <CssParameter name="font-style">normal</CssParameter>
           <CssParameter name="font-size">20</CssParameter>
           <CssParameter name="font-weight">bold</CssParameter>
         </Font>
         <Fill>
           <CssParameter name="fill">#ADFF2F</CssParameter>
         </Fill>
      </TextSymbolizer>
    </Rule>
    <Rule>
      <Name>Grass</Name>
      <Title>Grass - 1:65,000 to 1:150,000</Title>
      <Abstract>A Land Use style</Abstract>
      <ogc:Filter>
        <ogc:PropertyIsEqualTo>
          <ogc:PropertyName>AREA_CODE</ogc:PropertyName>
          <ogc:Literal>CED</ogc:Literal>
        </ogc:PropertyIsEqualTo>
      </ogc:Filter>
      <MinScaleDenominator>65000</MinScaleDenominator>
      <MaxScaleDenominator>150000</MaxScaleDenominator>
      <PolygonSymbolizer>
        <Fill>
          <CssParameter name="fill">#90EE90</CssParameter>
          <CssParameter name="fill-opacity">1</CssParameter>
        </Fill>
        <Stroke>
          <CssParameter name="stroke">#6B8E23</CssParameter>
          <CssParameter name="stroke-opacity"></CssParameter>
          <CssParameter name="stroke-width">1.4</CssParameter>
          <CssParameter name="stroke-linejoin">round</CssParameter>
          <CssParameter name="stroke-linecap">round</CssParameter>
        </Stroke>
      </PolygonSymbolizer>
      <TextSymbolizer>
         <Label>
           <ogc:PropertyName>NAME</ogc:PropertyName>
         </Label>
         <Font>
           <CssParameter name="font-family">Sans-Serif</CssParameter>
           <CssParameter name="font-style">normal</CssParameter>
           <CssParameter name="font-size">20</CssParameter>
           <CssParameter name="font-weight">bold</CssParameter>
         </Font>
         <Fill>
           <CssParameter name="fill">#6B8E23</CssParameter>
         </Fill>
      </TextSymbolizer>
    </Rule>
  </FeatureTypeStyle>
</UserStyle>

I currently have this code

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

# Run once per file
StyledLayerDescriptor = ET.Element("StyledLayerDescriptor",version="1.0", )
NamedLayer = ET.SubElement(StyledLayerDescriptor, "NamedLayer")
Name = ET.SubElement(NamedLayer, "Name")
Line_county_electoral_division_region"
UserStyle = ET.SubElement(NamedLayer, "UserStyle")
Name = ET.SubElement(UserStyle, "Name")"
FeatureTypeStyle = ET.SubElement(UserStyle, "FeatureTypeStyle")

# Run once per rule in file
Rule = ET.SubElement(FeatureTypeStyle, "Rule")
Name = ET.SubElement(Rule, "Name")
Line_county_electoral_division_region"
Title = ET.SubElement(Rule, "Title")
Abstract = ET.SubElement(Rule, "Abstract")
Filter = ET.SubElement(Rule, "Filter")
PropertyIsEqualTo = ET.SubElement(Filter, "PropertyIsEqualTo")
PropertyName = ET.SubElement(PropertyIsEqualTo, "PropertyName")
Literal = ET.SubElement(PropertyIsEqualTo, "Literal")
MinScaleDenominator = ET.SubElement(Rule, "MinScaleDenominator")
MaxScaleDenominator = ET.SubElement(Rule, "MaxScaleDenominator")
PolygonSymbolizer = ET.SubElement(Rule, "PolygonSymbolizer")
Fill = ET.SubElement(PolygonSymbolizer, "Fill")
Stroke = ET.SubElement(PolygonSymbolizer, "Stroke")
TextSymbolizer = ET.SubElement(Rule, "PolygonSymbolizer")
Label = ET.SubElement(TextSymbolizer, "Label")
PropertyName = ET.SubElement(Label, "PropertyName")
Front = ET.SubElement(TextSymbolizer, "Front")
Fill = ET.SubElement(TextSymbolizer, "Fill")

ET.SubElement(Fill, "CssParameter", name="fill")
ET.SubElement(Fill, "CssParameter", name="fill-opacity")
ET.SubElement(Stroke, "CssParameter", name="stroke")
ET.SubElement(Stroke, "CssParameter", name="stroke-opacity")
ET.SubElement(Stroke, "CssParameter", name="stroke-width")
ET.SubElement(Stroke, "CssParameter", name="stroke-linejoin")"
ET.SubElement(Stroke, "CssParameter", name="stroke-linecap")
ET.SubElement(Front, "CssParameter", name="font-family")
ET.SubElement(Front, "CssParameter", name="font-style")
ET.SubElement(Front, "CssParameter", name="font-size")
ET.SubElement(Front, "CssParameter", name="font-weight")
ET.SubElement(Fill, "CssParameter", name="fill")

Output = ET.ElementTree(StyledLayerDescriptor)
Output.write("SLD_Test.xml", xml_declaration=True, encoding='UTF-8')

parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse("SLD_Test.xml", parser)

tree.write("SLD_Test.xml", pretty_print=True)

This code works to generate a print xml tree

I am not just looking to change the namespaces but to reformat the structure of the xml, for example version 1.0 doesn't recognise but some of the data is needed under .

I'm not looking for the full solutions as I'm trying to get to grips with both python and XML. Any help would be great! Even a links to useful tutorials

  • Possible duplicate of [Remove namespace and prefix from xml in python using lxml](https://stackoverflow.com/questions/18159221/remove-namespace-and-prefix-from-xml-in-python-using-lxml) – stovfl Aug 24 '17 at 11:27
  • It looks similar, but I need to re-structure (if that the correct term) the xml and copy information from one to new file. But it certainly looks useful thank you! – Bradley_Burrell Aug 24 '17 at 11:32

1 Answers1

2

From what I gather from the question, the task involves making the following changes:

There are some missing words in your question, so I don't understand all the rules exactly. In any case, you'll want to review the differences between SLD 1.1 and 1.0 carefully to assure you have rules in place for the differences. Don't forget, you can cheat a bit by not writing rules for differences that are not used by your input files.

Here is a short Python program (sld11-10.py) that reads each XML file from an input directory, then creates an output directory and writes an identical directory structure with the same file names in the same directories, but with the content transformed as described.

#!/usr/bin/env python3
from lxml import etree
import os
import sys

sld10 = etree.XMLSchema(
    etree.parse("http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"))
sld11 = etree.XMLSchema(
    etree.parse("http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd"))
transform = etree.XSLT(etree.parse("sld11-10.xsl"))

def walk(sour_dir: str, dest_dir: str) -> None:
    os.mkdir(dest_dir)
    for item in os.listdir(sour_dir):
        sour_path = os.path.join(sour_dir, item)
        dest_path = os.path.join(dest_dir, item)
        if os.path.isdir(sour_path):
            walk(sour_path, dest_path)
        else:
            sour_doc = etree.parse(sour_path)
            if not sld11.validate(sour_doc):
                print(sour_path, sld11.error_log.last_error)
            dest_doc = transform(sour_doc)
            if not sld10.validate(dest_doc):
                print(dest_path, sld10.error_log.last_error)
            dest_doc.write(dest_path,
                pretty_print=True, xml_declaration=True, encoding="UTF-8")
    return None

if __name__ == "__main__":
    walk(sys.argv[1], sys.argv[2])

Here is the referenced XSLT file sld11-10.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:se="http://www.opengis.net/se"
  xmlns:ogc="http://www.opengis.net/ogc" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:sld="http://www.opengis.net/sld"
  exclude-result-prefixes="sld se">

  <xsl:output encoding="UTF-8"/>

  <!--
    Chanage the version and the schemaLocation values
  -->

  <xsl:template match="/sld:StyledLayerDescriptor">
    <StyledLayerDescriptor xmlns="http://www.opengis.net/sld"
       xmlns:ogc="http://www.opengis.net/ogc"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       xmlns:gml="http://www.opengis.net/gml">
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="version">1.0.0</xsl:attribute>
      <xsl:attribute name="xsi:schemaLocation"
        namespace="http://www.w3.org/2001/XMLSchema-instance">
        <xsl:text>http://www.opengis.net/sld </xsl:text>
        <xsl:text>http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd</xsl:text>
      </xsl:attribute>
      <xsl:apply-templates/>
    </StyledLayerDescriptor>
  </xsl:template>

  <!--
    Map SvgParameter elements in the http://www.opengis.net/se namespace
    to CssParameter in the http://www.opengis.net/sld namespace
  -->

  <xsl:template match="se:SvgParameter">
    <xsl:element name="CssParameter" namespace="http://www.opengis.net/sld">
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <!--
    Map remaining http://www.opengis.net/se elements and attributes
    to the http://www.opengis.net/sld namespace
  -->

  <xsl:template match="se:*">
    <xsl:element name="{local-name()}" namespace="http://www.opengis.net/sld">
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <!--
   Preserve all other elements and attributes
  -->

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

  <!--
   Preserve processing instructuions and comments
  -->

  <xsl:template match="processing-instruction()|comment()">
    <xsl:copy>.</xsl:copy>
  </xsl:template>

</xsl:stylesheet>

The if name == "main": walk(sys.argv[1], sys.argv[2] part means that when you run the program from the command line, it will use the first two command-line parameters as input to the walk routine. For example, say you have sld11-10.py, sld11-10.xsl, and input_directory on your Desktop, and then say input_directory contains files such as this

input_directory
  file1.xml
  file2.xml
  subdirectory
    file3.xml

then, assuming you do not have a directory called output_directory, you can run the file like this

sld11-10.py input_directory output_directory

The program will create output_direcory with the following structure:

output_directory
  file1.xml
  file2.xml
  subdirectory
    file3.xml

The difference between input_directory and output_directory is that the XML files in output_directory will have gone through the XSLT transformation. Also, note that during the transformation, information about the validity of the XML files, both SLD 1.0 and SLD 1.1 will be written to the screen during the process.

rcrews
  • 358
  • 3
  • 14
  • Hello! Thank You, I think this might be the solution. I'm just trying to fight out how it work. I think I've got it most worked out bar this section. if __name__ == "__main__": walk(sys.argv[1], sys.argv[2]) – Bradley_Burrell Aug 30 '17 at 13:19
  • Updated the answer to add an explanation of the `if __name__ == "__main__"` part. – rcrews Oct 01 '17 at 15:01