【问题标题】:xslt key() function returning false on valid node?xslt key() 函数在有效节点上返回 false?
【发布时间】:2021-10-31 17:29:28
【问题描述】:

我在我的 xml 文件中使用 XSLT 1.0。对于其中一个更改,我必须先将节点值的子字符串替换为另一个字符串,然后对其调用key()

假设节点值为'alpha.beta',那么在使用str:replace() 之后按照thread 的建议,值更新为'alpha.gamma'。稍后我在其上调用key(),对于key() 中实际存在节点的情况返回false。

我尝试的示例让我怀疑 replace() 是否正在更改元素是有道理的:

Case1:
pass the string 'alpha.gamma' directly to the key()
key(some-defined-key, 'alpha.gamma') returns true

Case2:
update the variable var from 'alpha.beta' to 'alpha.gamma' and pass the variable
key(some-defined-key, $var) returns false

我在每一步都打印了节点的值,以确保节点的值得到正确更新。所以,

  1. xslt 1.0 中的 key() 函数是否存在某种缺陷?
  2. replace() 改变节点是否有某种可能性是有道理的?
  3. 有没有办法查明原因?

更新:

我试图对确切的场景进行原型设计,请多多包涵。首先,我有一个 xsl 文件,其中包含在 local-namespace 的帮助下存储的我想吃的水果列表:

本地命名空间.xsl:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet
    version="1.0"
    xmlns:lcl="my_local_namespace"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <lcl:fruits>
        <lcl:fruit>apple</lcl:fruit>
        <lcl:fruit>banana</lcl:fruit>
    </lcl:fruits>

    <xsl:variable name="fruits-list" select="document('')/xsl:stylesheet/lcl:fruits/lcl:fruit"/>

</xsl:stylesheet>

现在我有一个显示可用水果的 xml 文件,我们称之为 fruits.xml:

<fruits>
    <fruit-available>
        <fruit name="fresh-apple"></fruit>
        <fruit name="fresh-banana"></fruit>
    </fruit-available>
    <fruit-selector>fresh-${FRUIT-NAME}</fruit-selector>
</fruits>

现在,使用 fruit-selector 节点的值,我必须用我想吃的每个水果(即 lcl:fruit 元素)替换 ${FRUIT-NAME},如果 fruit-available 节点中不存在水果,那么生成一个错误标签,指出特定水果不可用。

fruits.xsl 文件:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet
    version="1.0"
    xmlns:lcl="my_local_namespace"
    xmlns:str="http://exslt.org/strings"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:import href="local-namespace.xsl"/>
    <xsl:import href="http://exslt.org/str/functions/replace/str.replace.function.xsl"/>

    <xsl:key name="fruitsKey" match="fruits/fruit-available/fruit" use="@name"/>

    <xsl:output indent="yes"/>
    <xsl:template match="text()"/>

    <xsl:template match="/">
        <Errors>
            <xsl:apply-templates/>
        </Errors>
    </xsl:template>

    <xsl:template name="CheckFruits">
        <xsl:param name="fruitElement"/>
        <xsl:param name="currentFruit"/>
        <xsl:variable name="selector" select="str:replace($fruitElement, '${FRUIT-NAME}', string($currentFruit))"/>
        <xsl:if test="not(key('fruitsKey', $selector))">
            <Error><xsl:value-of select="concat('Fruit element: ', $selector, ' is not available')"/></Error>
        </xsl:if>
    </xsl:template>

    <xsl:template match="fruits/fruit-selector">
        <xsl:variable name ="currentfruitElement" select="."/>
        <xsl:for-each select="$fruits-list">
        <xsl:call-template name="CheckFruits">
            <xsl:with-param name="fruitElement" select="$currentfruitElement"/>
            <xsl:with-param name="currentFruit" select="."/>
        </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

我怀疑key() 即使在有效的情况下也显示为假:

执行结果:

xsltproc --xinclude fruits.xsl fruits.xml
<?xml version="1.0"?>
<Errors xmlns:lcl="my_local_namespace" xmlns:str="http://exslt.org/strings">
  <Error>Fruit element: fresh-apple is not available</Error>
  <Error>Fruit element: fresh-banana is not available</Error>
</Errors>

注意: 我是一个初学者,所以即使错误很简单,我也有兴趣理解所涉及的概念。

