前兄弟[1]不是最后一个

时间:2012-04-06 14:57:25

标签: xml xslt xpath xslt-2.0

我要感谢这个社区最近帮助了我这么多。你是我的救星:)

我正在尝试验证数字(x)何时与前一个数字不同 所以我的清单已经分类了,首先我有:

<xsl:apply-templates select="house">
    <xsl:sort select="./point/x"/>
</xsl:apply-templates>

重要的部分是:

<xsl:template match="house">

    <xsl:if test="./point/x != preceding-sibling::house[1]/point/x">
        <br/> 
        <xsl:value-of select="preceding-sibling::house[1]/point/x"/>
             value changed to <xsl:value-of select="./point/x"/>        
    </xsl:if>

    <!-- Just printing -->
    <br/>
    <td><xsl:value-of select="./point/x"/></td>
    <td><xsl:value-of select="./point/y"/></td>

</xsl:template>

注意我在print,x和y中有2个值,但我只使用x
我希望输出类似于:
XY
00
01
0值更改为1
10个
11

不知怎的,我的结果得到了错误的前一个数字

00
1值变为0
01
0值变为1
10个
0值变为1
11

进度: 我发现通过删除排序输出是正确的

00
0值变为1
10个
1值变为0
01
0值变为1
11

所以现在我的问题是......我该怎么做这个程序?

3 个答案:

答案 0 :(得分:3)

不是很好,但如果你必须知道“按照某个顺序的x的前一个值”,那么你必须再次订购节点并获取正确的节点:

<xsl:template match="house">
    <xsl:variable name="current-position" select="position()" />
    <xsl:variable name="preceding-x-ordered">
      <xsl:for-each select="../house">
        <xsl:sort select="point/x" />
        <xsl:if test="position() = $current-position - 1">
          <xsl:value-of select="point/x" />
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>

    <xsl:if test="position() &gt; 1 and point/x != $preceding-x-ordered">
        <br/> 
        <xsl:value-of select="$preceding-x-ordered"/>
        <xsl:text> value changed to </xsl:text>
        <xsl:value-of select="point/x"/>        
    </xsl:if>

    <!-- Just printing -->
    <br/>
    <td><xsl:value-of select="point/x"/></td>
    <td><xsl:value-of select="point/y"/></td>
</xsl:template>

XSLT应该是无副作用的,这意味着处理器可以按照自己的意愿处理模板,而不会改变整体结果。不是“知道”它在循环的最后一次迭代中所做的是该要求的结果。因此,preceding-sibling轴无法知道之前处理的节点。

答案 1 :(得分:3)

