
本文详解如何在 xslt 2.0+ 中通过 `group-adjacent` 配合 `replace()` 函数,将语义相关但 class 名称不同的相邻 html 元素(如 `warning` 和 `warninglistbullet`)合并到同一 `
在实际 HTML 结构化处理中,常遇到一类典型需求:多个视觉/语义上属于同一逻辑区块的元素,却因历史原因或 CMS 输出策略被赋予了不同但高度相关的 class 名称(例如 warning、warninglistbullet)。此时若直接按 @class 值分组,它们会被拆散;而简单用布尔表达式(如 @class = 'warning' or @class = 'warninglistbullet')作为 group-adjacent 的分组键,则无法保证“相邻性”——因为该表达式对所有匹配元素返回相同布尔值,导致所有同类元素被强行归为一组,无论其在 DOM 中是否真正相邻。
正确解法是构造一个归一化的分组键:对原始 @class 值进行预处理,将语义等价的 class 映射为同一标识符。XSLT 2.0+ 提供的 replace() 函数正适用于此场景:
<xsl:template match="section">
<xsl:copy>
<!-- 对每个带 class 的子元素,将 'warninglistbullet' 统一替换为 'warning' 作为分组键 -->
<xsl:for-each-group select="*[@class!='']"
group-adjacent="replace(@class, 'warninglistbullet', 'warning')">
<xsl:choose>
<!-- 当归一化后的 class 是 'parts' 或 'warning' 时,包裹进对应 div -->
<xsl:when test="current-grouping-key() = ('parts', 'warning')">
<div class="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:when>
<!-- 其他 class(如 heading1、bodytext)保持原样输出 -->
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>该方案的关键在于:
- group-adjacent="replace(@class, 'warninglistbullet', 'warning')" 确保了 warninglistbullet 元素在分组阶段与 warning 元素拥有完全相同的分组键 'warning';
- current-grouping-key() = ('parts', 'warning') 是 XSLT 2.0+ 支持的序列成员判断语法,简洁高效地覆盖多类目标;
- <xsl:copy> 替代 <xsl:element name="{local-name()}"> 更安全,能自动继承命名空间与属性;
- 未显式匹配的元素(如 <h1 class="heading1">)由内置模板默认处理,无需额外声明。
⚠️ 注意事项:
- 此方法依赖 XSLT 2.0 或更高版本(replace() 和序列比较 = 均非 XSLT 1.0 特性);
- 若存在更多需归并的 class(如 warningicon, warningfooter),可嵌套 replace() 或改用 if/else 表达式:
if (@class = ('warning', 'warninglistbullet', 'warningicon')) then 'warning' else @class; - group-adjacent 严格按文档顺序分组,仅合并物理相邻且归一化键相同的连续节点,完全符合“相邻归组”语义。
通过这种键值归一化策略,我们既保留了原始 HTML 的结构完整性,又实现了语义驱动的智能容器封装,为后续 CSS 样式统一、无障碍增强或内容提取奠定了坚实基础。









