【问题标题】:XSLT 中的嵌套函数调用太多——尾递归?
【发布时间】:2022-01-22 19:12:26
【问题描述】:

我的 XSLT 中有一个递归函数。它旨在识别元素的“链”,其中链被定义为具有 start(n+1)=end(n) 的元素序列,例如:

<!-- first chain starts here -->
<event start="T0" end="T1">doo</event>
<event start="T1" end="T2">doo</event>
<event start="T2" end="T3">doo</event>
<!-- first chain ends here -->
<event start="T4" end="T5">doo</event>
<event start="T5" end="T6">doo</event>

我正在使用以下递归函数:

<!-- returns latest event that is connected to the given event through an uninterrupted chain of other events -->
<xsl:function name="exmaralda:last-endpoint-of-segment-chain">
    <xsl:param name="event"/>
    <xsl:choose>
        <xsl:when test="not($event/following-sibling::event) or exmaralda:timeline-position($event/following-sibling::event[1]/@start)&gt;exmaralda:timeline-position($event/@end)">
            <xsl:value-of select="$event/@end"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="exmaralda:last-endpoint-of-segment-chain($event/following-sibling::event[1])"/>
        </xsl:otherwise>
    </xsl:choose>        
</xsl:function>

只要事件序列不太长(它们通常包含不超过 10 个元素),它就可以正常工作。但是,如果它们的长度超过某个值,则转换会引发错误“XSLT 中的嵌套函数调用过多”。我已经读过,为了避免这种情况,递归函数应该是尾递归的。但是,我不明白为什么我的递归具有该属性。谁能看到这里可能出了什么问题?

我使用 Saxon 9he 作为 XSL 引擎。

【问题讨论】:

标签: xml recursion xslt xslt-2.0 saxon


【解决方案1】:

您的函数不是尾递归的原因是尾递归函数必须按原样返回递归调用的结果,而无需进一步处理。您的调用请求进一步处理:xsl:value-of 指令要求将结果转换为文本节点。

Saxon 有时会发现您并不真正想要这种转换,但只有在您声明函数结果的类型时,它才能做到这一点。

因此,您应该对代码进行两项改进,这两项都是标准的良好编码习惯:(a) 使用 xsl:sequence 而不是 xsl:value-of 来返回函数结果,以及 (b) 使用 as 属性xsl:functionxsl:param 声明函数参数和结果的类型。

此外,正如 Martin 指出的那样,使用 xsl:for-each-groupxsl:iterate 而不是在有意义的地方使用递归函数是一个好主意:它通常使代码更具可读性并且通常更高效。

【讨论】:

  • 完美,太棒了,太棒了!进行了建议的更改。转换需要几秒钟,但它会以所需的结果终止并且没有错误消息。非常感谢。
  • 我只希望在内存耗尽时看到关于太多嵌套函数调用的错误,这最常见于错误的递归终止测试。 (在 OP 的情况下,内存可能会耗尽,但除非它远远超过“10 个元素”,否则从表面上看,内存耗尽似乎不太可能。)除了可用内存之外,Saxon 是否必须硬编码递归深度限制?跨度>
  • 另外,可能想在您的回答中明确指出,尾递归可以提供帮助的原因是它可以实现优化,从而将递归转换为编译器的迭代。
  • 错误信息“嵌套函数调用过多”是由 Saxon 捕获 StackOverflow 异常引起的。没有硬编码限制。
  • 很好,这就是我希望听到的。谢谢。
【解决方案2】:

假设 XSLT 3(自 9.8 起受支持),它似乎可以识别您可以使用的“链”group-adjacent

  <xsl:template match="events">
    <xsl:copy>
      <xsl:for-each-group select="event" composite="yes" group-adjacent="@end => substring-after('T') => xs:integer() - position(), @start => substring-after('T') => xs:integer() - (position() + 1)">
        <chain>
          <xsl:sequence select="current-group()"/>
        </chain>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

https://xsltfiddle.liberty-development.net/nbspVax

在 XSLT 中使用 break-when 可能会实现更通用和更精确的声明性分组实现,目前由 SaxonCS 支持,希望很快由 Saxon 11 支持:

  <xsl:template match="events">
    <xsl:copy>
      <xsl:for-each-group select="event" break-when="$next/@start => substring-after('T') => xs:integer() gt $group[last()]/@end => substring-after('T') => xs:integer()">
        <chain>
          <xsl:sequence select="current-group()"/>
        </chain>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-03
    • 2012-12-03
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-07
    相关资源
    最近更新 更多