使用fn:path()选取XML元素:跨h1节点同位置文本替换问题
解决XSLT中节点对应位置文本合并及路径字符串转节点集问题
核心需求
你需要将XML中h1 id="2"及后续h1节点里的文本,合并到h1 id="1"下的同层级节点中,最终得到合并后的文本节点。之前尝试用fn:path()生成路径字符串后无法直接当作节点选择器使用,问题在于字符串不能直接被XSLT解析为节点路径。
推荐方案:基于节点层级定位(无需字符串处理)
不用折腾路径字符串替换,直接通过节点的层级关系定位对应节点更可靠,也更易维护:
- 处理
h1 id="1"下的目标节点时,先获取该节点相对于父h1的层级结构(即从h1子节点到目标节点的每一层节点名称和位置)。 - 对所有其他
h1节点,按照这个层级结构找到对应的节点。
完整XSLT示例
针对你的XML结构,以下XSLT可以直接实现需求:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" /> <!-- 匹配h1 id="1"下的p节点,处理文本合并 --> <xsl:template match="h1[@id='1']//p"> <p> <!-- 保留原节点文本 --> <xsl:value-of select="text()" /> <!-- 遍历所有其他h1节点,获取对应位置的p文本 --> <xsl:for-each select="/h1[@id!='1']"> <xsl:variable name="target-h1" select="." /> <!-- 逐层定位对应节点:从h1的子节点开始,复刻原节点的层级路径 --> <xsl:variable name="target-p" select="$target-h1"> <xsl:for-each select="current()/ancestor-or-self::node()[. >> current()/ancestor::h1[@id='1']]"> <xsl:variable name="node-name" select="local-name()" /> <xsl:variable name="node-pos" select="count(preceding-sibling::*[local-name()=$node-name]) + 1" /> <xsl:sequence select="current()[local-name()=$node-name][$node-pos]" /> </xsl:for-each> </xsl:variable> <!-- 拼接文本,用/分隔 --> <xsl:value-of select="concat('/', $target-p/text())" /> </xsl:for-each> </p> </xsl:template> <!-- 忽略不需要输出的节点和文本 --> <xsl:template match="text()[not(parent::p)]" /> <xsl:template match="*[not(ancestor-or-self::h1[@id='1']//p)]"> <xsl:apply-templates /> </xsl:template> </xsl:stylesheet>
将这个XSLT应用到你的XML上,会输出期望的结果:
<p>text/translated text</p>
备选方案:用XSLT 3.0的xsl:evaluate解析路径字符串
如果你使用支持XSLT 3.0的处理器(比如Saxon),可以直接把路径字符串转换成节点集。调整你的路径替换逻辑后,用xsl:evaluate执行路径:
<xsl:variable name="original-path" select="path()" /> <!-- 替换路径中的h1[1]为目标h1的位置,比如h1[2] --> <xsl:variable name="target-path" select="replace($original-path, '/h1\[1\]', '/h1[2]')" /> <xsl:variable name="target-node" as="node()"> <xsl:evaluate xpath="$target-path" context-item="/" /> </xsl:variable> <!-- 之后就可以使用$target-node获取对应节点的文本了 -->
注意要处理好路径中的特殊字符(比如方括号转义),以及命名空间的问题(如果你的XML有命名空间,路径里的前缀要对应)。
内容的提问来源于stack exchange,提问作者Hobbes
相关产品推荐
相关产品推荐

