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

如何用XSLT包裹type='s'节点及其紧邻后续type='r'兄弟节点?

问题描述

源XML

<root>
  <i type="r"/>
  <i type="s"/>
  <i type="r"/>
  <i type="r"/>
  <i type="s"/>
  <i type="r"/>
  <i type="s"/>
</root>

需求

要把所有type='s'的节点,和它所有紧邻的后续type='r'兄弟节点(1到n个)一起包裹在<wrap>标签里。

不需要包裹的情况:

  • 没有前置type='s'节点的type='r'节点
  • 后面没有紧邻type='r'节点的type='s'节点

预期输出

<i type="r"/>
<wrap>
  <i type="s"/>
  <i type="r"/>
  <i type="r"/>
</wrap>
<wrap>
  <i type="s"/>
  <i type="r"/>
</wrap>
<i type="s"/>

尝试的XSLT代码

<xsl:for-each select="./i">
  <xsl:choose>
    <xsl:when test="current()[./@type='r' and count(preceding-sibling::i[@type='s']) = 0]">
      <!-- Processing w/o wrap -->
      <xsl:apply-templates select="current()" mode="fill"/>
    </xsl:when>
    <xsl:when test="current()[./@type='s' and following-sibling::i[@type='s']]">
      <!-- Processing w/o wrap -->
      <xsl:apply-templates select="current()" mode="fill"/>
    </xsl:when>
    <xsl:when test="current()[./@type='r' and count(preceding-sibling::i[@type='s']) > 0]">
      <!-- Do nothing -->
    </xsl:when>
    <xsl:when test="current()[./@type='s' and following-sibling::i[1][@type='r']]">
      <wrap>
        <xsl:apply-templates select="current() | //i[@type='r' and preceding-sibling::i[@type='s']" mode="fill"/>
      </wrap>
    </xsl:when>
  </xsl:choose>
</xsl:for-each>

问题:始终没法在<wrap>里定义正确的上下文,导致没法精准匹配每个s对应的后续紧邻r节点。(注:处理<i>节点的模板已省略,和当前问题无关)


解决方案

你代码的核心问题有两个:一是遍历每个<i>节点的逻辑太绕,容易重复判断;二是<wrap>里用//i会匹配所有符合条件的r节点,根本没限定到当前s节点的后续紧邻范围。换成分组处理的思路就能精准搞定:

正确的XSLT代码

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <!-- 匹配root节点作为处理入口 -->
  <xsl:template match="root">
    <!-- 1. 输出所有没有被s节点前置的r节点 -->
    <xsl:apply-templates select="i[@type='r' and not(preceding-sibling::i[@type='s'])]"/>

    <!-- 2. 处理每个需要包裹的s节点:即后面紧跟r的s -->
    <xsl:for-each select="i[@type='s' and following-sibling::i[1][@type='r']]">
      <wrap>
        <!-- 输出当前s节点 -->
        <xsl:apply-templates select="." mode="fill"/>
        <!-- 输出该s节点之后、下一个s节点之前的所有r节点 -->
        <xsl:apply-templates select="following-sibling::i[@type='r'][generate-id(preceding-sibling::i[@type='s'][1])=generate-id(current())]" mode="fill"/>
      </wrap>
    </xsl:for-each>

    <!-- 3. 输出所有后面没有紧跟r的s节点 -->
    <xsl:apply-templates select="i[@type='s' and not(following-sibling::i[1][@type='r'])]"/>
  </xsl:template>

  <!-- 保留你原有fill模板的逻辑,这里用copy-of作为示例 -->
  <xsl:template match="i" mode="fill">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>

代码说明

  1. 拆分逻辑更清晰:把输出拆成三个独立部分,不用在循环里反复判断每个节点的处理方式,减少出错概率:

    • 第一部分:直接筛选出所有没被s前置的r节点,直接输出
    • 第二部分:只处理那些后面紧跟r的s节点,通过generate-id判断r节点的最近前置s就是当前节点,确保只匹配属于该s的后续r,不会跨组
    • 第三部分:直接输出那些后面没紧跟r的s节点
  2. 避免错误匹配:原代码中//i会全局搜索符合条件的r,导致每个<wrap>都塞入无关节点。现在用following-sibling结合generate-id精准限定范围,每个<wrap>只包含当前s和它对应的后续r节点。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 01:20:20