Re: [xsl] RE: Designs for XSLT functions

Subject: Re: [xsl] RE: Designs for XSLT functions
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Wed, 21 Feb 2001 11:00:22 +0000
Mike Kay wrote:
>> > Saxon allows xsl:for-each with saxon:function, but doesn't allow
>> > saxon:return within xsl:for-each.
>> Did you see the use cases that I put forward where it could be
>> useful?:
>> What do you think?
> There's some logic to it: it suggests that <xsl:break> within an ordinary
> <xsl:for-each> could also be defined cleanly, as "discard any result nodes
> generated beyond this point". Could be useful:
> <xsl:template match="h1">
>   <section>
>     <xsl:for-each select="following-sibling::*">
>       <xsl:if test="h1"><xsl:break/></xsl:if>
>       <xsl:apply-templates/>
>     </xsl:for-each>
>   </section>
> </xsl:template>
> An alternative to saxon:leading(). I quite like it. I wouldn't like
> it if you could achieve this effect by writing a function and not
> achieve the same effect inline.

It would certainly help with that kind of grouping. The approximate
equivalent, of course, is:

<xsl:template match="h1">
                  [generate-id(following-sibling::h1) =
                   generate-id(current()/following-sibling::h1)]" />

But I don't see how this effect could be achieved in a function using
exsl:result? To build a new node set, you need to either put it in the
content of an xsl:variable and then return the value of that variable,
or put it in the content of exsl:result. I don't think that
exsl:result is (should be) allowed in either of those places.

Essentially, allowing xsl:for-each within exsl:function does two

(a) Gives a way of changing the current node within a function when
you need to return nodes from another document. There are three
functions in XSLT 1.0 that use information about the document the
current node is in: id(), key() and unparsed-entity-uri().

(b) Gives a way of returning a node based on its position in a node
list sorted in something other than document order.

I think that all other ways of using xsl:for-each within exsl:function
can be achieved through other means.

[Perhaps it would be clearer to have different ways of achieving the
 two goals above and forget about xsl:for-each. I can't really think of
 an intuitive way of doing so. For example:

 <exsl:in document="document($file-name, $base-node)">
    <exsl:result select="key($key-name, $key-value)" />

 <exsl:sort nodes="$nodes">
    <xsl:if test="position() = 1">
       <exsl:result select="." />
 </exsl:sort>   ]

I didn't exactly mean it as a way of exiting early from a function,
but this is something we're going to have to cope with in some way if
we allow anything other than xsl:variable.  For example:

<exsl:function name="my:foo">
   <xsl:if test="$test"><exsl:result select="'foo'" /></xsl:if>
   <exsl:result select="'bar'" />

equivalent to:

<exsl:function name="my:foo">
      <xsl:when test="$test"><exsl:result select="'foo'" /></xsl:when>
      <xsl:otherwise><exsl:result select="'bar'" /></xsl:otherwise>

We could just say that exsl:result cannot be instantiated more than
once during the processing of the function.  That would prevent people
from doing the first example above, and it would prevent them from
having an xsl:for-each that ever returned more than one exsl:result -
either you only choose one node with it (and use it as a way of
changing the current node) or you have an xsl:if that picks out the
node you want by position (to have access to xsl:sort).

Of course there is a demand for building larger node sets that would
be nicely catered for with something like exsl:append or
exsl:reference-of.  However, I think that these can both be achieved
with a combination of the above and some recursion.  For example, to
return the first five nodes in alphabetically sorted order, I could
use something like:

<xsl:variable select="my:first-alphabetically($nodes, 5)" />

<exsl:function name="my:first-alphabetically">
   <exsl:param name="nodes" />
   <exsl:param name="number" />
      <xsl:when test="$number = 1">
         <xsl:for-each select="$nodes">
            <xsl:sort />
            <xsl:if test="position() = 1">
               <exsl:result select="." />
         <xsl:variable name="first"
                       select="my:first-alphabetically($nodes, 1)" />
         <xsl:variable name="rest"
                       select="$nodes[count(.|$first) != 1]" />
         <exsl:result select="$first |
                                                      $number - 1)" />

It's not particularly pretty, but at least it's doable.



Jeni Tennison

 XSL-List info and archive:

Current Thread