如何用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>
代码说明
拆分逻辑更清晰:把输出拆成三个独立部分,不用在循环里反复判断每个节点的处理方式,减少出错概率:
- 第一部分:直接筛选出所有没被
s前置的r节点,直接输出 - 第二部分:只处理那些后面紧跟
r的s节点,通过generate-id判断r节点的最近前置s就是当前节点,确保只匹配属于该s的后续r,不会跨组 - 第三部分:直接输出那些后面没紧跟
r的s节点
- 第一部分:直接筛选出所有没被
避免错误匹配:原代码中
//i会全局搜索符合条件的r,导致每个<wrap>都塞入无关节点。现在用following-sibling结合generate-id精准限定范围,每个<wrap>只包含当前s和它对应的后续r节点。
内容的提问来源于stack exchange,提问作者Cipy
相关产品推荐
相关产品推荐

