这是一个更有效的(线性 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('
(', $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('
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>
</xsl:text>
<xsl:value-of select="concat('value changed to ', point/x)"/>
</xsl:if>
<xsl:text>
</xsl:text>
<xsl:value-of select="concat('(',point/x,',',point/y, ')')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
产生完全相同的正确结果。
二、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('
value changed to ', $vNewX, '
')
else '
'
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)