removal of empty nodes resulting from application of other templates

T

tschwartz

I'm trying to write a stylesheet which removes nodes which are empty
as a result of other template processing.

For example, given the following xml, I'd like to:
- remove all "b" elements
- remove all "a" elements which, as a result of "b" element
removal, now have no children

so, starting with:

<?xml version="1.0" encoding="UTF-8"?>
<doc>
<a>
<b/>
</a>
<a>
<b/>
<c/>
</a>
</doc>

I'd like to end up with:

<?xml version="1.0" encoding="UTF-8"?>
<doc>
<a>
<c/>
</a>
</doc>

I've written this stylesheet:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- matches all nodes and attributes -->
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>

<!-- rule for element "a", ought to reproduce "a" only if it has
children -->
<xsl:template match="//a">
<xsl:if test="count(./*) &gt; 0">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>

<!-- remove all b nodes -->
<xsl:template match="//b" priority="3" name="b-removal"/>

</xsl:stylesheet>

but the resultant output has not removed the empty "a" element:
<doc>
<a/>
<a>
<c/>
</a>
</doc>

Can anyone suggest a way to do this?

Thanks,

Ted
 
J

johanneskrueger

I'm trying to write a stylesheet which removes nodes which are empty
as a result of other template processing.

How about applying that stylesheet to your data twice?

Regards,
Johannes
 
J

Joseph Kesselman

XSLT 1.0 doesn't have any built-in ability to do two-pass processing.
The usual solution is to run two stylesheets in succession, or the same
one twice.

If you MUST do it in a single stylesheet, the usual workaround is to use
the EXSLT node-set function. Do a first pass outputting into a variable
to obtain a Result Tree Fragment, then use exslt:node-set() to make that
available to XSLT in a form that it can walk over again, generating the
final output. Modes may be useful in keeping the two passes separate, if
the behavior is different in one than in the other.

(XSLT 2.0 -- still not widely supported, unfortunately, since it adds a
lot of complexity along with the simplifications -- eliminates the
distinction between node-sets and RTFs, and can do what I've just
described without needing a "standard nonstandard" helper.)
 
T

tschwartz

XSLT 1.0 doesn't have any built-in ability to do two-pass processing.
The usual solution is to run two stylesheets in succession, or the same
one twice.

If you MUST do it in a single stylesheet, the usual workaround is to use
the EXSLT node-set function. Do a first pass outputting into a variable
to obtain a Result Tree Fragment, then use exslt:node-set() to make that
available to XSLT in a form that it can walk over again, generating the
final output. Modes may be useful in keeping the two passes separate, if
the behavior is different in one than in the other.

(XSLT 2.0 -- still not widely supported, unfortunately, since it adds a
lot of complexity along with the simplifications -- eliminates the
distinction between node-sets and RTFs, and can do what I've just
described without needing a "standard nonstandard" helper.)

Thanks for the help! As I would like to apply the templates in one
pass I've successfully used the approach of using included
stylesheets and modes described above. Here are the stylesheets:


first-pass.xsl:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- matches all nodes and attributes -->
<xsl:template match="node()|@*" name="identity" mode="first-pass">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="first-pass"/>
</xsl:copy>
</xsl:template>

<xsl:template match="/">
<xsl:apply-templates mode="first-pass"/>
</xsl:template>

<!-- remove all b nodes -->
<xsl:template match="//b" priority="3" name="b-removal" mode="first-
pass"/>

</xsl:stylesheet>

second-pass.xsl:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- matches all nodes and attributes -->
<xsl:template match="node()|@*" name="identity" mode="second-pass">
<xsl:copy>
<xsl:apply-templates select="node()|@*" mode="second-pass"/>
</xsl:copy>
</xsl:template>

<xsl:template match="/">
<xsl:apply-templates mode="second-pass"/>
</xsl:template>

<!-- rule for element "a", ought to reproduce a only if it has
children -->
<xsl:template match="//a" mode="second-pass">
<xsl:if test="count(./*) &gt; 0">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

main.xsl:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:include href="first-pass.xsl"/>
<xsl:include href="second-pass.xsl"/>

<xsl:template match="/">
<xsl:variable name="first">
<xsl:apply-templates mode="first-pass" />
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($first)" mode="second-
pass" />
</xsl:template>

</xsl:stylesheet>


Then with execution of main.xsl against the source xml, I get the
desired output:

<doc>
<a>
<c/>
</a>
</doc>

Thanks again.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top