XPath/XSLT技术问询:如何将特定兄弟节点嵌套至新元素
解决XSLT成对标记包裹内容的转换问题
要实现把<font>start</font>和<font>stop</font>之间的所有内容嵌套到<hi>元素里,同时支持单个<p>里的多组结构,关键是精准选中start之后到对应stop之前的所有节点,下面是具体的实现方案:
完整XSLT代码(适配XPath 2.0及以上,比如Saxon、Xalan 2.x)
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 核心identity模板:复制所有未被专门匹配的节点和属性 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 匹配start标记,替换为hi元素并包含中间内容 --> <xsl:template match="font[text()='start']"> <hi> <!-- 选中当前start之后、第一个stop之前的所有兄弟节点 --> <xsl:apply-templates select="following-sibling::node()[. << following-sibling::font[text()='stop'][1]]"/> </hi> </xsl:template> <!-- 匹配stop标记,直接忽略不输出 --> <xsl:template match="font[text()='stop']"/> </xsl:stylesheet>
兼容XPath 1.0的版本(比如旧版Xalan、MSXML)
如果你的XSLT处理器只支持XPath 1.0,把start标记的模板改成下面的写法:
<xsl:template match="font[text()='start']"> <hi> <!-- 通过计数stop标记的数量来精准选中对应范围的节点 --> <xsl:apply-templates select="following-sibling::node()[count(preceding-sibling::font[text()='stop']) = count(current()/preceding-sibling::font[text()='stop'])]"/> </hi> </xsl:template>
关键逻辑说明
- Identity模板:这是XSLT转换的基础,它会原样复制所有没有被我们专门模板匹配的节点、属性和文本,保证除了start/stop标记和中间包裹逻辑外,其他内容完全保留。
- Start标记处理:
- 我们把
<font>start</font>替换成<hi>元素。 - 用
following-sibling::node()选中start之后的所有兄弟节点,再通过条件过滤出到第一个stop标记之前的内容:- XPath 2.0+用
<<运算符判断节点顺序,. << following-sibling::font[text()='stop'][1]表示当前节点在第一个stop标记之前。 - XPath 1.0通过统计每个节点前面的stop标记数量,和当前start前面的stop数量对比,确保选中的是当前start对应的stop之前的内容,完美支持多组嵌套结构。
- XPath 2.0+用
- 我们把
- Stop标记处理:直接匹配后不输出任何内容,避免它出现在最终结果里。
测试效果
比如输入包含多组标记的内容:
<p> This is <font>start</font> just some <i>happy</i> text <font>stop</font> that might continue here. And another <font>start</font> <b>bold</b> part <font>stop</font> too. </p>
转换后会得到:
<p> This is <hi> just some <i>happy</i> text </hi> that might continue here. And another <hi> <b>bold</b> part </hi> too. </p>
内容的提问来源于stack exchange,提问作者J. Schäuble
相关产品推荐
相关产品推荐

