XSLT 1.0下无需双遍历/node-set查找最近闭合的指定元素
需求:用XSLT 1.0查找最近闭合的指定元素
基于XSLT 1.0(可使用EXSLT,尽量避免扩展函数)实现:从当前节点反向遍历文档文本,找到第一个已闭合的<x>元素(即第一个遇到的</x>标签对应的起始<x>元素),而非最近打开的<x>元素。
示例场景
<e3> <e2> <x id="1"> <y/><!-- 当前节点为此y时,无匹配结果 --> ... <x id="2"> <y/><!-- 当前节点为此y时,无匹配结果 --> ... <x id="3"/> <y/><!-- 当前节点为此y时,匹配id="3"的x --> </x> ... <y/><!-- 当前节点为此y时,匹配id="2"的x --> </x> </e2> <e1> ... <y/><!-- 当前节点为此y时,匹配id="1"的x --> </e1> </e3>
注:<x>和<y>可嵌套在任意其他元素中。
现有方案的不足
- 预处理添加标记元素,再通过
preceding::marker[1]/preceding-sibling:x[1]查找,但需要全量复制文档,开销大。 - 使用以下代码通过校验最近打开的元素实现,但
generate-id(.)会触发全文档遍历,效率极低:
<xsl:variable name="currentId" select="generate-id(.)"/> preceding:x[1]/ancestor-or-self:x[following::node()[generate-id(.) = $currentId]][last()]
优化方案(无需node-set/双遍历)
核心思路是在单次遍历中维护一个「已闭合<x>元素栈」,处理<y>节点时直接取栈顶的<x>元素即可,无需二次遍历或文档复制。以下代码可达到与原node-set方案相同的输出效果:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 默认身份模板:复制所有节点和属性 --> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:apply-templates> <xsl:with-param name="closed-x-stack" select="$closed-x-stack"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <!-- 处理<x>元素:完成自身复制后,将ID加入闭合栈再处理后续节点 --> <xsl:template match="x"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:apply-templates> <xsl:with-param name="closed-x-stack" select="$closed-x-stack"/> </xsl:apply-templates> </xsl:copy> <!-- 将当前<x>的ID压入栈顶,处理下一个兄弟节点 --> <xsl:apply-templates select="following-sibling::node()[1]"> <xsl:with-param name="closed-x-stack" select="concat(generate-id(.), ' ', $closed-x-stack)"/> </xsl:apply-templates> <!-- 跳过默认后续节点处理,避免重复遍历 --> <xsl:apply-templates select="following-sibling::node()[position() > 1]" mode="skip"/> </xsl:template> <!-- 跳过节点模板:仅传递闭合栈,不执行额外复制逻辑 --> <xsl:template match="node()" mode="skip"> <xsl:apply-templates select="."> <xsl:with-param name="closed-x-stack" select="$closed-x-stack"/> </xsl:apply-templates> </xsl:template> <!-- 处理<y>元素:从闭合栈提取最近闭合的<x>的ID --> <xsl:template match="y"> <xsl:param name="closed-x-stack"/> <xsl:copy> <xsl:if test="$closed-x-stack != ''"> <xsl:variable name="top-x-id" select="substring-before($closed-x-stack, ' ')"/> <xsl:attribute name="lastClosingXhadID"> <xsl:value-of select="//*[generate-id(.) = $top-x-id]/@id"/> </xsl:attribute> </xsl:if> <xsl:apply-templates select="@*"/> <xsl:apply-templates> <xsl:with-param name="closed-x-stack" select="$closed-x-stack"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <!-- 初始化闭合栈为空 --> <xsl:template match="/"> <xsl:apply-templates> <xsl:with-param name="closed-x-stack" select="''"/> </xsl:apply-templates> </xsl:template> </xsl:stylesheet>
方案说明
- 利用参数
$closed-x-stack维护已闭合<x>元素的ID栈,栈顶始终是最近闭合的<x>元素。 - 处理
<x>元素时,完成自身及子节点复制后,将自身ID压入栈顶,再处理后续兄弟节点。 - 处理
<y>元素时,直接从栈顶提取最近闭合的<x>元素ID,无需反向遍历文档。 - 全程仅需一次文档遍历,避免了原方案的全量复制或低效的
following::node()遍历。
内容的提问来源于stack exchange,提问作者tolanj
相关产品推荐
相关产品推荐

