XSLT to produce subtree of a tree

M

Martin Bright

I am trying to write an XSLT template which creates a subtree of an input
document, which is a simple tree. For example, the input might be

<node name="1"/>
<node name="2">
<node name="2.1"/>
<node name="2.2"/>
</node>
<node name="3"/>

I pass the template a parameter "2.2" and it creates the following result
fragment:

<node name="2">
<node name="2.2"/>
</node>


There is clearly one inefficient way of doing this:

<xsl:template match="node[.//node[@name='2.2']]">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>

But it seems a shame to evaluate that XPath expression on each node. There
ought also to be a recursive way of doing it, like this:

<xsl:template match="node">
<xsl:choose>
<xsl:when test="@name='2.2'"><xsl:copy/></xsl:when>
<xsl:eek:therwise>
<xsl:variable name="children"><xsl:apply-templates/></xsl:variable>
<xsl:if test="$children">
<xsl:copy><xsl:copy-of select="$children"/></xsl:copy>
</xsl:if>
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

Unfortunately this doesn't work. The reason, AFAICT, is that the Boolean
value of a result tree fragment is always true, because it always contains a
hidden root node.

Can anybody either solve this particular problem of how to tell when a
result tree fragment is empty, or tell me a better way to approach this?

Many thanks
Martin Bright
 
D

Dimitre Novatchev [MVP]

Martin Bright said:
I am trying to write an XSLT template which creates a subtree of an input
document, which is a simple tree. For example, the input might be

<node name="1"/>
<node name="2">
<node name="2.1"/>
<node name="2.2"/>
</node>
<node name="3"/>

I pass the template a parameter "2.2" and it creates the following result
fragment:

<node name="2">
<node name="2.2"/>
</node>

First of all, do notice that the source xml document provided in your
message is not well-formed.

This transformation:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>

<xsl:param name="pNodeID" select="2.2"/>

<xsl:template match="/">
<xsl:apply-templates select="//node[@name = $pNodeID]"/>
</xsl:template>

<xsl:template match="node">
<xsl:apply-templates select="ancestor::*[last()]" mode="gen">
<xsl:with-param name="pPath"
select=".|ancestor::node[position()&lt; last()]" />
</xsl:apply-templates>
</xsl:template>

<xsl:template match="*" mode="gen">
<xsl:param name="pPath" select="/.."/>

<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="$pPath[1]" mode="gen">
<xsl:with-param name="pPath" select="$pPath[position() > 1]"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

when applied on your source.xml (corrected in order to become well-formed):

<node name="0">
<node name="1"/>
<node name="2">
<node name="2.1"/>
<node name="2.2"/>
</node>
<node name="3"/>
</node>

produces the desired result:

<node name="0">
<node name="2">
<node name="2.2"/>
</node>
</node>


Hope this helped.


Cheers,

Dimitre Novatchev [XML MVP],
FXSL developer, XML Insider,

http://fxsl.sourceforge.net/ -- the home of FXSL
Resume: http://fxsl.sf.net/DNovatchev/Resume/Res.html
 

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,995
Messages
2,570,228
Members
46,817
Latest member
AdalbertoT

Latest Threads

Top