Re: [xsl] Sequential numbers in pure xslt, breaking the no-side-effect rule

Subject: Re: [xsl] Sequential numbers in pure xslt, breaking the no-side-effect rule
From: "Dimitre Novatchev" <dnovatchev@xxxxxxxxx>
Date: Fri, 16 Mar 2007 10:16:29 -0700
It is possible to do whatver in XSLT.

Here is an example how to write any XSLT transformation (the function
f:transform) that will have the ability to get the next "tick" (next
consecutive sequence number) at any time.

The transformation will only need (this can be done almost entirely in
a wrapper) to surround its oun output with two "coventional outputs"
-- the new tick count and the stop condition for the transformation.

Every "tick" corresponds to a single iteration as carried out by f:iterUntil().

 f:iterUntil() (yes, that same FXSL function) iterates whatever
function is passed as its second argument until the stop condition
(the predicate passed as its first argument) evaluates to true().

This is a very generic approach. *Any* transformation can be
substituted for the body of f:transform().

The only discipline is the layout of the results and to start a new
iteration (for example whenever it thinks something significant has
happened that needs to increment the tick counter) by returning with a
false() stop condition.

or stopping iself by returning a true() stop condition.

Here's a small demo:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:xs="http://www.w3.org/2001/XMLSchema";
xmlns:f="http://fxsl.sf.net/";
exclude-result-prefixes="f xs">

<xsl:import href="../f/func-iter.xsl"/>

<!-- To be applied on any xml file, or with initial template named
"initial" -->

<xsl:output omit-xml-declaration="yes"/>

<xsl:variable name="vExecutionLimit" as="xs:integer"
              select="100"/>
<xsl:variable name="vNL" select="'&#xA;'"/>

<xsl:template name="initial" match="/">

   <xsl:variable name="vRawResults" select=
   "f:iterUntil(f:last(), f:transform(), (0,false()))"/>

Results:

   Last tick: <xsl:sequence select="$vRawResults[1]"/>
   Stop condition:  <xsl:sequence select="$vRawResults[last()]"/>

   Transform output:
<xsl:text/>
    <xsl:sequence select=
        "for $i in 2 to count($vRawResults) -1
             return $vRawResults[$i]"
    />

</xsl:template>

 <xsl:function name="f:transform" as="item()+">
   <xsl:param name="pWorld" as="item()+"/>

<xsl:variable name="vnewTick" select="$pWorld[1]+1"/>

   <xsl:variable name="vmyResults" as="item()*">
     <!-- Do whatever processing is needed here -->
     <!-- and produce our "useful" results      -->

     <xsl:sequence select=
         " remove(remove($pWorld, count($pWorld)),1),
          $vnewTick, '   ', 3*$vnewTick, $vNL"/>

     <!-- Set the stop condition in the last item -->
     <xsl:sequence select=
      "if($vnewTick gt $vExecutionLimit)
         then true()
         else false()
      "/>
   </xsl:variable>

   <!-- Produce the results                        -->
   <!-- The "tick" is always the 1st item returned -->

   <xsl:sequence select="$vnewTick, $vmyResults"/>
 </xsl:function>

 <xsl:function name="f:transform" as="element()">
   <f:transform/>
 </xsl:function>

 <xsl:template match="f:transform" as="item()+" mode="f:FXSL">
   <xsl:param name="arg1" as="item()+"/>

   <xsl:sequence select="f:transform($arg1)"/>
 </xsl:template>

 <xsl:function name="f:last" as="xs:boolean">
   <xsl:param name="arg1" as="item()*"/>

   <xsl:sequence select="xs:boolean($arg1[last()])"/>
 </xsl:function>

 <xsl:function name="f:last" as="element()">
   <f:last/>
 </xsl:function>

 <xsl:template match="f:last" as="xs:boolean" mode="f:FXSL">
   <xsl:param name="arg1" as="item()*"/>

   <xsl:sequence select="f:last($arg1)"/>
 </xsl:template>
</xsl:stylesheet>


In this case I show a transformation, which can use the tick count at any point. To illustrate doing some useful work, it produces the tick count multiplied by 3.

The stop condition is: exceeding 100 iterations.

The final output produced is:




Results:


   Last tick: 101
   Stop condition:  true

   Transform output:
1     3
2     6
3     9
4     12
5     15
6     18
7     21
8     24
9     27
10     30
11     33
12     36
13     39
14     42
15     45
16     48
17     51
18     54
19     57
20     60
21     63
22     66
23     69
24     72
25     75
26     78
27     81
28     84
29     87
30     90
31     93
32     96
33     99
34     102
35     105
36     108
37     111
38     114
39     117
40     120
41     123
42     126
43     129
44     132
45     135
46     138
47     141
48     144
49     147
50     150
51     153
52     156
53     159
54     162
55     165
56     168
57     171
58     174
59     177
60     180
61     183
62     186
63     189
64     192
65     195
66     198
67     201
68     204
69     207
70     210
71     213
72     216
73     219
74     222
75     225
76     228
77     231
78     234
79     237
80     240
81     243
82     246
83     249
84     252
85     255
86     258
87     261
88     264
89     267
90     270
91     273
92     276
93     279
94     282
95     285
96     288
97     291
98     294
99     297
100     300
101     303


Conclusion:


 This is trivial (and actually quite boring) to achieve in a
functional language. To quote Simon Peyton-Jones, "Haskell is the
finest imperative language"   :o)




-- Cheers, Dimitre Novatchev --------------------------------------- Truly great madness cannot be achieved without significant intelligence. --------------------------------------- To invent, you need a good imagination and a pile of junk ------------------------------------- You've achieved success in your field when you don't know whether what you're doing is work or play





On 3/16/07, Abel Braaksma <abel.online@xxxxxxxxx> wrote:
Hi XSLT'ers,

AFAIK the answer to this is a simple, short "no, cannot be done". But I
wanted to check it with the specialists to be sure ;)

The request is simple: create a function that returns '1' on first call,
and returns one higher each next call, without overlaps or jumps,
without any input. Let's call it my:seq-number:

<xsl:function name="my:seq-number" as="xs:integer">
   <!-- some implementation -->
</xsl:function>

then the following line would return '1 2 3':
<xsl:value-of select="my:seq-number(), my:seq-number(), my:seq-number()" />

There are three scenarios to resolve this issue:

 1. Use an extension function in the host language (java, .net)
 2. Use an extension instruction like saxon:assign
 3. Use 'hidden features' of generate-id().

The first two are obvious. The last one is the best I good get with
"pure xslt", but as I tested, does not work with AltovaXML (in fact, it
is not even guaranteed to work on other or future versions of Saxon):

<xsl:function name="my:seq:number" as="xs:integer">
   <xsl:variable name="node"><xsl:comment /></xsl:variable>
   <xsl:sequence select="xs:integer(substring(generate-id($node), 2) -
1" />
</xsl:function>

this will indeed return 1,2,3 etc, provided that, depending on the order
of execution, the first node that is auto-created is this node, and that
no other nodes are created meanwhile. (FYI, Saxon numbers document nodes
as d1, d2 etc, where 'd1' is the input doc).

Doing this with AltovaXML generates too much randomness, i.e., the
output of generate-id is: idroot219951944 idroot219959376 etc (this is
not a non-comformance bug. Not being able to deal with empty
<xsl:comment /> is, however, but I am getting adrift...).

Am I overlooking something, or is this about the best I can get? I know
I am in violation and that requesting for functions/variables/templates
that return different results with the same input is asking for breaking
the rules.

Thanks for any extra insights on an already overly discussed subject,

Cheers,
-- Abel Braaksma

Current Thread