Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT stylesheet replaces self-closing tags with empty paired tags

Tags:

xslt

I'm using XSLT to process my ASP.Net web.config file to insert some extra log4net configuration. It's applied by the NANT standard task called <style>. While it successfully inserts the new content, it turns the many self-closing tags into empty paired tags. For example, a partial web.config looks like this before:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
    <section name="log4net"
             type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<appSettings>
    <add key="SomeKey" value="SomeValue"/>
</appSettings>

After applying the stylesheet, the <section> and <add> tags (and all other tags) are no longer self-closing:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <configSections>
        <section name="log4net"
         type="log4net.Config.Log4NetConfigurationSectionHandler, log4net">
        </section>
    </configSections>
    <appSettings>
        <add key="SomeKey" value="SomeValue">
        </add>
    </appSettings>

My stylesheet looks like this:

<?xml version="1.0" encoding="utf-8"?>
<!-- This stylesheet is applied to web.config files to insert log4net appender
filters that will prevent logging messages resulting from pages requested by
AIS monitoring systems. -->
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
    exclude-result-prefixes="msxsl">
    <xsl:output method="xml" indent="yes" />
    <xsl:preserve-space elements="configuration"/>
    <!-- Copy input to output, most of the time -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
    </xsl:template>

    <!-- Within log4net <appender> elements, insert standard filters to
    exclude logging traffic resulting from AIS monitoring.  Any existing
    filters are preserved. -->
    <xsl:template match="/configuration/log4net/appender">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
            <xsl:comment
            > Filters inserted by build server during deployment </xsl:comment>
            <filter name="AIS monitor"
             type="log4net.Filter.PropertyFilter">
                <regexToMatch value="^35\.8\.113\.[0-9]+$"/>
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
            <filter name="AIS load balancer"
             type="log4net.Filter.PropertyFilter">
                <regexToMatch value="^10\.160\.0\.[0-9]+$" />
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
            <filter name="localhost" type="log4net.Filter.PropertyFilter">
                <stringToMatch value="127.0.0.1"/>
                <key value="ClientIP"/>
                <acceptOnMatch value="false"/>
            </filter>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Before using NANT to process the stylesheet, I tried MSBuild, using the MSBuild Extension Pack task XmlTask. It preserved the self-closing tags, but would lose most of the line breaks, which made the file human-unreadable (though otherwise correct). Using NANT fits in nicely with my build process, so I'd prefer to use it if I can.

It seems like I should be able to specify that I want to keep self-closing tags in the stylesheet, but I can't figure out how.

like image 490
Carl Raymond Avatar asked Feb 17 '11 17:02

Carl Raymond


2 Answers

Self-closing tags <empty/> and empty element with start and end tags <empty></empty> are semantically identical. Therefore XSLT processor can output whichever it sees best.

You could try to fool the processor by adding empty content in elements. In this case it can be done by modifying the identity template.

<!-- Define a dummy variable with empty content -->
<xsl:variable name="empty" select="''"/>

<!-- Copy input to output, most of the time -->
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
<!-- Insert empty content into copied element -->
        <xsl:value-of select="$empty"/>
    </xsl:copy>
</xsl:template>

Or you could restrict this to empty elements by keeping the original identity template and adding this:

<!-- Identity template for empty elements -->
<xsl:template match="*[not(node())]">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
        <xsl:value-of select="$empty"/>
    </xsl:copy>
</xsl:template>

Functionality depends on the XSLT processor.

Note: A proper XML tool should make no difference between self-closing tags or empty elements with start and end tag. So if this syntax difference is really causing you problems, you should reconsider what methods or tools you use or how you use them.


Update

Crap. Somehow I kept on reading your question the opposite way you meant (probably means it's time for me to take a nap). So... the code above tries to convert self-closing tags to empty pairs, whereas you wanted the opposite <tag></tag> --> <tag/>. Sorry for the mismatch, let me try again.

In a comment you said:

Before it was on a new line and indented to the same position as the opening tag.

From the viewpoint of the XML data model, this means that the node has only empty whitespace as child content. One way of trying to avoid copying these text nodes would be to provide an empty template for them. This could cause problems with mixed content, though.

<xsl:template match="text()[normalize-space() = '']"/>

Another possible solution would be that when we confront empty elements we create a new element with same name instead of copying it.

<xsl:template match="*[not(comment() | processing-instruction() | *)][normalize-space(text()) = '']">
    <xsl:element name="{name()}" namespace="{namespace-uri()}">
        <xsl:for-each select="@* | namespace::*">
            <xsl:copy/>
        </xsl:for-each>
    </xsl:element>
</xsl:template>

This template also copies (unused) namespace definitions in empty elements, which actually seems quite unnecessary. <xsl:for-each> is used instead of <xsl:apply-templates> only because template match doesn't allow the use of namespace axis. <xsl:apply-templates select="@*"/> works too, if you don't want to preserve the extra namespace definitions.

But in the end, AFAIK all these are just processor specific workarounds. When XSLT 1.0 processor creates an empty element, can freely choose whether to use a self-closing tag or an empty pair. Someone please correct me if I'm wrong.

like image 84
jasso Avatar answered Sep 23 '22 19:09

jasso


Just add comment in the element, it will not self close.

<xsl:comment> </xsl:comment>

like image 36
Askkan Avatar answered Sep 24 '22 19:09

Askkan