【问题讨论】:

  • 请同时显示一个最小但完整的 XSLT 示例,以说明问题。一个键建立在一个输入文档上,任何中间结果在 XSLT 1.0 中首先是一个结果树片段。您可以将其转换为节点集,但它将是与输入文档不同的文档。我们确实需要了解您的密钥是如何声明的、如何应用更改以及在何处/如何使用密钥功能。
  • 如果不提供重现问题的方法,您只能期望猜测。我的猜测是,简单的解决方案是颠倒操作顺序:首先使用密钥获取您想要的节点,然后替换您想要替换的部分。 -- 在任何情况下,XSL 转换都对输入树进行操作——并且该树永远不会被转换修改。转换所做的只是将结果写入输出树。
  • 这不是“展示问题的最小但完整的 XSLT 示例”。
  • 附注:EXSLT str:replace() 函数的规范声明结果应该是节点集。我已经在exslt.org/str/functions/replace/str.replace.function.xsl 测试了实现,它确实符合。
  • 我们还需要知道您使用的是什么 XSLT 处理器。您正在调用的str:replace() 的实现调用func:function,它是规范的扩展,在某些处理器中的实现可能与在其他处理器中不同。通常,您在这里非常依赖处理器 - 如果您迁移到 XSLT 2.0,事情会更清楚。

标签: xml xslt


【解决方案1】:

看看下面的例子对你是否有意义:

XML

<fruits>
    <fruit-available>
        <fruit name="fresh-apple"/>
        <fruit name="fresh-banana"/>
    </fruit-available>
    <fruit-selector>fresh-${FRUIT-NAME}</fruit-selector>
</fruits>

XSLT 1.0 + EXSLT(假设是 libxslt 处理器)

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
xmlns:lcl="my_local_namespace"
exclude-result-prefixes="str lcl">

<xsl:import href="http://exslt.org/str/functions/replace/str.replace.function.xsl"/>

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<lcl:fruits>
    <lcl:fruit>apple</lcl:fruit>
    <lcl:fruit>pear</lcl:fruit>
    <lcl:fruit>banana</lcl:fruit>
</lcl:fruits>

<xsl:key name="available-fruit" match="fruit" use="@name"/>

<xsl:template match="/fruits">
    <xsl:variable name="xml" select="/" />
    <xsl:variable name="fruit-selector" select="fruit-selector" />
    <Errors>
        <xsl:for-each select="document('')/xsl:stylesheet/lcl:fruits/lcl:fruit">
            <xsl:variable name="key" select="str:replace($fruit-selector, '${FRUIT-NAME}', string(.))"/>
            <!-- switch context back to XML in order to use key -->
            <xsl:for-each select="$xml">
                <xsl:if test="not(key('available-fruit', $key))">
                    <Error>
                        <xsl:value-of select="$key"/>
                    </Error>
                </xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </Errors>
</xsl:template>

</xsl:stylesheet>

结果

<?xml version="1.0" encoding="UTF-8"?>
<Errors>
  <Error>fresh-pear</Error>
</Errors>

【讨论】:

  • 我知道了基本的想法,你能用一些解释来结束这个,以避免类似的事情吗?或者不应该在xslt 中进行基本检查,以提供WARNING,您在错误的上下文中调用key()
  • 除了知道自己在做什么之外,我不知道您如何避免类似的事情。 XSLT 无法知道您在“错误的”上下文中调用了键,因为它不是读心器,并且不知道您在概念级别上在做什么。调用一个键并得到一个空的节点集作为结果没有什么不寻常的。即使在您自己的示例中,您也可能会收到一个没有任何可用水果与您的硬编码水果相匹配的 XML。毕竟,这就是您首先进行转型的原因,不是吗?
  • @DonBaka XSLT 2.0 通过添加定义要搜索的文档的第三个参数来识别 key() 函数设计中的问题。遗憾的是 (a) 您无法对规范进行追溯更改,并且 (b) 许多像您这样的用户仍在使用 1999 年的原始 1.0 标准,其中存在所有缺陷。
  • 你好,我在这里发了一个问题,你能快速看看:unix.stackexchange.com/questions/687831/…
  • @DonBaka 请不要那样做。
【解决方案2】:

我不确定您做错了什么,因为您没有提供所有代码,但是您需要了解key() 在有多个文档时如何工作。 str:replace() 函数在新构造的文档中返回新构造的节点。 key() 函数只搜索包含上下文节点的文档。它不会搜索由str:replace() 创建的文档,除非上下文节点是该文档中的节点。

您的陈述“我在每一步都打印了节点的值,以确保节点的值得到正确更新”,我也很担心。我不知道你到底做了什么,但你写这篇文章的方式表明你还没有完全理解节点是不可变的。在 XSLT 中,节点永远不会更新;您通过创建修改后的副本来执行转换。

【讨论】:

    猜你喜欢
    • 2011-12-21
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多