Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to concatenate two node-sets such that order is respected?

My understanding has been that, despite the fact that XSLT's "node-sets" are called "sets", they are, in fact, ordered lists of nodes (which is why each node is associated with an index). I've therefore been trying to use the "|" operator to concatenate node-sets such that the order of the nodes is respected.

What I am attempting to accomplish is something like the following JavaScript code:

[o1,o2,o3].concat([o4,o5,o6])

Which yields:

[o1,o2,o3,o4,o5,o6]

But, consider the following reduced example:

testFlatten.xsl

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

 <xsl:template match="/">

  <xsl:variable name="parentTransition" select="//*[@id='parentTransition']"/>
  <xsl:variable name="childTransition" select="//*[@id='childTransition']"/>
  <xsl:variable name="parentThenChildTransitions" select="$parentTransition | $childTransition"/>
  <xsl:variable name="childThenParentTransitions" select="$childTransition | $parentTransition"/>

  <return>
   <parentThenChildTransitions>
    <xsl:copy-of select="$parentThenChildTransitions"/>
   </parentThenChildTransitions>
   <childThenParentTransitions>
    <xsl:copy-of select="$childThenParentTransitions"/>
   </childThenParentTransitions>
  </return>

 </xsl:template>

</xsl:stylesheet>

Given the following input:

<?xml version="1.0"?>
<root>
        <element id="parentTransition"/>

 <element id="childTransition"/>
</root>

Which yields (with xsltproc):

<?xml version="1.0"?>
<return>
    <parentThenChildTransitions>
        <element id="parentTransition"/><element id="childTransition"/>
    </parentThenChildTransitions>
    <childThenParentTransitions>
        <element id="parentTransition"/><element id="childTransition"/>
    </childThenParentTransitions>
</return>

So the "|" operator in fact does not respect the order of the node-set operands. Is there a way I can concatenate node-sets such that order is respected?

like image 714
jbeard4 Avatar asked Dec 17 '22 18:12

jbeard4


2 Answers

This is actually not an XSLT but an XPath question.

In XPath 1.0 there isn't anything similar to a "list" datatype. A node-set is a set and it has no order.

In XPath 2.0 there is the sequence data type. Any items in a sequence are ordered. This has nothing to do with document order. Also, the same item (or node) can appear more than once in a sequence.

So, in XSLT 2.0 one just uses the XPath 2.0 sequence concatenation operator ,:

//*[@id='parentTransition'] , //*[@id='childTransition']

and this evaluates to the sequence of all elements in the document with id attribute 'parentTransition' followed by all elements in the document with id attribute 'childTransition'

In XSLT it is still possible to access and process nodes not in document order: for example using the <xsl:sort> instruction -- however the set of nodes that are processed as result of <xsl:apply-templates> or <xsl:for-each> is a node-list -- not a node-set.

Another example of evaluating nodes not in document order is the position() function within <xsl:apply-templates> or <xsl:for-each> that have a <xsl:sort> child or within a predicate of a location step (of an XPath expression) in which a reverse axis is used (such as ancesstor:: or preceeding::)

like image 117
Dimitre Novatchev Avatar answered Dec 19 '22 08:12

Dimitre Novatchev


In XSLT 1.0, you can process nodes in a selected order (for example by use of xsl:sort), but you can't hold a list of nodes in a variable. The only thing you can hold in a variable (or pass to a template, etc) is a node-set; node-sets have no intrinsic order, but when you process them, they are always processed in document order unless you use xsl:sort to request a different processing order.

You might be able to solve your problem by copying the nodes:

<xsl:variable name="temp">
  <xsl:copy-of select="$ns0"/>
  <xsl:copy-of select="$ns1"/>
</xsl:variable>
...
<xsl:apply-templates select="exslt:node-set($temp/*)"/>

but this depends on your use-case.

Switch to XSLT 2.0 if you can!

like image 29
Michael Kay Avatar answered Dec 19 '22 08:12

Michael Kay