使用XSLT每年打印一次

时间:2012-10-03 13:42:14

标签: xml xslt loops date-range

我的源文档包含两个定义范围的值(年)。我把它们放在变量$year1$year2中。我现在需要打印以在这两个值之间每年输出一个-Element,包括开始和结束年份。我看到了使用递归创建循环的方法,但尤其是。不知道如何每次增加一个值。有什么想法吗?

3 个答案:

答案 0 :(得分:2)

当必须生成的序列的大小未事先知道且不受限制时,Piez方法不适用。

在这种情况下,XSLT解决方案必须使用递归。

这是一个通用的“迭代”模板,它对初始输入执行操作,然后对其结果执行操作,直到指定给定条件

这个转换是 tail-recursive ,并且在没有堆栈溢出的情况下使用智能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,'&#xA;')"/>
  </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>&#xA;</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 library 。它提供了常用高阶函数的DVC变体,例如foldl()map(),可以生成几乎任何递归算法的DVC变体。

当然,在XSLT2.0中,只需编写

<xsl:sequence select="1 to 1000000"/>

答案 1 :(得分:1)

XSLT 1.0解决方案

请参阅Piez technique

<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() &lt; ($year2 - $year1 + 2)]">
    <xsl:value-of select="concat($year1+position()-1,'&#x0A;')" />
  </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="&#x0A;" />
</xsl:template>

</xsl:stylesheet>

输出

这两种样式表都会产生产品......

2010
2011
2012
2013

答案 2 :(得分:0)

感谢所有帮助

我正在使用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。这很简单。 Trickier是,年份必须格式化为xs: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>

再次感谢 奥利弗