使用previous-sibling with xsl:sort

时间:2017-03-15 09:48:20

标签: sorting xslt

我正在尝试使用previous-sibling和following-sibling以及对它们进行排序的记录子集。上一个/后一个从原始xml顺序返回值的问题:

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

当我在for-each(Salaries / Salary)下使用ac#函数将偏移分钟添加到日期并转换为长号201701010000时(在xslt中进行操作更容易)。

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

排序完美,我按以下顺序输出记录:

  • 2000
  • 1000
  • 3000

如果我使用previous-sibling / preceding(和follow),问题就来了。我希望第一个记录(2000)没有前面的记录,最后一个记录(3000)没有跟随。 但是当我使用前面/后面的时候,我得到了原始XML中的上一条记录和下一条记录:

  • 2000(前 - 1000 /以下 - 3000)
  • 1000(前/后 - 2000)
  • 3000(前 - 2000 /以下 - )

我希望能够与之前的记录(按排序顺序)和当前记录(按排序顺序)进行比较:

  • 2000(前 - 后 - 1000)
  • 1000(前 - 2000 /跟随3000)
  • 3000(前 - 1000 /以下 - )

我已经尝试过前兄弟和前面的

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

(薪水位于不同的命名空间(p) 这实际上是可行的,还是我必须使用变量来保存先前记录的数据以进行比较?

感激地收到任何想法。我正在使用xslt 1.0

3 个答案:

答案 0 :(得分:1)

preceding-sibling轴以文档顺序获取上下文节点的前一个兄弟节点。

要在排序之后引用节点的前面兄弟节点,您需要首先将排序的节点存储在变量中 - 并且在XSLT 1.0中,将变量转换为节点集。

答案 1 :(得分:1)

虽然XSLT / XPath经常讨论节点序列&#34;但实际上它更准确地将其视为节点引用序列&#34; - 因为,例如,同一节点可能在序列中出现多次。对一系列节点引用进行排序时,不能以任何方式更改各个节点,只需更改顺序。这意味着节点在原始树中仍然存在于以前的位置,并且它们的父母,兄弟姐妹和后代与之前完全一样。

你想要的不是节点的前后兄弟节点,而是排序顺序中前后的节点,这是完全不同的事情。

执行此操作的一种方法是构造一个包含原始节点副本的新树,例如,如果您这样做

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

复制节点的兄弟关系将反映排序顺序。还有一个小问题,在XSLT 1.0中,$ x是一个结果树片段,所以你必须使用exslt:node-set()函数将它转换为节点集。

事实上在XSLT 1.0中,这可能是唯一的方法,因为XSLT 1.0数据模型只有节点集,而不是序列,这意味着无法捕获和处理节点序列文件订单以外的任何内容。 2.0模型具有更大的灵活性和强大功能。如果可以升级 - XSLT 1.0即将接近20年。

答案 2 :(得分:1)

感谢Michael的回答。在这里发布完整性。因为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>