How to get rid of empty attributes in XSLT output?

Some time ago, I faced an issue related to XSLT stylesheets. It was while we were developing quite complex XSLT template whose purpose was to convert one  XML  into completely different another one. During the implementation, it turned out that the output XML contains a lot unnecessary empty attributes, e.g.:

<field attr="" />

It was actually possible to get rid of them with <xsl:if> or <xsl:choose> instructions. However, it would require one conditional instruction per one <xsl:attribute…/> instruction. This would make the XSLT file too complex. So I needed other approach, that would act after the basic transform. And this is achievable by usage of

multi-pass XSLT transforms.

Let’s say that we have the following input XML:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<database>
<record firstName=\"john\" lastName=\"smith\">writer</record>
</database>

We would like to have the above XML converted by this XSLT transform:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output indent="yes" exclude-result-prefixes="xs xsl"/>

    <xsl:template match="/database/record">
        <output>
            <xsl:attribute name="firstName">
                <xsl:value-of select="@firstName"/>
            </xsl:attribute>
            <xsl:attribute name="lastName">
                <xsl:value-of select="@firstName"/>
            </xsl:attribute>
            <xsl:attribute name="middleName">
                <xsl:value-of select="@middleName"/>
            </xsl:attribute>
            <xsl:attribute name="dob">
                <xsl:value-of select="@dob"/>
            </xsl:attribute>
            <xsl:text>recordFound</xsl:text>
        </output>
    </xsl:template>
</xsl:stylesheet>

The output looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<output firstName="john" lastName="john" middleName="" dob="">recordFound</output>

As you can see there are two empty attributes: middlename and dob. And this is where we can think about multi-pass transforms. We would like to have another transform applied on the results of the this XSLT.

Now, let’s consider what should be done to remove empty attributes. This is very to accomplish using identity transforms. This is simple variant of identity transform that removes empty attributes:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:template match="node()|@*" >
        <xsl:copy>
             <xsl:apply-templates select="@*[.!='']" />
             <xsl:apply-templates select="node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

What is left is merging our both  XSLTs. The following code is the final XSLT transformation. We are using modes that are available XSLT 2.0. We store the result of our business operation in the variable $firstPassResult and we perform the removal of empty attributes on it.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">    
    <xsl:output indent="yes" exclude-result-prefixes="xs xsl"/>
    <xsl:variable name="firstPassResult">
        <xsl:apply-templates select="/" mode="firstPass" />
    </xsl:variable>

    <xsl:template match="/database/record" mode="firstPass">
        <output>
            <xsl:attribute name="firstName">
                <xsl:value-of select="@firstName"/>
            </xsl:attribute>
            <xsl:attribute name="lastName">
                <xsl:value-of select="@firstName"/>
            </xsl:attribute>
            <xsl:attribute name="middleName">
                <xsl:value-of select="@middleName"/>
            </xsl:attribute>
            <xsl:attribute name="dob">
                <xsl:value-of select="@dob"/>
            </xsl:attribute>
            <xsl:text>recordFound</xsl:text>
        </output>
    </xsl:template>

    <xsl:template match="/">
        <xsl:apply-templates select="$firstPassResult"
            mode="secondPass" />
    </xsl:template>

    <xsl:template match="node()|@*" mode="secondPass">
        <xsl:copy>
             <xsl:apply-templates select="@*[.!='']" mode="secondPass"/>
             <xsl:apply-templates select="node()" mode="secondPass"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

One thought on “How to get rid of empty attributes in XSLT output?

  1. Pingback: How to change XML to lower case using XSLT multi-pass? « Łukasz Baran Blog

Comments are closed.