rxmlxml2sld

Format sld file (xml) with break lines and indentation in `xml2`


I'm programatically writing lots of SDL files for a geoserver, which under the hood are XML files. I'm using {xml2} and while I get the right format in a small reprex (each tag in one line and with indentation), this does not work anymore in my full example.

I would assume that geoserver does not care about line breaks, but the admins of the served insist that the SLD file does not work if not properly indented.

How can I force the output file to place one tag in a line with proper indentation?

Small reprex that formats properly:

library(xml2)

x <- read_xml("<parent></parent>")
xml_add_child(x, "child", type = "child")
child <- xml_find_first(x, ".//child")

for (i in 1:5) {
  xml_add_child(child, "small_child",
                label = i,
                .where = i)
}

x
#> {xml_document}
#> <parent>
#> [1] <child type="child">\n  <small_child label="1"/>\n  <small_child label="2 ...

write_xml(x, "small_reprex.sld")

Created on 2024-04-17 with reprex v2.1.0

Output file with proper indentation:

<?xml version="1.0" encoding="UTF-8"?>
<parent>
  <child type="child">
    <small_child label="1"/>
    <small_child label="2"/>
    <small_child label="3"/>
    <small_child label="4"/>
    <small_child label="5"/>
  </child>
</parent>

Large reprex that does not indent tags:

library(xml2)

xml_file <- read_xml('<?xml version="1.0" encoding="UTF-8"?>
<sld:StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" version="1.0.0">
  <sld:NamedLayer>
    <sld:Name>SLD template for qualitative rasters</sld:Name>
    <sld:UserStyle>
      <sld:Name>SLD template for qualitative rasters</sld:Name>
      <sld:FeatureTypeStyle>
        <sld:Name>name</sld:Name>      
        <sld:Rule>
                <sld:RasterSymbolizer>
                </sld:RasterSymbolizer>
        </sld:Rule>
      </sld:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>')

namedlayer <- xml_find_first(xml_file, ".//sld:NamedLayer")
namelayer <- xml_find_first(namedlayer, ".//sld:Name")
xml_text(namelayer) <- "Name changed"

userstyle <- xml_find_first(namedlayer, ".//sld:UserStyle")
namelayer <- xml_find_first(userstyle, ".//sld:Name")
xml_text(namelayer) <- "Name changed"

symb <- xml_find_first(xml_file, ".//sld:RasterSymbolizer")
xml_add_child(symb, "sld:ColorMap", type = "values", .where = 1)

colormap <- xml_find_first(xml_file, ".//sld:ColorMap")

labels <- letters[1:5]
colors <- viridis::turbo(5)

for (i in seq_along(labels)) {
  xml_add_child(colormap, "sld:ColorMapEntry",
                color = colors[[i]],
                quantity = i,
                label = labels[[i]],
                .where = i)
}

write_xml(xml_file, "reprex.sld", format = c("format", "as_xml"))

Created on 2024-04-17 with reprex v2.1.0

And here the output file that places all color map entries in one line:

<?xml version="1.0" encoding="UTF-8"?>
<sld:StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" version="1.0.0">
  <sld:NamedLayer>
    <sld:Name>Name changed</sld:Name>
    <sld:UserStyle>
      <sld:Name>Name changed</sld:Name>
      <sld:FeatureTypeStyle>
        <sld:Name>name</sld:Name>
        <sld:Rule>
          <sld:RasterSymbolizer>
                <sld:ColorMap type="values"><sld:ColorMapEntry color="#30123BFF" quantity="1" label="a"/><sld:ColorMapEntry color="#28BBECFF" quantity="2" label="b"/><sld:ColorMapEntry color="#A2FC3CFF" quantity="3" label="c"/><sld:ColorMapEntry color="#FB8022FF" quantity="4" label="d"/><sld:ColorMapEntry color="#7A0403FF" quantity="5" label="e"/></sld:ColorMap></sld:RasterSymbolizer>
        </sld:Rule>
      </sld:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>

Solution

  • Here is a generic XSLT that will indent any well-formed XML file.

    Input XML + XSLT => Output XML

    You just need to learn how to execute that XSLT in your favorite programming language.

    Check it out an extension for the 'xml2' package to transform XML documents by applying an 'xslt' style-sheet: xslt: Extensible Style-Sheet Language Transformations

    XSLT

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
       <xsl:output  method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="no"/>
    
       <xsl:strip-space elements="*"/>
    
       <xsl:template match="node()|@*">
          <xsl:copy>
             <xsl:apply-templates select="node()|@*"/>
          </xsl:copy>
       </xsl:template>
    </xsl:stylesheet>