这是一个更有效的(线性vs O(N ^ 2 * log(N))XSLT 1.0解决方案

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

 <xsl:key name="kHouseById" match="house" use="generate-id()"/>

 <xsl:variable name="vIdsOfSorted">
  <xsl:for-each select="/*/house">
    <xsl:sort select="point/x" data-type="number"/>
    <xsl:copy-of select="concat(generate-id(), ' ')"/>
  </xsl:for-each>
 </xsl:variable>


 <xsl:template match="/*">
  <xsl:call-template name="traverseIds">
   <xsl:with-param name="pIds" select="concat($vIdsOfSorted, ' ')"/>
   <xsl:with-param name="pLength" select="count(house)"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="traverseIds">
   <xsl:param name="pIds"/>
   <xsl:param name="pLength"/>
   <xsl:param name="pPos" select="1"/>
   <xsl:param name="pPrevId" select="''"/>

   <xsl:if test="not($pPos > $pLength)">
     <xsl:variable name="vId" select="substring-before($pIds, ' ')"/>
     <xsl:variable name="vHouse" select="key('kHouseById', $vId)"/>

     <xsl:value-of select=
      "concat('&#xA;(', $vHouse/point/x, ',', $vHouse/point/y, ')')"/>
       <xsl:if test="not($pPos >= $pLength)">
        <xsl:variable name="vNextId" select=
         "substring-before(substring-after($pIds, ' '), ' ')"/>

        <xsl:variable name="vNextHouse" select="key('kHouseById', $vNextId)"/>

        <xsl:if test="not($vHouse/point/x = $vNextHouse/point/x)">
         <xsl:value-of select=
           "concat('&#xA;value changed to ', $vNextHouse/point/x)"/>
        </xsl:if>

        <xsl:call-template name="traverseIds">
          <xsl:with-param name="pIds" select="substring-after($pIds, ' ')"/>
          <xsl:with-param name="pLength" select="$pLength"/>
                <xsl:with-param name="pPos" select="$pPos+1"/>
                <xsl:with-param name="pPrevId" select="$vId"/>
        </xsl:call-template>
       </xsl:if>
     </xsl:if>
 </xsl:template>
</xsl:stylesheet>

对以下XML文档应用此转换时:

<t>
  <house>
    <point>
      <x>1</x><y>0</y>
    </point>
  </house>
  <house>
    <point>
      <x>0</x><y>1</y>
    </point>
  </house>
  <house>
    <point>
      <x>0</x><y>0</y>
    </point>
  </house>
  <house>
    <point>
      <x>1</x><y>1</y>
    </point>
  </house>
</t>

产生了想要的正确结果

(0,1)
(0,0)
value changed to 1
(1,0)
(1,1)

如果你不喜欢递归,这里是一个使用xxx:node-set()扩展函数的非递归XSLT 1.0解决方案

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common">
 <xsl:output method="text"/>
 <xsl:variable name="vrtfSorted">
        <xsl:for-each select="/*/house">
            <xsl:sort select="point/x" data-type="number"/>
            <xsl:copy-of select="."/>
        </xsl:for-each>
 </xsl:variable>

 <xsl:variable name="vSorted" select="ext:node-set($vrtfSorted)/*"/>

    <xsl:template match="/*">
        <xsl:for-each select="$vSorted">
            <xsl:variable name="vPos" select="position()"/>
            <xsl:if test=
              "$vPos > 1 
             and 
               not(point/x = $vSorted[position()=$vPos -1]/point/x)">
                <xsl:text>&#xA;</xsl:text>
                <xsl:value-of select="concat('value changed to ', point/x)"/>
            </xsl:if>
            <xsl:text>&#xA;</xsl:text>
            <xsl:value-of select="concat('(',point/x,',',point/y, ')')"/>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

产生完全相同的正确结果


<强> II。 XSLT 2.0解决方案

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xsl:output method="text"/>

 <xsl:template match="/*">
  <xsl:variable name="vSorted">
   <xsl:perform-sort select="house">
     <xsl:sort select="xs:integer(point/x)"/>
   </xsl:perform-sort>
  </xsl:variable>

  <xsl:sequence select=
   "(for $n   in 2 to count($vSorted/*),
        $n-1 in $n -1,
        $vNewX in $vSorted/*[$n]/point/x,
        $vOldX in $vSorted/*[$n-1]/point/x,
        $vNewY in $vSorted/*[$n]/point/y,
        $vOldY in $vSorted/*[$n-1]/point/y,
        $remark in
              if($vNewX ne $vOldX)
                then concat('&#xA;value changed to ', $vNewX, '&#xA;')
                else '&#xA;'
     return
       concat('(',$vOldX,',',$vOldY, ')', $remark)
     ),
     concat('(',$vSorted/*[last()]/point/x,',',
                $vSorted/*[last()]/point/y, ')'
            )
   "/>
 </xsl:template>
</xsl:stylesheet>

当应用于同一XML文档(上图)时,会产生相同的正确结果:

 (0,1)
 (0,0)
value changed to 1
 (1,0)
 (1,1)

答案 2 :(得分:2)

这是另一种方法。你可能想要从群体的角度思考。正如你已经标记了问题xslt 2.0,这确实带来了一些有用的分组函数(在xslt 1.0中你必须使用Meunchian分组技术)

有效地,您按照 point / x 值对房屋进行分组,因此您可以获得每个不同的组:

<xsl:for-each-group select="house" group-by="point/x">
   <xsl:sort select="point/x"/>

然后你可以像这样迭代组内的房子:

<xsl:for-each select="current-group()">
    <xsl:sort select="point/y"/>

这是完整的XSLT

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

   <xsl:template match="houses">
      <xsl:text>xy&#13;</xsl:text>
      <xsl:for-each-group select="house" group-by="point/x">
         <xsl:sort select="point/x"/>
         <xsl:if test="position() &gt; 1">
            <xsl:value-of select="concat(' value changed to ', point/x, '&#13;')" />
         </xsl:if>
         <xsl:for-each select="current-group()">
            <xsl:sort select="point/y"/>
            <xsl:value-of select="concat(point/x, point/y, '&#13;')" />
         </xsl:for-each>
         <xsl:if test="position() &lt; last()">
            <xsl:value-of select="point/x" />
         </xsl:if>
      </xsl:for-each-group>
   </xsl:template>
</xsl:stylesheet>

应用于以下XML ....

<houses>
    <house><point><x>1</x><y>0</y></point></house>
    <house><point><x>2</x><y>1</y></point></house>
    <house><point><x>0</x><y>1</y></point></house>
    <house><point><x>1</x><y>1</y></point></house>
    <house><point><x>0</x><y>0</y></point></house>
    <house><point><x>2</x><y>0</y></point></house>
</houses>

以下是输出:

xy
00
01
0 value changed to 1
10
11
1 value changed to 2
20
21