Почему index-of() возвращает несколько значений при применении к последовательности уникальных узлов?

Я использую значение index-of xpath2, чтобы вернуть индекс current() в отсортированной последовательности узлов. При использовании SAXON отсортированная последовательность узлов уникальна, но index-of возвращает последовательность из двух значений.

Это происходит не постоянно, а очень редко, но не по какой-либо причине, которую я могу найти. Может кто-нибудь объяснить, что происходит?

Я разработал минимальный пример, основанный на примере данных, которые подпрограммы дают такое странное поведение.

Исходные данные:

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

Мой документ xsl помещает узлы сеанса в отсортированную последовательность $orderd в самом верху корневого шаблона:

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

Поскольку узлы уже были упорядочены @startOn, но в обратном порядке, последовательность $orderd должна быть такой же, как последовательность $nodes, упорядоченная по документам, за исключением обратного порядка.

Когда я создаю вывод с помощью оператора for-each, я обнаруживаю, что каким-то образом два узла воспринимаются как идентичные при тестировании с использованием index-of.

Код ниже используется для вывода данных (и идет сразу после фрагмента выше):

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

Как видно из вывода (показанного ниже), функция index-of возвращает последовательность (1,2), что означает, что она видит оба узла как идентичные. Я проверил выражение, используемое для сортировки значений, и оно создает четкие и правильно сформированные строки даты и времени.

<output>
<session reverse=documentOrder="1"
        sortedOrder="1 2"/>
<session reverse-documentOrder="2"
        sortedOrder="1 2"/>
</output>

person David R    schedule 30.01.2012    source источник


Ответы (2)


Документация http://www.w3.org/TR/xpath-functions/#func-index-of of index-of говорит: «Элементы в последовательности $seqParam сравниваются с $srchParam в соответствии с правилами для оператора eq. Значения типа xs:untypedAtomic сравниваются, как если бы они были типа хз:строка.". Таким образом, вы пытаетесь сравнить нетипизированные узлы элементов, и это означает, что они сравниваются как строки, и оба элемента session имеют одно и то же содержимое пробела, только строковое содержимое. Таким образом, оба сравниваются как равные.

Я не уверен, что предложить, поскольку я не уверен, чего вы хотите достичь, но я надеюсь, что вышеизложенное объясняет результат, который вы получаете.

person Martin Honnen    schedule 30.01.2012
comment
Спасибо, я думаю, что вместо этого я буду использовать index-of( for $n in $orderd return generate-id($n), generate-id(current()) ). (Герритт Имсике предложил это в справочном списке Саксона.) - person David R; 30.01.2012
comment
@DavidR: вам действительно не нужно использовать generate-id(), и вы можете написать свою очень простую функцию index-of(), как показано в моем ответе. - person Dimitre Novatchev; 30.01.2012

Не полагаясь на функцию generate-id(), которая является функцией XSLT, а не функцией XPath, можно написать простую функцию index-of(), которая работает с идентификатором узла:

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

когда это преобразование применяется к следующему XML-документу:

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

возвращается нужный правильный результат:

2 3

Объяснение: использование оператора is.

person Dimitre Novatchev    schedule 30.01.2012
comment
Я прошу прощения за неприятную ошибку SO, из-за которой иногда отформатированный код не имеет отступа. - person Dimitre Novatchev; 30.01.2012