Re: [xsl] pruning nodes not in xpath list

Subject: Re: [xsl] pruning nodes not in xpath list
From: "Cliff McBride" <cliff.mcbride@xxxxxxxxxxxxxx>
Date: Wed, 28 Feb 2001 17:07:08 -0800

Thanks a million!!!  I'm not sure I would have ever thought of using
"count(.|$selected-nodes) = $nselected-nodes" to test if the context
node is in the list of selected nodes.  Looks like I need to keep
reading the books and experimenting.

Thanks again,

----- Original Message -----
From: "Jeni Tennison" <mail@xxxxxxxxxxxxxxxx>
To: "Cliff McBride" <cliff.mcbride@xxxxxxxxxxxxxx>
Cc: <XSL-List@xxxxxxxxxxxxxxxxxxxxxx>
Sent: Wednesday, February 28, 2001 1:38 AM
Subject: Re: [xsl] pruning nodes not in xpath list

> 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
> expressions:
> <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="*">
>    <xsl:choose>
>       <!-- this node is one of the selected nodes -->
>       <xsl:when test="count(.|$selected-nodes) = $nselected-nodes">
>          <xsl:copy-of select="."/>
>       </xsl:when>
>       <xsl:otherwise>
>          <xsl:copy>
>             <!-- copy attributes that are selected nodes -->
>             <xsl:copy-of select="@*[count(.|$selected-nodes) =
>                                     $nselected-nodes]"/>
>             <!-- apply templates to nodes that are to be copied -->
>             <xsl:apply-templates select="node()[count(.|$copied-nodes)
>                                                 $ncopied-nodes]"/>
>          </xsl:copy>
>       </xsl:otherwise>
>    </xsl:choose>
> </xsl:template>
> 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
> variable:
> <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">
>    ...
> </oxsl:variable>
> The select attribute is created dynamically by iterating over the
> various expressions that you have (I'll assume their held in an $exprs
> variable):
> <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>
>       </xsl:for-each>
>    </xsl:attribute>
> </oxsl:variable>
> You should be able to create the rest of the stylesheet very
> straight-forwardly.
> I hope that helps,
> Jeni
> ---
> Jeni Tennison

 XSL-List info and archive:

Current Thread