XSLT 2.0/3.0 遍历tokenize属性结果时上下文报错问题
问题根因
报错的核心是上下文项类型不匹配:
- 执行
xsl:for-each select="tokenize(.,'\s')时,循环内部的上下文项是tokenize返回的原子字符串值,不是XML节点。 - XPath中以
/开头的绝对路径,要求上下文项必须是节点(用于锚定所属文档的根节点),上下文为字符串时无法解析根路径,就会抛出Leading '/' selects nothing: the context item is not a node错误。 - 之前用
xsl:key无返回结果,是因为key函数默认在当前上下文项所属的文档内查找匹配节点,在字符串上下文下调用时没有文档锚点,自然查不到结果。
可落地方案
方案1:保留节点锚点修复原逻辑
如果不想用key,可在进入分词循环前提前缓存文档根节点,循环内用缓存的根作为XPath起点,避免依赖字符串上下文解析路径:
<xsl:template match="@cautionRefs | @warningRefs"> <!-- 此时上下文还是属性节点,可正常获取所属文档根 --> <xsl:variable name="docRoot" select="root()"/> <!-- 分词时用\s+匹配多空格,加过滤条件跳过空值,兼容首尾/连续空格场景 --> <xsl:for-each select="tokenize(., '\s+')[normalize-space()]"> <!-- 用current()取当前循环的分词ID,不要直接写.,避免和XPath步骤内的上下文节点混淆 --> <xsl:apply-templates select="$docRoot//*[@id = current()]"/> </xsl:for-each> </xsl:template>
方案2:正确使用key提升性能(推荐,大文档下效率远高于全文档遍历)
你之前定义key的逻辑本身没问题,只要在节点上下文下调用key、传入分词后的ID序列即可,不需要额外写for-each循环:
<!-- 全局key定义,放在xsl:stylesheet根节点下、与模板同级 --> <xsl:key name="elemById" match="*" use="@id"/> <xsl:template match="@cautionRefs | @warningRefs"> <!-- 此时上下文是属性节点,key可正常锚定当前文档,直接传入分词后的ID序列即可批量匹配节点 --> <xsl:apply-templates select="key('elemById', tokenize(., '\s+')[normalize-space()])"/> </xsl:template>
key函数支持传入序列作为第二个参数,会自动返回所有匹配任意ID的节点,直接触发apply-templates后,就会自动匹配你已经写好的cautionRef、warningRef模板,拉取对应的声明内容输出,效果和你手写逐个ID调用apply-templates完全一致。
注意事项
- 分词正则不要用单个
\s,否则遇到连续多个空格会拆分出空字符串,导致匹配无效ID,用\s+加空值过滤的写法兼容性更好。 - 循环内取当前分词值必须用
current(),如果直接写$docRoot//*[@id = .],这里的.代表XPath遍历过程中的当前元素节点,不是外层分词循环的字符串值,会导致匹配逻辑错误。
内容的提问来源于stack exchange,提问作者Caroline
相关产品推荐
相关产品推荐

