Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does index-of() return multiple values when applied to a sequence of unique nodes?

I'm using xpath2's index-of value to return the index of current() within a sorted sequence of nodes. Using SAXON, the sorted sequence of nodes are unique, yet index-of returns a sequence of two values.

This does not happen all the time, just very occasionally, but not for any reason I can find. Can someone please explain what is going on?

I have worked up a minimal example based on an example of data that routines gives this odd behavior.

The source data is:

<data>
<student userID="1" userName="user1"/>
<session startedOn="01/16/2012 15:01:18">
</session>
<session startedOn="11/16/2011 13:31:33">
</session>
</data>

My xsl document puts the session nodes into a sorted sequence $orderd at the very top of the root template:

<xsl:template match="/">
<xsl:variable name="nodes" as="node()*" select="/data/session"></xsl:variable>
<xsl:variable name="orderd" as="node()*">
<xsl:for-each select="$nodes">
<xsl:sort select="xs:dateTime(xs:dateTime(concat(substring(normalize-space(@startedOn),7,4),'-',substring(normalize-space(@startedOn),1,2),'-',substring(normalize-space(@startedOn),4,2),'T',substring(normalize-space(@startedOn),12,8)))
)" order="ascending"/>
    <xsl:sequence select="."/>
</xsl:for-each>
</xsl:variable>

Since the nodes were already ordered by @startOn but in the opposite order, the sequence $orderd should be the same as document-ordered sequence $nodes, except in reverse order.

When I create output using a for-each statement, I find that somehow the two nodes are seen as identical when tested using index-of.

The code below is used to output data (and comes immediately after the chunk above):

<output>
<xsl:for-each select="$nodes">
<xsl:sort select="position()" order="descending"></xsl:sort>
<xsl:variable name="index" select="index-of($orderd,current())" as="xs:integer*"></xsl:variable>
<xsl:variable name="pos" select="position()"></xsl:variable>        
<session reverse-documentOrder="{$pos}"  sortedOrder="{$index}"/>
</xsl:for-each>
</output>

As the output (shown below) indicates, the index-of function is returning the sequence (1,2), meaning that it sees both nodes as identical. I have checked the expression used to sort the values, and it produces distinct and well-formed date-Time strings.

<output>
<session reverse=documentOrder="1"
        sortedOrder="1 2"/>
<session reverse-documentOrder="2"
        sortedOrder="1 2"/>
</output>
like image 862
David R Avatar asked Jan 30 '12 12:01

David R


2 Answers

Not relying on the generate-id() function, which is XSLT function, but not XPath function, one can write a simple index-of() function that operates on node identity:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:my="my:my">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
    <xsl:variable name="vNum3" select="/*/*[3]"/>
    
    <xsl:variable name="vSeq" select="/*/*[1], /*/*[3], /*/*[3]"/>
    
 <xsl:template match="/">
   <xsl:sequence select="my:index-of($vSeq, $vNum3)"/>
 </xsl:template>
 
 <xsl:function name="my:index-of" as="xs:integer*">
  <xsl:param name="pSeq" as="node()*"/>
  <xsl:param name="pNode" as="node()"/>
  
  <xsl:for-each select="$pSeq">
    <xsl:if test=". is $pNode">
      <xsl:sequence select="position()"/>
    </xsl:if>
  </xsl:for-each>
 </xsl:function>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

the wanted, correct result is returned:

2 3

Explanation: Use of the is operator.

like image 195
Dimitre Novatchev Avatar answered Oct 28 '22 01:10

Dimitre Novatchev


The documentation http://www.w3.org/TR/xpath-functions/#func-index-of of index-of says "The items in the sequence $seqParam are compared with $srchParam under the rules for the eq operator. Values of type xs:untypedAtomic are compared as if they were of type xs:string.". So you are trying to compare untyped element nodes and that means they are compared as strings and both session elements have the same white space only string contents. That way both are compared as equal.

I am not sure what to suggest as I am not sure what you want to achieve but I hope the above explains the result you get.

like image 35
Martin Honnen Avatar answered Oct 27 '22 23:10

Martin Honnen