gene b.
gene b.

Reputation: 11984

Specify Exceptions in XSLT for Removing Empty Tags

We have the following XSLT that removes empty tags on all levels.

We also want to specify some exceptions. It can be either a specific tag to avoid, or a parameterized list of tags to avoid. For instance, if we encounter a tag called SpecialTag, then that tag should be allowed to be empty. Everything underneath that tag can still get trimmed. Is that possible?

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

 <xsl:template match="*[not(descendant-or-self::*[text()[normalize-space()] | @*])]"/>

</xsl:stylesheet>

Upvotes: 1

Views: 120

Answers (1)

Eir&#237;kr &#218;tlendi
Eir&#237;kr &#218;tlendi

Reputation: 1180

Here are two ways to do this. Based on your description, this involves comparing against a list of element names.

XSLT 1.0

XSLT 1.0 is more limited in its featureset -- specifically affecting us here, we cannot compare against variables in template match expressions. Consequently, we take a straightforward brute-force approach.

<!-- Test first that the name doesn't match anything we want to keep.
    Just add more conditions as needed.
    Put the main test at the end (that checks for attributes or text). -->
<xsl:template match="*
    [local-name() != 'SpecialTag']
    [local-name() != 'OtherSpecialTag']
    [local-name() != 'ThirdSpecialTag']
    ['Add other conditions here.']
    [not(descendant-or-self::*[text()[normalize-space()] | @*])]
    "/>

XSLT 2.0

In XSLT 2.0, we're allowed to compare against variables in template match expressions. Here's one possible way of using a variable, which might look cleaner or more readable than the XSLT 1.0 approach.

<xsl:variable name="Specials">
    <item>SpecialTag</item>
    <item>OtherSpecialTag</item>
    <item>ThirdSpecialTag</item>
    <!-- Add other tag names here. -->
    <item>...</item>
</xsl:variable>

<!-- Test first that the name doesn't match anything we want to keep.
    Note the syntax - we say `not()` rather than using `!=`, 
    because `!=` evaluates to true if *any* <item> value doesn't match
    the name of the current context element.
    Meanwhile, `not(... = ...)` only evaluates to true if *all* of the
    <item> values don't match the name of the current context element. 
    Put the main test at the end (that checks for attributes or text). -->
<xsl:template match="*
    [not(local-name() = $Specials/item)]
    [not(descendant-or-self::*[text()[normalize-space()] | @*])]
    "/>

Note

The functions name() and local-name() produce the same values if the elements don't have an explicit prefix. If they do have prefixes, name() includes the prefix, while local-name() does not. Adjust the sample code above accordingly to match your use case.

Upvotes: 2

Related Questions