XSLT 2.0 Xpath错误 "not a node item".

3

这是我的第一个帖子 - 希望有人能知道答案!我已经找到了许多问题的解决方案,但对于这个问题还没有。

我有一个包含格式规范的XML文件,像这样:

<Format>
    <TagNr>92</TagNr>
    <Option>A</Option>
    <Format>//[N]15d</Format>
</Format>
<Format>
    <TagNr>92</TagNr>
    <Option>B</Option>
    <Format>//3!a/3!a/15d</Format>
</Format>

TagNr + option是此节点集中的唯一组合。

我定义了一个关键字,以使使用格式集更加容易:

<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>

我确实可以使用这个键并获得正确的值,但只能在非特殊元素中使用;当在for-each或其他类似下面的结构中使用该键时,会出现“XPath 2.0表达式中的错误:不是节点项”的错误。

我想要实现的是:在处理其他节点时,有一串选项字符串,我希望检索每个字符的格式。 例如:

<Tag>
    <TagNr>92</TagNr>
    <Options>AB</Options>
</Tag>

我尝试了很多下面的变体,但没有成功:

<xsl:variable name="TN"><xsl:value-of select="TagNr"/></xsl:variable>
<xsl:variable name="optList">
    <xsl:analyze-string select="./Options" regex="[A-Z]">
        <xsl:matching-substring>
            <xsl:variable name="TNO" select="concat($TN, .)"/>
            <opt>
                <tag><xsl:value-of select="$TNO"/></tag>
                <fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
            </opt>
        </xsl:matching-substring>
    </xsl:analyze-string>
</xsl:variable>

使用正则表达式将文本拆分成单个字符时,获取(仅)opt/tag值也很好。 但是当我添加opt/fmt时,对于Xpath表达式 select="key('xx', $TNO)" 我遇到了上述错误信息。

我尝试按照该网站的另一个帖子建议,基于key函数定义变量,但没有成功。

有人能帮帮我吗?

3个回答

5

key()函数(带有两个参数)搜索包含上下文节点的文档。如果上下文项目不是节点 - 例如,在analyze-string内部 - 那么您会收到此错误,因为它不知道要搜索哪个文档。答案是使用key()的第三个参数来提供这些信息。


非常感谢你们的帮助,真是太棒了。我想知道如何指定键函数的第三个参数 - 是否需要使用“/”来表示文档根目录?我在XSLT 2.0指南中找不到相关信息。 - Maestro13

1
问题在于您的analyze-string元素中上下文发生了变化。也许以下解决方案可以帮助您。

对于这样的XML文件:

<a>
    <Format>
        <TagNr>92</TagNr>
        <Option>A</Option>
        <Format>//[N]15d</Format>
    </Format>
    <Format>
        <TagNr>92</TagNr>
        <Option>B</Option>
        <Format>//3!a/3!a/15d</Format>
    </Format>
    <Tag>
        <TagNr>92</TagNr>
        <Options>AB</Options>
    </Tag>
</a>

考虑以下 XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    <xsl:output indent="yes"/>
    <xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
    <xsl:template match="/">
        <result>
            <xsl:apply-templates select="//Tag"/>            
        </result>
    </xsl:template>
    <xsl:template match="Tag">
        <xsl:call-template name="createOPT">
            <xsl:with-param name="str" as="xs:string" select="Options"/>
        </xsl:call-template>                
    </xsl:template>
    <xsl:template name="createOPT">
        <xsl:param name="str"/>
        <xsl:if test="string-length($str) > 0">
            <xsl:variable name="firstChar" select="substring($str,1,1)"/>
            <xsl:variable name="TNO" select="concat(TagNr,$firstChar)"/>
            <opt>
                <tag><xsl:value-of select="$TNO"/></tag>
                <fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
            </opt>
            <xsl:call-template name="createOPT">
                <xsl:with-param name="str" select="substring($str,2)"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

结果是:

<?xml version="1.0" encoding="UTF-8"?>
<result>
   <opt>
      <tag>92A</tag>
      <fmt>//[N]15d</fmt>
   </opt>
   <opt>
      <tag>92B</tag>
      <fmt>//3!a/3!a/15d</fmt>
   </opt>
</result>

谢谢 - 这确实让我再次上手了!还为我提供了一个逐个字符分割 char 的方法,这本来就是我想做的。不过有一个问题:为什么上下文会丢失?另一篇帖子中提到了更优雅的解决方案:定义变量以恢复上下文,为什么没有使用呢? - Maestro13
您也可以使用变量来恢复上下文。也许这更加优雅。我个人的看法是,analyze-string 使用和维护都有些棘手,如果可能的话,我更喜欢避免使用它。 - Vincent Biragnet
1
上下文并没有丢失,然而在analyze-string函数内部它不是一个节点,而是一个原子项,即当前正在处理的(子)字符串。 - Martin Honnen
感谢马丁指出这一点。我是指以前的上下文。我会更新的。 - Vincent Biragnet

1

处理字符串的最简单的XSLT 2.0方法是以下内容

<xsl:for-each select="string-to-codepoints($vStr)">
 <xsl:variable name="$vChar" select=
     "codepoints-to-string(.)"/>

 <!-- Process $vChar here: -->
</xsl:for-each/>

您可以将此与将原始文档上下文保存到变量(例如$vDoc)中相结合,并使用此变量作为key()函数的第三个参数——这又是一个仅适用于XSLT 2.0的特性。

因此,您将拥有类似以下的内容:

key('xx', concat($TN, $vChar), $vDoc)

摘要:

  1. 使用string-to-codepoints()codepoints-to-string()函数进行逐字符处理。

  2. 使用key()函数的第三个参数来指定与当前上下文不同的上下文。


那么,“将原始文档上下文保存到变量中”是如何实现的呢? - Maestro13
@Maestro13: 就像这样:<xsl:variable name="vDoc" select="/>。 这个变量定义可以是全局的(xsl:stylesheet 的子元素)。 - Dimitre Novatchev
@Maestro13:我的回答(包括对你评论的回复)有用吗?你还有问题吗? - Dimitre Novatchev
我为我的问题想出了另一个解决方案(将选项值列表保存到变量中,使用analyze-string,然后将列表与包含格式的另一个变量一起传递给命名模板)。但是每当我需要进行三个参数键函数调用时,我肯定会尝试您的建议。 - Maestro13
作为测试,我按照建议使用了键函数的第三个参数,结果完美。我仅保留了analyze-string部分,而没有来回替换您建议的代码点,因为我认为使用analyze-string更清晰明了。 - Maestro13
@Maestro13:我非常尊重您的意见。xsl:analyze-string更为复杂,适用于复杂处理。我的解决方案只是从字符串中创建一个字符序列,然后使用xsl:for-each以非常便捷的方式处理该序列。最后,结果(在本例中是每个单独的字符)被转换回字符串并使用。这样做有以下好处:1. 更高效(因为更简单)。2. 更易于理解、记忆和在其他情况下使用。3. 非常通用,可以作为设计模式记忆和使用。4. 可以并行执行。 - Dimitre Novatchev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接