Re: [xsl] pruning nodes not in xpath list

Subject: Re: [xsl] pruning nodes not in xpath list
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Wed, 28 Feb 2001 09:38:31 +0000
Hi Cliff,

> I would like to be able to generate a sytlesheet that will produce
> output documents that exclude all nodes that are not matched by any
> of the xpath expressions.

The first thing is to work out a stylesheet that does what you want
with the expressions hard-coded into it...

> From an algorithm point of view, one solution is to create a
> node-set collection of all nodes that should be copied to the output
> document. For each xpath, find the terminal nodes and add them and
> all of their ancestor elements to the node-set collection. After the
> node-set collection is created, visit each node in source tree in
> document order -- if the node exists in the node-set, use
> <xsl:copy/>. This would ensure that each node only gets copied once,
> and in the document-order so the tree is maintained. I cannot figure
> out a way to implement this algorithm using XSLT. This could be done
> with a DOM (Document), but I don't want to have to implement all of
> the code to handle "xpath" expressions.

OK, so first work out which nodes have been selected by the

<xsl:variable name="selected-nodes"
              select="//product/@sku | //product/cost"/>

Then to work out which nodes should therefore be copied - that's all
those nodes that are selected, plus their ancestors:

<xsl:variable name="copied-nodes"
              select="$selected-nodes | $selected-nodes/ancestor::*"/>

Now, starting from the top (the document element is always the first
in the list of copied nodes), we want to work through the document. If
the element is in the list of selected nodes, then we just want to
copy it completely. Otherwise, we want to copy the element, copy any
attributes that are in the list of selected nodes, and apply templates
to any of its children that are in the list of nodes to be copied.

Now, working out whether a node is in a set of nodes involves a bit of
set manipulation - if you count how many nodes there are in the set
that's the union of the node and the node set, and it's the same as
the number of nodes in the node set, then the node has to be in the
node set.  So to work out whether the context node is in the set of
$selected-nodes, I can use:

  count(.|$selected-nodes) = count($selected-nodes)

Since $selected-nodes and $copied-nodes are known up front, I'll
create variables that indicate how many nodes are in each of them:

<xsl:variable name="nselected-nodes" select="count($selected-nodes)" />
<xsl:variable name="ncopied-nodes" select="count($copied-nodes)"/>

So the template looks like:

<xsl:template match="*">
      <!-- this node is one of the selected nodes -->
      <xsl:when test="count(.|$selected-nodes) = $nselected-nodes">
         <xsl:copy-of select="."/>
            <!-- copy attributes that are selected nodes -->
            <xsl:copy-of select="@*[count(.|$selected-nodes) =
            <!-- apply templates to nodes that are to be copied -->
            <xsl:apply-templates select="node()[count(.|$copied-nodes) =

And there you have it.  That stylesheet will take the input you
specified and give the output you specified.

Now all you have to do is create a stylesheet that creates it.  In
fact most of it is static, so you could bundle it out to a separate
stylesheet and include it rather than create it dynamically.  The only
thing that isn't static is the creation of the $selected-nodes

<xsl:variable name="selected-nodes"
              select="//product/@sku | //product/cost"/>

Assuming that you're using oxsl as the namespace prefix for your XSLT
namespace alias, then you need to create an oxsl:variable with a name
attribute equal to selected-nodes:

<oxsl:variable name="selected-nodes">

The select attribute is created dynamically by iterating over the
various expressions that you have (I'll assume their held in an $exprs

<oxsl:variable name="selected-nodes">
   <xsl:attribute name="select">
      <xsl:for-each select="$exprs">
         <xsl:value-of select="." />
         <xsl:if test="position() != last()"> | </xsl:if>

You should be able to create the rest of the stylesheet very

I hope that helps,


Jeni Tennison

 XSL-List info and archive:

Current Thread