XSLT 1.0递归超1000次崩溃,如何转为DVC风格?
问题描述
使用XSLT 1.0处理器编写的entry-check递归函数,当index>1000时因栈溢出崩溃,报错信息:
detected a recursion xmlXPathCompiledEval: 3 objects left on stack
其中仅处理cmt节点的递归调用行失效。函数代码如下:
<func:function name="entry-check"> <xsl:param name="bunch"/> <xsl:param name="history"/> <xsl:param name="index"/> <xsl:param name="depth"/> <xsl:variable name="member" select="$bunch[$index]"/> <xsl:variable name="vipmember" select="key('member-any',$member/@i)[check-vipmember(.)]"/> <xsl:choose> <xsl:when test="not($member)"> <func:result select="$bunch[$history]"/> </xsl:when> <xsl:when test="$vipmember"> <func:result select="pp:tern($history,set:trailing($bunch,$member),$vipmember[1])"/> </xsl:when> <xsl:when test="$member/self::cmt"> <func:result select="entry-check($bunch,$history,$index -1,$depth)"/> </xsl:when> <xsl:otherwise> <xsl:variable name="next" select="pp:version-bunch(key('i',$member[$depth]/@from)/..)"/> <func:result select="entry-check($next,$history,count($next),$depth -1)|$bunch[$history]"/> </xsl:otherwise> </xsl:choose> </func:function>
参数说明:
bunch:节点集history:恒为true()- 初始调用时
depth=3,index为bunch节点数减1
已尝试改写为尾递归但问题依旧,需要改为DVC(分治)风格或其他有效方案,支持1000-100000次迭代。
解决方案
方案1:节点集过滤替代线性递归遍历cmt
原函数中处理cmt节点的线性递归是栈溢出的核心原因。直接用XPath过滤掉目标范围内的所有cmt节点,一次性定位到需要处理的非cmt节点,彻底消除逐层递归的栈压力。
修改后的函数代码:
<func:function name="entry-check"> <xsl:param name="bunch"/> <xsl:param name="history"/> <xsl:param name="index"/> <xsl:param name="depth"/> <!-- 过滤index范围内的所有非cmt节点,取最后一个(对应原逻辑倒序找到的第一个有效节点) --> <xsl:variable name="filtered-members" select="$bunch[position() <= $index][not(self::cmt)]"/> <xsl:variable name="member" select="$filtered-members[last()]"/> <xsl:choose> <xsl:when test="not($member)"> <func:result select="$bunch[$history]"/> </xsl:when> <xsl:when test="key('member-any',$member/@i)[check-vipmember(.)]"> <func:result select="pp:tern($history,set:trailing($bunch,$member),key('member-any',$member/@i)[check-vipmember(.)][1])"/> </xsl:when> <xsl:otherwise> <xsl:variable name="next" select="pp:version-bunch(key('i',$member[$depth]/@from)/..)"/> <func:result select="entry-check($next,$history,count($next),$depth -1)|$bunch[$history]"/> </xsl:otherwise> </xsl:choose> </func:function>
逻辑说明:
- 跳过原函数中逐个
cmt节点递归的步骤,用XPath一次性筛选出有效节点,避免线性递归的栈累积 - 保留原函数核心业务逻辑,仅优化遍历
cmt节点的方式
方案2:DVC分治实现(适配复杂遍历场景)
如果需要保留更灵活的遍历逻辑,采用分治策略将节点集拆分为前后两部分,递归深度为对数级(即使10万节点,深度仅约17),彻底避免栈溢出。
<!-- 分治核心函数 --> <func:function name="entry-check-dvc"> <xsl:param name="bunch"/> <xsl:param name="history"/> <xsl:param name="depth"/> <!-- 终止条件:节点集为空 --> <xsl:if test="not($bunch)"> <func:result select="$bunch[$history]"/> <xsl:return/> </xsl:if> <!-- 拆分节点集为前后两半 --> <xsl:variable name="mid" select="floor(count($bunch) div 2)"/> <xsl:variable name="first-half" select="$bunch[position() <= $mid]"/> <xsl:variable name="second-half" select="$bunch[position() > $mid]"/> <!-- 优先处理后半部分(对应原逻辑的倒序遍历) --> <xsl:variable name="second-result" select="entry-check-dvc($second-half,$history,$depth)"/> <!-- 后半部分找到有效结果则直接返回 --> <xsl:if test="$second-result[not($second-result = $bunch[$history])]"> <func:result select="$second-result"/> <xsl:return/> </xsl:if> <!-- 否则处理前半部分 --> <func:result select="entry-check-dvc($first-half,$history,$depth)|$bunch[$history]"/> </func:function> <!-- 适配原函数参数的调用入口 --> <func:function name="entry-check"> <xsl:param name="bunch"/> <xsl:param name="history"/> <xsl:param name="index"/> <xsl:param name="depth"/> <!-- 截取原逻辑中需要处理的节点范围 --> <xsl:variable name="target-bunch" select="$bunch[position() <= $index + 1]"/> <func:result select="entry-check-dvc($target-bunch,$history,$depth)"/> </func:function>
逻辑说明:
- 分治函数优先处理节点集后半部分,匹配原函数倒序遍历的逻辑
- 递归深度随节点数量呈对数增长,完全规避栈溢出风险
- 保留原函数的业务判断逻辑,仅改变遍历的递归方式
关键注意事项
- 因
history恒为true(),$bunch[$history]等价于$bunch,可根据实际情况简化代码 - 确保
key('member-any')和check-vipmember()函数的性能,避免节点过多导致的额外性能损耗 - 测试时需验证过滤/分治后的节点处理顺序与原逻辑一致,保证结果正确性
内容的提问来源于stack exchange,提问作者Ivanhou
相关产品推荐
相关产品推荐

