Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT - select nodes that came after another node

I'm trying to select all nodes that 1) come after a node with a particular property and 2) have a particular property themselves. So if I had the following XML:

<node id="1"><child attr="valueOfInterest"/></node>
<node id="2"><child attr="boringValue"/></node>
...
<node id="3"><child attr="valueOfInterest"/></node>
<node id="4"><child attr="boringValue"/></node>
<node id="5"><child attr="boringValue"/></node>
<node id="6"><child attr="boringValue"/></node>
...

My XSLT traverses through each node tag. At each node, I want it to select all previous nodes that occurred since the most recent node that had a child whose attr was valueOfInterest. So if I were at node #2, I would want an empty node set. If I were at node #6, I would want to select node #'s 4 and 5. I currently have the following XSLT:

<xsl:variable name="prev_vals"
    select="preceding-sibling::node/child[@attr = $someValueICareAbout]/@attr"/>

So this XSLT gets all preceding attr values that are a particular value. How do I only get those preceding attr values that are in nodes that come after the most recent node whose child has a particular attr value (i.e., "valueOfInterest")? The id attribute on node tags is not guaranteed to be increasing, so we can't compare against that.

Edit: I thought these might be of use:

<xsl:variable name="prev_children_of_interest"
    select="preceding-sibling::node/child[@attr != $someValueICareAbout]"/>
<xsl:variable name="mru_child_of_interest"
    select="$prev_children_of_interest[count($prev_children_of_interest)]"/>

So that's all previous child tags with attr=valueOfInterest and then the most recently used (closest to current node) child tag that has the attribute I'm looking for. From mru_child_of_interest we can find the most recently used parent tag, but how do we then look for nodes that come after that tag?

like image 540
Sarah Vessels Avatar asked Jun 13 '11 19:06

Sarah Vessels


2 Answers

I am not sure if I understand your question correctly, but here is some XSL 1.0 (additional each-nodes attributes are informational only):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="nodes">
        <xsl:copy>
            <xsl:apply-templates select="node"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="node">
        <xsl:variable name="someValueICareAbout">valueOfInterest</xsl:variable>

        <xsl:variable name="recentParticularNode"
            select="preceding-sibling::node[child/@attr = $someValueICareAbout][1]"/>

        <xsl:variable name="recentParticularNodePosition"
            select="count($recentParticularNode/preceding-sibling::node) + 1"/>

        <xsl:variable name="currentPosition" select="position()"/>

        <xsl:if test="child/@attr != $someValueICareAbout">
            <each-nodes id="{@id}" cp="{$currentPosition}" 
                    rpnp="{$recentParticularNodePosition}">
                <xsl:copy-of
                    select="../node[position() &gt; $recentParticularNodePosition
                    and position() &lt; $currentPosition]"/>
            </each-nodes>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Input XML:

<?xml version="1.0" encoding="UTF-8"?>
<nodes>
    <node id="1"><child attr="valueOfInterest"/></node>
    <node id="2"><child attr="boringValue2"/></node>
    <node id="3"><child attr="valueOfInterest"/></node>
    <node id="4"><child attr="boringValue4"/></node>
    <node id="5"><child attr="boringValue5"/></node>
    <node id="6"><child attr="boringValue6"/></node>
</nodes>

Result XML:

<?xml version="1.0" encoding="UTF-8"?>
<nodes>
   <each-nodes id="2" cp="2" rpnp="1"/>
   <each-nodes id="4" cp="4" rpnp="3"/>
   <each-nodes id="5" cp="5" rpnp="3">
      <node id="4">
         <child attr="boringValue4"/>
      </node>
   </each-nodes>
   <each-nodes id="6" cp="6" rpnp="3">
      <node id="4">
         <child attr="boringValue4"/>
      </node>
      <node id="5">
         <child attr="boringValue5"/>
      </node>
   </each-nodes>
</nodes>
like image 123
Grzegorz Szpetkowski Avatar answered Oct 09 '22 20:10

Grzegorz Szpetkowski


This transformation copies exactly the wanted nodes:

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

 <xsl:template match=
 "node[not(child/attr='valueOfInterest')]">

 <xsl:variable name="vFollowing" select=
 "preceding-sibling::node
          [child/@attr='valueOfInterest'][1]
            /following-sibling::node"/>

 <xsl:variable name="vPreceding" select=
  "preceding-sibling::node"/>

  <xsl:copy-of select=
  "$vFollowing[count(. | $vPreceding)
              =
               count($vPreceding)
              ]
  "/>
======================
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

when applied on this XML document (based on the provided XML fragment and wrapping it in a top element to make it a well-formed XML document):

<t>
    <node id="1">
        <child attr="valueOfInterest"/>
    </node>
    <node id="2">
        <child attr="boringValue"/>
    </node>...
    <node id="3">
        <child attr="valueOfInterest"/>
    </node>
    <node id="4">
        <child attr="boringValue"/>
    </node>
    <node id="5">
        <child attr="boringValue"/>
    </node>
    <node id="6">
        <child attr="boringValue"/>
    </node>...
</t>

the wanted, correct result is produced:

======================

======================
 <node id="2">
   <child attr="boringValue"/>
</node>
======================

======================
 <node id="4">
   <child attr="boringValue"/>
</node>
======================
 <node id="4">
   <child attr="boringValue"/>
</node>
<node id="5">
   <child attr="boringValue"/>
</node>
======================

Explanation:

  1. Here we use the well-known Kayessian formula (discovered by the SO user @Michael Kay) for the intersection of two node-sets $ns1 and $ns2:

    ns1[count(.|$ns2) = count($ns2)]

  2. We simply substitute $vFollowing and $vPreceding for $ns1 and $ns2 in the above formula.

$vFollowing is defined to contain exactly all the following sibling elements namednodeof the nearestnode` that satisfies the condition (to be interesting).

$vPreceding is the set of all node elements that are preceding siblings of the current (matched) node.

.3. Their intersection is exactly the wanted node-set.

like image 3
Dimitre Novatchev Avatar answered Oct 09 '22 19:10

Dimitre Novatchev