XSLT函数返回不同的结果[Saxon-EE与Saxon-HE/PE]

9
我目前正在使用不同版本的Saxon处理器进行纯XSL转换工作。下面是我的简短样式表,为了回答问题而简化了它:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:foo="bar">

    <xsl:output encoding="UTF-8" method="text"/>

    <xsl:template match="/">
        <xsl:text>Call of func_1: </xsl:text>        
        <xsl:value-of select="foo:func_1()"/>

        <xsl:text>&#xA;Call of func_1: </xsl:text>
        <xsl:value-of select="foo:func_1()"/>

        <xsl:text>&#xA;Call of func_1: </xsl:text>
        <xsl:value-of select="foo:func_1()"/>

        <xsl:text>&#xA;Call of func_2: </xsl:text>
        <xsl:value-of select="foo:func_2()"/>
    </xsl:template>

    <xsl:function name="foo:func_1" as="xs:string">
        <!-- do some other stuff -->
        <xsl:value-of select="foo:func_2()"/>
    </xsl:function>

    <xsl:function name="foo:func_2" as="xs:string">
        <xsl:variable name="node">
            <xsl:comment/>
        </xsl:variable>
        <xsl:sequence select="generate-id($node)"/>
    </xsl:function>

</xsl:stylesheet>

描述:
`foo:func_1` 是一个包装函数,用于返回第二个函数的值并执行其他操作(可以忽略)。这种函数调用其他函数的概念是强制性的!
`foo:func_2` 为元素生成唯一的 id。该元素在名为“node”的本地作用域变量中创建。
基于 Saxon 版本的不同结果
预期结果:
Call of func_1: d2
Call of func_1: d3
Call of func_1: d4
Call of func_2: d5

Saxon-EE 9.6.0.7 / Saxon-EE 9.6.0.5 结果
Call of func_1: d2
Call of func_1: d2
Call of func_1: d2
Call of func_2: d3

Saxon-HE 9.6.0.5 / Saxon-PE 9.6.0.5 / Saxon-EE 9.5.1.6 / Saxon-HE 9.5.1.6 结果
like expected

问题 / 更深入的了解
我已经尽力自己调试了这个问题。如果我在函数“func_1”中将 xsl:value-of 改为 xsl:sequence,那么所有版本的结果都将相同[如预期]。但这不是我的意图!
我想了解,在 Saxon 版本中 xsl:value-ofxsl:sequence 之间有什么区别。是否有任何“隐藏”的缓存?在我的情况下,使用 xsl:sequencexsl:value-of 的正确方法是什么。[顺便说一句:我已经知道,value-of 创建一个带有 select 语句结果的文本节点。sequence 可以是对节点或原子值的引用。就我所知,这不能解决我的问题]

1
有趣的问题。但我不明白为什么你要编写声明返回字符串的函数,并使用 as="xs:string",然后使用 xsl:value-of 返回文本节点(这个节点必须转换为字符串以匹配 as 声明)。 - Martin Honnen
1
使用Saxon 9.7 EE,如果我从命令行中使用opt:0关闭任何优化,则每次调用的结果都会有不同的ID。因此,看起来EE正在执行一些更改结果的优化。 - Martin Honnen
1
我认为XSLT 3.0试图通过new-each-time属性解决https://www.w3.org/TR/xslt-30/#function-determinism中的问题。 - Martin Honnen
1个回答

3
这是一个长期存在的而且相当深入的问题。在纯函数式语言中,使用相同参数两次调用纯函数总是产生相同的结果。这使得许多优化成为可能,例如,如果参数不变,则从循环中拉出函数调用,或者如果它不是递归的,则内联函数调用。不幸的是,XSLT和XQuery函数并不完全是纯函数式的:特别是,它们被定义为如果函数创建新节点,则两次调用该函数会产生不同的节点(f() is f() 返回 false)。
Saxon 优化器在这些限制下尽力进行优化,特别是通过识别创建新节点的函数并避免对这些函数进行激进的优化。
但规范本身并不是100%规定性的。例如,如果像您的示例中有一个局部变量没有依赖于函数参数,我认为规范授权实现决定变量的值在每次评估时是相同的节点还是新节点。
正如Martin所说,新的 XSLT 3.0 属性 new-each-time 是试图将其控制在范围内的一种尝试:如果您确实希望每次调用函数都获得新节点,则应指定 new-each-time="yes"
注意:
这里发生的具体优化(可以通过使用 -explain 选项查看)是首先内联 func_2,然后将其主体提取到全局变量中。有些版本正在执行此操作,而其他版本则不会——它可能非常敏感于细微的更改。最好的建议是不要依赖函数具有这种副作用。如果您解释了真正的问题,那么也许我们可以找到一种对语言语义边缘情况不太敏感的方法。

非常感谢您提供的深入见解。我已经考虑了一些处理器优化,例如提示缓存。 - uL1
我的真实情况是:我正在使用一个广泛传播的uuid.xsl(没有源代码,也不知道作者),用于在xslt中生成uuid;过去我无法使用任何java类,所以我使用了那个xsl。现在,我使用xmlns:uuid="java:java.util.UUID => uuid:randomUUID()。但对我来说,重要的是了解可能在未来再次面临的问题。我是否应该仍然开启一个新线程来讨论这个真实情况?值得吗?否则我会节省您宝贵的时间。 - uL1

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