我的源文档包含定义范围的两个值(年)。我把它们放在变量$year1
和$year2
. 我现在需要打印以在这两个值之间的每一年输出一个 -Element,包括开始和结束年份。我看到了一种使用递归创建循环的方法,但尤其是。看不到如何每次将值增加一。有任何想法吗?
3 回答
当必须生成的序列的大小事先不知道且不受限制时,Piez 方法不适用。
在这种情况下,XSLT 解决方案必须使用递归。
这是一个通用的“迭代”模板,它对初始输入执行操作,然后对其结果执行操作,直到指定给定条件。
这种转换是尾递归的,并且可以使用智能 XSLT 处理器在没有堆栈溢出的情况下工作:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:action>
<end>1000000</end>
</my:action>
<xsl:variable name="vAction"
select="document('')/*/my:action"/>
<xsl:template match="/">
<xsl:call-template name="iterate">
<xsl:with-param name="pAction" select="$vAction"/>
<xsl:with-param name="pInput" select="0"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="iterate">
<xsl:param name="pAction"/>
<xsl:param name="pInput"/>
<xsl:if test="string-length($pInput)">
<xsl:variable name="vResult">
<xsl:apply-templates select="$pAction">
<xsl:with-param name="pInput" select="$pInput"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:copy-of select="$vResult"/>
<xsl:call-template name="iterate">
<xsl:with-param name="pAction"
select="$pAction"/>
<xsl:with-param name="pInput" select="$vResult"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="my:action">
<xsl:param name="pInput" select="0"/>
<xsl:if test="not($pInput >= end)">
<xsl:value-of select="concat($pInput+1,'
')"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当将此转换应用于任何 XML 文档(未使用)时,将尾递归优化为迭代的智能 XSLT 处理器会产生所需的结果,而不会发生堆栈溢出。Saxon 6.5.4 就是这种情况,我用它来产生结果。
问题是并非所有 XSLT 处理器都能识别和优化尾递归。
对于这样的处理器,可以使用 DVC 风格的递归:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="1000000"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="displayNumbers">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="not($pStart > $pEnd)">
<xsl:choose>
<xsl:when test="$pStart = $pEnd">
<xsl:value-of select="$pStart"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vMid" select=
"floor(($pStart + $pEnd) div 2)"/>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vMid"/>
</xsl:call-template>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$vMid+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
使用 MSXML4,此转换会产生正确的结果而不会发生任何崩溃。
使用此 DVC 转换,最大递归深度仅为 Log2(N)——在本例中为 19。
我建议使用FXSL 库。它提供了常用高阶函数的 DVC 变体,例如foldl()
并且map()
可以生成几乎任何递归算法的 DVC 变体。
当然,在 XSLT2.0 中可以简单地写:
<xsl:sequence select="1 to 1000000"/>
XSLT 1.0 解决方案
请参阅Piez 技术。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="year1" select="2010" />
<xsl:variable name="year2" select="2013" />
<xsl:template match="/">
<xsl:for-each select="(//node()|//@*)[position() < ($year2 - $year1 + 2)]">
<xsl:value-of select="concat($year1+position()-1,'
')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0 解决方案
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="year1" select="2010" />
<xsl:variable name="year2" select="2013" />
<xsl:template match="/">
<xsl:value-of select="$year1 to $year2" separator="
" />
</xsl:template>
</xsl:stylesheet>
输出
这两种样式表都将产生产品...
2010
2011
2012
2013
感谢所有的帮助
我正在使用 xslt 2.0,所以 Dimitris 最后的简短说明已经做到了:
<xsl:template name="yearranges">
<xsl:param name="year1" />
<xsl:param name="year2" />
<xsl:for-each select="$year1 to $year2">
<year>
<xsl:sequence select="." />
</year>
</xsl:for-each>
</xsl:template>
我每年都必须在 -tag 中打印。那很简单。更棘手的是,年份必须格式化为 xs:integer for xsl:sequence,这是在调用模板中完成的:
<xsl:call-template name="yearranges">
<xsl:with-param name="year1" as="xs:integer" select="xs:integer($year1)" />
<xsl:with-param name="year2" as="xs:integer" select="xs:integer($year2)" />
</xsl:call-template>
再次感谢奥利弗