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
From what I gather from the question, the task involves making the following changes:
CssParameter
element to SvgParameter
, preserving the attributesThere 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.