关于xslt:使用fn:random-number-generator多次产生随机数

Using fn:random-number-generator to produce random numbers more than once

我尝试编写一个简单的函数来在每次调用它时为我提供一个随机字母,但是我很难将我的想法与功能编程方法的概念结合起来。一路上的一些帮助将不胜感激!
我的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<xd:doc>
        <xd:desc>Provides one random letter, if the type is provided it returns a letter of thet type</xd:desc>
        <xd:param name="type">The type of letter to return, one of (A,a,B,b)</xd:param>
    </xd:doc>
    <xsl:function name="gdpr:randomLetter" as="xs:string">
        <xsl:param name="type" as="xs:string"></xsl:param>
        <xsl:choose>
            <xsl:when test="$type = 'A'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'a'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'B'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'b'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 52)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:function>

您的问题包含以下问题:

I try to write a simple function to provide me with a random letter
each time i call it

但是在不同的调用(使用相同的参数)上产生不同结果的函数不是真正的(" pure")函数。

一种解决方法是利用XSLT已经具有某种"不纯"函数的事实:创建新节点的函数每次都会返回一个不同的节点,您可以使用generate-id( )。所以你可以写

1
2
3
4
<xsl:function name="my:random" as="xs:double">
  <xsl:variable name="dummy"></xsl:variable>
  <xsl:sequence select="fn:random-number-generator(generate-id($dummy))?permute(1 to 10)"/>
</xsl:function>

唯一的问题是,您正处于规范中定义良好的边界上,优化器可能无法让您摆脱这些trick俩。如果可以的话,最好找到一种在每次调用该函数时将不同的参数传递给该函数的方法:例如,将序列号或generate-id()应用于当前正在处理的输入节点。


在XSLT 3的上下文中,我认为为您需要的每个节点创建一个"新" random-number-generator的一种方法是定义一个累加器:

1
2
3
<xsl:accumulator name="rng" as="map(xs:string, item())" initial-value="random-number-generator(current-dateTime())">
    <xsl:accumulator-rule match="foo[@type]" select="$value?next()"/>
</xsl:accumulator>

这样,您就可以将功能实现为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<xsl:function name="gdpr:randomLetter" as="item()*">
    <xsl:param name="type" as="xs:string"/>
    <xsl:param name="rng" as="map(xs:string, item())"/>
    <xsl:choose>
        <xsl:when test="$type = 'A'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'a'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'B'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'b'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 52)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:otherwise>
    </xsl:choose>        
</xsl:function>

,然后用例如

进行调用

1
2
3
4
5
<xsl:template match="foo[@type]">
    <xsl:copy>
        <xsl:value-of select="gdpr:randomLetter(@type, accumulator-before('rng'))"/>  
    </xsl:copy>
</xsl:template>

,并确保您使用

1
<xsl:mode on-no-match="shallow-copy" use-accumulators="rng"/>

为了完整起见,我想出了这个解决方案,但是由于递归的深度,它仅适用于小块文本。

在一个旁注中,我意识到我的解决方案是浪费时间,因为我在XSLT实现中使用了不包含random-number-generator的exist-db。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<xsl:function name="gdpr:rngRecurseStart">
     <xsl:param name="text"></xsl:param>
     <xsl:variable name="chars" select="functx:chars($text)"/>

     <xsl:copy-of select="gdpr:rngRecurse($chars,random-number-generator(current-dateTime()),'')"></xsl:copy-of>
 </xsl:function>

 <xsl:function name="gdpr:rngRecurse">
     <xsl:param name="chars"></xsl:param>
     <xsl:param name="rngGenerator"></xsl:param>
     <xsl:param name="newText"></xsl:param>
     <xsl:variable name="curentchar" select="$chars[1]"></xsl:variable>
     <xsl:variable name="newRngGenerator" select="$rngGenerator?next()"/>
     <xsl:choose>
         <xsl:when test="count($chars) >1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="gdpr:rngRecurse(subsequence($chars,2),$newRngGenerator,$resultText)"></xsl:copy-of>
         </xsl:when>
         <xsl:when test="count($chars) =1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="$resultText"></xsl:copy-of>
         </xsl:when>
         <xsl:otherwise><xsl:copy-of select="$newText"></xsl:copy-of></xsl:otherwise>
     </xsl:choose>

 </xsl:function>


问题在于fn:random-number-generator函数是确定性的。规范本身说明:

Both forms of the function are ·deterministic·: calling the function
twice with the same arguments, within a single ·execution scope·,
produces the same results.

您需要通过调用random-number-generator函数来正确使用结果映射中包含的键next下的函数。就像规范说的那样:

The entry with key"next" is a zero-arity function that can be called
to return another random number generator.