You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

方案说明

  1. 利用参数$closed-x-stack维护已闭合<x>元素的ID栈,栈顶始终是最近闭合的<x>元素。
  2. 处理<x>元素时,完成自身及子节点复制后,将自身ID压入栈顶,再处理后续兄弟节点。
  3. 处理<y>元素时,直接从栈顶提取最近闭合的<x>元素ID,无需反向遍历文档。
  4. 全程仅需一次文档遍历,避免了原方案的全量复制或低效的following::node()遍历。

内容的提问来源于stack exchange,提问作者tolanj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:56:04