XSLT - select the node s that appeared after another node

I am trying to select all the nodes that 1) come after a node with a specific property and 2) have a specific property. Therefore, 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 goes through the node tag. On each node I want it to select all previous node that came from the very last node that had a child , whose attr was valueOfInterest . Therefore, if I were in node # 2, I need an empty node set. If I were in node # 6, I would like to select node # 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 the previous attr values, which are a specific value. How to get only those that precede attr values ​​that are in the node that appear after the last node that child has a specific attr value (ie "ValueOfInterest")? The id attribute in the node tags does not guarantee an increase, so we cannot compare with this.

Edit: I thought they could be useful:

 <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, all the previous child tags with attr=valueOfInterest , and then the most recent (closest to the current node tag) child tag that has the attribute I'm looking for. From mru_child_of_interest we can find the last used parent tag, but how can we look for nodes that appear after this tag?

+6
source share
4 answers

I'm not sure if I understood your question correctly, but here is some XSL 1.0 (the additional attributes of each-nodes are informational):

 <?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> 

XML input:

 <?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> 

XML Result:

 <?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> 
+6
source

Looks like you want the intersection of two sets. Set 1 is all the nodes after the last valueOfInterest . Set 2 is all nodes up to the current node that do not contain valueOfInterest . For XPath 1.0, the following message will give you an intersection (link found here ).

 $set1[count($set2|.)=count($set2)] 

Given your input, the following XSL demonstrates the list of nodes you are looking for

 <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:variable name="someValueICareAbout">valueOfInterest</xsl:variable> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:template match="node[child/@attr='boringValue']"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> <xsl:variable name="set1" select="preceding-sibling::node[child/@attr='valueOfInterest'][1]/following-sibling::node "/> <xsl:variable name="set2" select="preceding-sibling::node[child/@attr='boringValue']"/> <predecessors> <xsl:copy-of select="$set1[count($set2|.)=count($set2)]"/> </predecessors> </xsl:copy> </xsl:template> </xsl:stylesheet> 

Here is the conclusion

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

Note that the reason I use [1] in preceding-sibling::node[child/@attr='valueOfInterest'][1] is because the order of the set of nodes refers to preceding-sibling here .

If you have XPath 2.0, you can use the intersect operator

  <predecessors> <xsl:copy-of select="$set1 intersect $set2"/> </predecessors> 

This gives the same result.

+3
source

This conversion copies exactly the necessary 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 to this XML document (based on the provided XML fragment and wrapping it in the 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> 

required, the correct result is obtained :

 ====================== ====================== <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

  • Here we use the well-known Kaisei formula (opened by user SO @ Michael Kay) to intersect two node-sets $ns1 and $ns2 :

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

  • 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 named node of the nearest node `, which satisfies the condition (to be interesting).

$vPreceding is a collection of all node elements that are the forerunners of the sibling of the current (matching) node.

0.3. Their intersection is exactly the desired node-set.

+3
source

Here is one way to do this in XSLT 2.0:

 <xsl:variable name="prevVOI" select="(preceding-sibling::node[child/@attr = 'valueOfInterest'])[last()]" /> <xsl:variable name="prevNodesAfterVOI" select="preceding-sibling::node[. >> $prevVOI]" /> 
+2
source

Source: https://habr.com/ru/post/890438/


All Articles