using preceding-sibling with with xsl:sort

2019-03-02 10:54发布

I'm trying to use preceding-sibling and following-sibling with a subset of records with a sort on them. The problem that the preceding / following brings back values from the original xml order:

<Salaries>
    <Salary>
        <Base>1000</Base>
        <CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
            <d7p1:DateTime>2016-01-09T14:38:54.8440764Z</d7p1:DateTime>
            <d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
        </CreatedDate>
    </Salary>
    <Salary>
        <Base>2000</Base>
        <CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
            <d7p1:DateTime>2015-01-09T14:38:54.8440764Z</d7p1:DateTime>
            <d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
        </CreatedDate>
    </Salary>
    <Salary>
        <Base>3000</Base>
        <CreatedDate xmlns:d7p1="http://schemas.datacontract.org/2004/07/System">
            <d7p1:DateTime>2017-01-09T14:38:54.8440764Z</d7p1:DateTime>
            <d7p1:OffsetMinutes>0</d7p1:OffsetMinutes>
        </CreatedDate>
    </Salary>
</Salaries>

When I use a sort under a for-each (Salaries/Salary) with a c# function to add offset minutes into a date and convert to a long number 201701010000 for example(to make manipulation in xslt easier).

<xsl:sort select="number(cs:Convertdatetolong(cs:AddOffsetMinutes(substring(p:CreatedDate/d5p1:DateTime,1,19),p:CreatedDate/d5p1:OffsetMinutes)))" order="ascending"/>

The sort works perfectly and I get the records out in the following order:

  • 2000
  • 1000
  • 3000

The problem comes if I use preceding-sibling / preceding (and following). I would expect the first record (2000) to have no preceding record and the last record (3000) to have no following. However when I use the preceding / following I get the previous record and the next record from the original XML:

  • 2000 (preceding - 1000 / following - 3000)
  • 1000 (preceding - / following - 2000)
  • 3000 (preceding - 2000 / following - )

I would like to be able to compare against the previous record (in the sorted order) and the current record (in the sorted order):

  • 2000 (preceding - / following - 1000)
  • 1000 (preceding - 2000 / following 3000)
  • 3000 (preceding - 1000 / following - )

I've tried preceding-sibling and preceding

<xsl:value-of select="preceding::p:Salary[1]/p:Base"/>
<xsl:value-of select="preceding-sibling::p:Salary[1]/p:Base"/>
<xsl:value-of select="preceding::p:Salary[position()=1]/p:Base"/>

(the salary is in a different namespace (p) Is this actually possible or do I have to use variables to save the previous record's data to compare against?

Any ideas gratefully received. I'm using xslt 1.0

标签: sorting xslt
3条回答
Melony?
2楼-- · 2019-03-02 11:33

Although XSLT/XPath often talks of a "sequence of nodes", it's actually more accurate to think of it as a "sequence of node references" - because, for example, the same node can appear more than once in the sequence. When you sort a sequence of node references, you don't change the individual nodes in any way, you only change the sequence. That means the nodes still exist in their original tree exactly where they were before, and their parents, siblings, and descendants are exactly as they were before.

What you want is not the preceding and following siblings of the node, but the nodes that come before and after it in the sorted sequence, which is a quite different thing.

One way to do this is to construct a new tree containing copies of the original nodes, which you get, for example, if you do

<xsl:variable name="x">
  <xsl:for-each ...>
    <xsl:sort ...>
      <xsl:copy-of select="."/>

The sibling relationships of the copied nodes will then reflect the sorted order. There's the minor problem that in XSLT 1.0, $x is a result tree fragment so you have to convert it to a node-set using the exslt:node-set() function.

In fact in XSLT 1.0 that's probably the only way of doing it, because the XSLT 1.0 data model only has node sets, not sequences, which means there is no way of capturing and processing a sequence of nodes in anything other than document order. The 2.0 model has much more flexibility and power. Upgrade if you can - XSLT 1.0 is approaching 20 years old.

查看更多
等我变得足够好
3楼-- · 2019-03-02 11:33

Thanks to Michael for the answer. Posted here for completeness. Complicated because of the name spaces in use in the xml:

  <!-- Puts the whole of the Salary Node into a variable-->
  <xsl:variable name="SALARY" >
    <xsl:copy-of select="p:Salaries" />
  </xsl:variable>

  <!-- Puts the the required key data into a node-set with the correct sort applied-->
  <xsl:variable name="SAL">
    <xsl:for-each select="msxsl:node-set($SALARY)//p:Salary">
      <xsl:sort select="number(cs:Convertdatetolong(cs:AddOffsetMinutes(substring(p:CreatedDate/d5p1:DateTime,1,19),p:CreatedDate/d5p1:OffsetMinutes)))" order="ascending"/>
      <xsl:copy-of select="." />
    </xsl:for-each>
  </xsl:variable>

<!-- Quick Output-->
  <xsl:for-each select="msxsl:node-set($SAL)//p:Salary">
    <xsl:text>Sa:</xsl:text>
    <xsl:value-of select="position()" />
    <xsl:text>Preceding:</xsl:text>
    <xsl:value-of select="preceding-sibling::p:Salary[1]/p:Base"/>
    <xsl:value-of select="$newline" />
    <xsl:text>Current:</xsl:text>
    <xsl:value-of select="p:Base"/>
    <xsl:value-of select="$newline" />
    <xsl:text>Following:</xsl:text>
    <xsl:value-of select="following-sibling::p:Salary[1]/p:Base"/>
    <xsl:value-of select="$newline"/>
  </xsl:for-each>
查看更多
混吃等死
4楼-- · 2019-03-02 11:38

The preceding-sibling axis gets the preceding siblings of the context node in document order.

To refer to the preceding siblings of a node after sorting, you will need to store the sorted nodes in a variable first - and, in XSLT 1.0, convert the variable into a node-set.

查看更多
登录 后发表回答