[Cache from http://www.jenitennison.com/xslt/exsl.html; please use this canonical URL/source if possible.]


User-Defined Extension Functions in XSLT

Version: 0.1
Author: Jeni Tennison

Abstract

This document describes a method for defining user extension functions using XSLT in XSLT 1.0.

XPath contains a number of functions that allow you to perform manipulation of strings, numbers, node sets and so on. While these cover basic functionality, there are often situations where stylesheet authors either need to or want to do more within an XPath.

Most XSLT applications offer a range of extension functions. However, using only implementation's extension functions limits the stylesheet author to those thought of and implemented by a particular vendor. It also means that the stylesheet itself is limited to that vendor. Allowing users to define their own extension functions enables them to create the functions that they need for their particular application and enhances the portability of their stylesheets.

Stylesheet authors need to have a ways of defining their own functions. These definitions may be in any programming language, but it is likely that different XSLT processors will support different languages. The one language that all XSLT processors support is XSLT. It therefore makes sense to allow stylesheet authors to define extension functions using XSLT - the implementation may not be as efficient as it would be in, say, Java, but at least it can be supported across platforms and implementations, and limits the number of langauges that stylesheet authors have to learn.

Status of this Document

This document is a first draft for review by the implementers of XSLT processors and the XSLT stylesheet authors. It is based on discussions on XSL-List. Comments on this document should be sent to XSL-List.

Contents

1. Introduction
2. Namespace
3. Defining Extension Functions
   3.1 Defining Function Arguments
   3.2 Defining Function Return Values
      3.2.1 Return Values
4. Calling Extension Functions
5. Additional Functions
A. References
B. Sample Functions
   B.1 Common Extension Functions
   B.2 Set Functions
   B.3 Numerical Functions
   B.4 Generative Functions
   B.5 Sorting Functions
   B.6 Other Document Functions
C. Acknowledgements

1. Introduction

This document defines extension elements and functions to support user definition of extension functions using XSLT, in XSLT 1.0.

The extension elements and functions defined within this document are governed by the general rules about extensions to XSLT covered in Section 14 [Extensions] in the XSLT 1.0 Recommendation which can be found at http://www.w3.org/TR/xslt.

An XSLT processor that supports user-defined extension functions in XSLT using the extension elements and functions defined within this document must conform to the behaviour described within this document.

2. Namespace

The namespace for the extension elements and functions described in this document is:

...
		

Issue: Namespace - what namespace should be used for these extension elements/functions? Possibility:

http://xmlns.opentechnology.org/xslt-extensions/common
			

Throughout this document, the prefix exsl is used to refer to this namespace. Any other prefix can be used within a particular stylesheet (though a prefix must be specified to enable the extension functions to be recognised as extensions).

3. Defining Extension Functions

<exsl:function
   name = QName>
   <!-- Content: (xsl:param*,
                  (xsl:variable | xsl:if | xsl:choose | xsl:message)*,
		  (exsl:return, xsl:fallback?)? -->
</exsl:function>
		

Extension functions written in XSLT are defined by the exsl:function element, which can occur only at the top level of the stylesheet.

Issue: Wrapper - should there be a wrapper element to contain the exsl:function elements in a document?

Issue: Conditions - should xsl:if and xsl:choose be allowed as direct children of exsl:function? It makes it easier to do conditional processing if they are. But conditional processing could alternatively be achieved through an extension to XPath syntax that gave a conditional construct.

An exsl:function element must have a name attribute, indicating the name of the function. The value of the name attribute is a QName, which is expanded as described in Section 2.4 [Qualified Names] in the XSLT 1.0 Recommendation. It is an error if the namespace URI of the expanded name of the function is null - extension functions must not be in a null namespace.

Note: the rules on resolving qualified names entail that if no prefix is defined, the namespace URI resolves to the null namespace. Thus, it is an error if the qualified name specified does not have a prefix.

When an extension function defined with exsl:function is called, the content of the exsl:function is instantiated to give the return value of the function.

It is an error if the instantiation of the content of exsl:function results in the creation of result tree nodes unless they are created as part of a result tree fragment. An XSLT processor may signal the error; if it does not signal the error, it must recover by ignoring the result tree nodes.

Issue: RTF error - should generating result nodes be an unrecoverable error?

The implication of this is that literal result elements, xsl:copy, xsl:copy-of, xsl:element, xsl:attribute, xsl:processing-instruction, xsl:comment, xsl:text, xsl:value-of and xsl:number elements must not be instantiated during the instantiation of the content of exsl:function, unless they are instantiated during the creation of a result tree fragment (i.e. within xsl:variable, xsl:param, xsl:with-param or exsl:return).

It is an error if the instantiation of the content of exsl:function results in a call to or application of a template unless the template is called or applied within a variable-binding element such as xsl:variable or xsl:param or within exsl:return.

Issue: Templates - should it be possible to call or apply templates within exsl:function outside variable-binding elements? Given that generating result nodes is an error, the only purpose of calling or applying templates would be for templates that generate messages. This is achievable using an extension function instead.

It is an error if the instantiation of the content of exsl:function results in an xsl:for-each element being instantiated unless the xsl:for-each is within a variable-binding element such as xsl:variable or xsl:param or within exsl:return.

Issue: xsl:for-each - should xsl:for-each be allowed within exsl:function outside variable-binding elements? It would ease the return of node sets created (a) from other documents or (b) involving sorting the node set in other than document order. This is achievable by building an RTF with IDs referring to the relevant nodes instead.

3.1 Defining Function Arguments

Arguments for functions are defined with the xsl:param element, as specified in Section 11 [Variables and Parameters] of the XSLT 1.0 Recommendation.

Issue: Arguments - should arguments be specified with the xsl:param element or through attributes on exsl:function? One problem with specifying them as arguments is that the attribute order is not guaranteed; this wouldn't be a problem if arguments were passed by name rather than position.

When an extension function is called with arguments passed by position, the values passed as arguments are assigned to parameters according to the position of the xsl:param. The first argument is assigned to the first parameter, the second to the second and so on. The presence of an xsl:param indicates that an argument is expected for the function but does not imply that an argument has to be passed to the function.

Issue: Optional arguments - should there be an extension attribute on xsl:param that indicates whether an argument is optional?

An XSLT processor must not signal an error if an extension function is called with fewer arguments than there are parameters defined for the extension function. It is an error to call a function with more arguments than there are parameters defined for the extension function. An XSLT processor may signal the error; if it does not signal the error, then it must recover by ignoring the extra arguments.

Issue: Argument error - should trying to pass more arguments than there are xsl:param elements be considered an error? Should it be an unrecoverable error?

As an example, take the following function definition:

<exsl:function name="my:func">
   <xsl:param name="foo" />
   <xsl:param name="bar" select="false()" />
   ...
</exsl:function>
		

All the following function calls are legal:

my:func()
my:func('Fred')
my:func('Fred', true())
my:func('Fred', 'Barney')
		

The following function call is illegal:

my:func('Fred', true(), 'Barney')
		

The value specified by an xsl:param indicates the default value for an argument if that argument is not given in a function call, but does not indicate the acceptable value types for the function.

Issue: Argument types - should there be an extension attribute on xsl:param that indicates the value type of an argument?

3.2 Defining Function Return Values

When a function is called, the content of the exsl:function element is instantiated to give the return value of the function. The instantiation of the content of the exsl:function element may involve the instantiation of an exsl:return element.

<exsl:return
   select = expression>
   <!-- Content: template -->
</exsl:return>
		

The exsl:return element works in a similar way to variable-binding elements as described in Section 11.2 of the XSLT 1.0 Recommendation (see [3.2.1 Return Values]).

Issue: exsl:return name - should exsl:return be called exsl:result instead?

Issue: exsl:reference-of - should it be possible to return node sets by building them up gradually through an extension element such as exsl:reference-of? It would ease the return of multiple nodes from a function. This is achievable by building an RTF with IDs pointing to the relevant nodes instead.

It is an error for exsl:return to appear outside the content of exsl:function. For example, the following is an error:

<xsl:template match="foo">
   <exsl:return select="." />
</xsl:template>
		

Issue: exsl:return parent - should the use of exsl:return be restricted to within exsl:function or is this too restrictive? Are there any other places where exsl:return might be useful?

It is an error if instantiating the content of the exsl:function element results in the instantion of more than one exsl:return elements. An XSLT processor may signal the error; if it does not signal the error, it must recover by ignoring all exsl:return elements after the first.

Issue: Multiple exsl:return error - should instantiating exsl:return more than once be an unrecoverable error?

The following is an error if the value of the context node when the function is called is equal to the string 'yes', as two exsl:return elements are instantiated: one within the xsl:if and one directly within the exsl:function:

<exsl:function name="my:func1">
   <xsl:if test=". = 'yes'">
      <exsl:return select="true()" />
   </xsl:if>
   <exsl:return select="false()" />
</exsl:function>
		

If an XSLT processor recovers from this error, the above function is equivalent to:

<exsl:function name="my:func1">
   <xsl:choose>
      <xsl:when test=". = 'yes'">
	 <exsl:return select="true()" />
	 
	 
	 <exsl:return select="false()" />
	 
   </xsl:choose>
</exsl:function>
		

It is an error if an exsl:return element occurs within an exsl:return element. Thus the following is an error:

<exsl:function name="my:func2">
   <exsl:return>
      <exsl:return select="." />
   </exsl:return>
</exsl:function>
		

It is an error if instantiating the content of a variable-binding element (i.e. xsl:variable, xsl:param) results in the instantiation of an exsl:return element. Thus the following is an error:

<exsl:function name="my:func3">
   <xsl:variable name="foo">
      <exsl:return select="." />
   </xsl:variable>
</exsl:function>
		

If no exsl:return element is instantiated during the execution of a function, the return value for the function is the empty string ('').

Issue: Return values - should the default return value from a function be a node set instead of an empty string? Attempting to interpret a string as a node set will lead to an unrecoverable error. For example:

<exsl:function name="my:root">
   <xsl:if test="false()">
      <exsl:return select="/" />
   </xsl:if>
</exsl:function>
			

would (accidentally) return an empty string. If it was used in:

<xsl:variable name="foo" select="my:root()/foo" />
			

this would cause an unrecoverable error.

3.2.1 Return Values

The exsl:return element can specify the value of the variable in three alternative ways.

4. Calling Extension Functions

Extension functions defined using exsl:function can be called in the same way as XPath functions. Calling extension functions in this way causes arguments to be passed by position.

Issue: Arguments by name - should there be a way to pass arguments to extension function by parameter name?

Issue: Dynamic calls - should there be a way to dynamically determine the name of the function being called?

5. Additional Functions

This section defines additional common extension functions.

Function: node-set exsl:node-set(object)

The purpose of the exsl:node-set function is to convert a result tree fragment into a node set. If the argument is a node set already, it is simply returned as is. It is an error if the argument to exsl:node-set is not a node set or a result tree fragment.

Issue: exsl:node-set name - should exsl:node-set be called something else instead?

Issue: exsl:node-set argument - should authors be able to use exsl:node-set to create a node set from a type other than a result tree fragment? If a value other than a node set or result tree fragment were passed as an argument, this could be converted to a string and a node set generated with a root node with a single text node child with a string value of the value.

Issue: exsl:if - should this specification define an exsl:if function for conditional processing that doesn't involve evaluating both the true and false parts?

Issue: Type tests - should this specification define functions to test the type of values passed as parameters? Several XPath functions allow an argument to be either a string or a node set, but treating a string as a node set will cause an error and there's no way to detect whether a variable value is actually a string or a node set.

A. References

XSLT
World Wide Web Consortium. XSL Transformations (XSLT). W3C Recommendation. See http://www.w3.org/TR/xslt
XPath
World Wide Web Consortium. XML Path Language. W3C Recommendation. See http://www.w3.org/TR/xpath

B. Sample Extension Functions

This appendix holds example implementations of several extension functions.

B.1 Common Extension Functions

Function: object com:if(boolean, object, object)

The com:if function returns the second argument if the first argument is true and the third argument if the first argument is false. Both the second and third arguments are evaluated whether the first argument is true or false.

<exsl:function name="com:if">
   <xsl:param name="test" select="true()" />
   <xsl:param name="true" />
   <xsl:param name="false" />
   <xsl:choose>
      <xsl:when test="$test">
         <exsl:return select="$true" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return select="$false" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: RTF com:eval(node-set, string?)

The com:eval function is used to give the 'value' of a node. Usually the value of a node is its string value, but in different contexts it may be generated by, for example, counting how many children it has or the value of a particular attribute.

The first argument is a node set; if the node set has more than one node in it then only the first node is considered. By default, the string value of the node is returned as a result tree fragment. Stylesheet authors can change what value is returned by adding templates that match the node in com:eval mode and return different values.

The second argument is a string. This nominally gives the expression that should be evaluated for the node, but is actually simply passed through as a parameter to the template that gives the value for the node. A stylesheet author can use the expression to retrieve different values for the same node in different situations.

<exsl:function name="com:eval">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <exsl:return>
      <xsl:apply-templates select="$node-set[1]">
         <xsl:with-param name="expr" select="$expr" />
      </xsl:apply-templates>
   </exsl:return>
</exsl:function>
		
<xsl:template match="node()" mode="com:eval">
   <xsl:param name="expr" select="'.'" />
   <xsl:value-of select="." />
</xsl:template>
		

For example, stylesheet authors could add a template to enable the retrieval of a count of the number of cells a table row contains:

<xsl:template match="tr" mode="com:eval">
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="$expr = 'count(td)'">
         <xsl:value-of select="count(td)" />
      </xsl:when>
	  <xsl:otherwise>
	     <xsl:value-of select="." />
	  </xsl:otherwise>
   </xsl:choose>
</xsl:template>
		

The com:eval function is used as a basis of functions such as set:distinct, num:max, num:min and so on.

B.2 Set Functions

Function: node-set set:difference(node-set, node-set)

The set:difference function returns the difference between two node sets - those nodes that are in the node set passed as the first argument that are not in the node set passed as the second argument.

<exsl:function name="set:difference">
   <xsl:param name="node-set1" select="/.." />
   <xsl:param name="node-set2" select="/.." />
   <exsl:return select="$node-set1[count(.|$node-set2) != count($node-set2)]" />
</exsl:function>
		

Function: boolean set:has-same-node(node-set, node-set)

The set:has-same-node function returns true if the node set passed as the first argument shares any nodes with the node set passed as the second argument. If there are no nodes that are in both node sets, then it returns false.

<exsl:function name="set:has-same-node">
   <xsl:param name="node-set1" select="/.." />
   <xsl:param name="node-set2" select="/.." />
   <exsl:return
      select="boolean($node-set1[count(.|$node-set2) = count($node-set2)])" />
</exsl:function>
		

Function: node-set set:intersection(node-set, node-set)

The set:intersection function returns a node set comprising the nodes that are within both the node sets passed as arguments to it.

<exsl:function name="set:intersection">
   <xsl:param name="node-set1" select="/.." />
   <xsl:param name="node-set2" select="/.." />
   <exsl:return select="$node-set1[count(.|$node-set2) = count($node-set2)]" />
</exsl:function>
		

Function: node-set set:distinct(node-set, string?)

The set:distinct function returns the nodes within the node set passed as the first argument that have different values. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to set:distinct.

<exsl:function name="set:distinct">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:param name="distinct" select="/.." />
   <xsl:choose>
      <xsl:when test="not($node-set)">
         <exsl:return select="/.." />
      </xsl:when>
      <xsl:when test="count($node-set) = 1">
         <xsl:variable name="node-value" 
	               select="string(com:eval(., $expr))" />
         <xsl:choose>
	    <xsl:when test="$distinct[string(com:eval(., $expr)) = 
	                              $node-value]">
	       <exsl:return select="$distinct" />
	    </xsl:when>
	    <xsl:otherwise>
	       <exsl:return select="$distinct | $node-set" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
         <exsl:return
	    select="set:distinct($node-set[1], 
	                         $expr, 
	                         set:distinct($node-set[position() != 1], 
				              $expr))" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: node-set set:leading(node-set, string, string?)

The set:leading function returns the nodes in the node set passed as the first argument that precede, in document order, the first node in the node set whose value is equal to the string passed as the second argument. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the third argument to set:leading.

<exsl:function name="set:leading">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="value" />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="not($node-set) or 
                      string(com:eval($node-set[1], $expr)) =
                      string($value)">
         <exsl:return select="/.." />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return select="$node-set[1] | 
	                      set:leading($node-set[position() != 1], 
			                  $value, $expr)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: node-set set:following(node-set, string, string?)

The set:following function returns the nodes in the node set passed as the first argument that is itself or that follow, in document order, the first node in the node set whose value is equal to the string passed as the second argument. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the third argument to set:following.

<exsl:function name="set:following">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="value" />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="not($node-set)">
         <exsl:return select="/.." />
      </xsl:when>
      <xsl:when test="string(com:eval($node-set[1], $expr)) =
                      string($value)">
         <exsl:return select="$node-set" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return select="set:following($node-set[position() != 1],
	                                    $value, $expr)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: boolean set:exists(node-set, string?)

The set:exists function returns true if the value of any of the nodes in the node set passed as the first argument is true. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to set:exists.

<exsl:function name="set:exists">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="not($node-set)">
         <exsl:return select="false()" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="value" 
	               select="string(com:eval($node-set[1], $expr))" />
	 <xsl:choose>
	    <xsl:when test="$value = 'false' or not($value)">
               <exsl:return select="set:exists($node-set[position() != 1],
	                                       $value, $expr)" />
	    </xsl:when>
            <xsl:otherwise>
	       <exsl:return select="true()" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: boolean set:for-all(node-set, string?)

The set:for-all function returns true if the value of all the nodes in the node set passed as the first argument is true. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to set:for-all.

<exsl:function name="set:for-all">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="not($node-set)">
         <exsl:return select="true()" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="value"
	               select="string(com:eval($node-set[1], $expr))" />
         <xsl:choose>
	    <xsl:when test="$value = 'false' or not($value)">
	       <exsl:return select="false()" />
	    </xsl:when>
	    <xsl:otherwise>
               <exsl:return select="set:for-all($node-set[position() != 1],
	                                        $value, $expr)" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

B.3 Numerical Functions

Function: number num:max(node-set, string?)

The num:max function returns the maximum value of the nodes in the node set passed as the first argument. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to num:max.

<exsl:function name="num:max">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:variable name="value-of-first" 
                 select="number(com:eval($node-set[1], $expr))" />
   <xsl:choose>
      <xsl:when test="count($node-set) <= 1">
         <exsl:return select="$value-of-first" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="max-of-rest"
                       select="num:max($node-set[position() != 1], $expr)" />
	 <xsl:choose>
	    <xsl:when test="$value-of-first > $max-of-rest">
	       <exsl:return select="$value-of-first" />
	    </xsl:when>
	    <xsl:otherwise>
	       <exsl:return select="$max-of-rest" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: number num:min(node-set, string?)

The num:min function returns the minimum value of the nodes in the node set passed as the first argument. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to num:min.

<exsl:function name="num:min">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:variable name="value-of-first" 
                 select="number(com:eval($node-set[1], $expr))" />
   <xsl:choose>
      <xsl:when test="count($node-set) <= 1">
         <exsl:return select="$value-of-first" />
      </xsl:when>
      <xsl:otherwise>
      <xsl:variable name="min-of-rest"
                       select="num:min($node-set[position() != 1], $expr)" />
	 <xsl:choose>
	    <xsl:when test="$value-of-first < $max-of-rest">
	       <exsl:return select="$value-of-first" />
	    </xsl:when>
	    <xsl:otherwise>
	       <exsl:return select="$min-of-rest" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: node-set num:highest(node-set, string?)

The num:highest function returns the node in the node set passed as the first argument with the highest value. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to num:highest.

<exsl:function name="num:highest">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="count($node-set) <= 1">
         <exsl:return select="$node-set[1]" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="highest-of-rest"
                       select="num:highest($node-set[position() != 1], 
		                           $expr)" />
	 <xsl:choose>
	    <xsl:when test="number(com:eval($node-set[1], $expr)) > 
	                    number(com:eval($highest-of-rest, $expr))">
	       <exsl:return select="$node-set[1]" />
	    </xsl:when>
	    <xsl:otherwise>
	       <exsl:return select="$highest-of-rest" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: node-set num:lowest(node-set, string?)

The num:lowest function returns the node in the node set passed as the first argument with the lowest value. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to num:lowest.

<exsl:function name="num:lowest">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:choose>
      <xsl:when test="count($node-set) <= 1">
         <exsl:return select="$node-set[1]" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="lowest-of-rest"
                       select="num:lowest($node-set[position() != 1], 
		                          $expr)" />
	 <xsl:choose>
	    <xsl:when test="number(com:eval($node-set[1], $expr)) < 
	                    number(com:eval($lowest-of-rest, $expr))">
	       <exsl:return select="$node-set[1]" />
	    </xsl:when>
	    <xsl:otherwise>
	       <exsl:return select="$lowest-of-rest" />
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: number num:sum(node-set, string?)

The num:sum function returns the sum of the values of the nodes in the node set passed as the first argument. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the object passed as the second argument to num:sum.

<exsl:function name="num:sum">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="expr" select="'.'" />
   <xsl:variable name="value-of-first"
                 select="number(com:eval($node-set[1], $expr))" />
   <xsl:choose>
      <xsl:when test="count($node-set) <= 1">
         <exsl:return select="$value-of-first" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return select="$value-of-first +
	                      num:sum($node-set[position() != 1], $expr)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

B.4 Generative Functions

Function: node-set gen:range(number, number)

The gen:range function generates a node set in which the string values of the nodes are numbers between the number passed as the first argument and the number passed as the second argument.

<exsl:function name="gen:range">
   <xsl:param name="start" select="0" />
   <xsl:param name="end" select="0" />
   <xsl:choose>
      <xsl:when test="number($start) > number($end)">
         <exsl:return select="/.." />
      </xsl:when>
      <xsl:when test="number($start) = number($end)">
         <xsl:variable name="rtf">
	    <node><xsl:value-of select="$start" /></node>
	 </xsl:variable>
	 <exsl:return select="exsl:node-set($rtf)" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="mid-point" 
	               select="$start + floor(($end - $start) div 2)" />
         <xsl:variable name="rtf">
	    <xsl:copy-of select="gen:range($start, $midpoint)" />
	    <xsl:copy-of select="gen:range($midpoint + 1, $end)" />
	 </xsl:variable>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Function: string gen:padding(number, string?)

The gen:padding function generates a string generated by repeating the string given as the second argument (or a space [' '] if there is no second argument) repeated the number of times specified by the first argument.

<exsl:function name="gen:padding">
   <xsl:param name="repeat" select="1" />
   <xsl:param name="string" select="' '" />
   <xsl:variable name="padding">
      <xsl:for-each select="gen:range(1, $repeat)">
         <xsl:value-of select="$string" />
      </xsl:for-each>
   </xsl:variable>
   <exsl:return select="string($padding)" />
</exsl:function>
		

Function: node-set gen:tokens(string, string?)

The gen:tokens function tokenises the string passed as the first argument by splitting it at each character in the string passed as the second argument. If no second argument is specified, then a space (' ') is assumed.

<exsl:function name="gen:tokens">
   <xsl:param name="string" />
   <xsl:param name="delimiters" select="' '" />
   <xsl:variable name="char" select="substring($delimiters, 1, 1)" />
   <xsl:choose>
      <xsl:when test="string-length($delimiters) > 1">
         <xsl:variable name="replacement"
                       select="gen:padding(string-length($delimiters) - 1,
		                           $char)" />
         <xsl:variable name="string-tokens"
                       select="translate($string, 
		                         substring($delimiters, 2),
			                 $replacement)" />
	 <exsl:return select="gen:tokens($string, $char)" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:choose>
	    <xsl:when test="contains($string, $delimiters)">
	       <exsl:return>
	          <node>
		     <xsl:value-of select="substring-before($string, $char)" />
		  </node>
		  <xsl:copy-of 
		     select="gen:tokens(substring-after($string, $char),
		                        $char)" />
	       </exsl:return>
	    </xsl:when>
	    <xsl:otherwise>
	       <node><xsl:value-of select="$string" /></node>
	    </xsl:otherwise>
	 </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

B.5 Sorting Functions

Function: number sort:position(node-set, string?, string?, string?)

The sort:position function gives the position of the current node within the node-set passed as the first argument when the nodes in it are sorted by value. The 'value' of a node is calculated using com:eval with a first argument being the node, and the second argument being the expression passed as the fourth argument to sort:position.

The second argument gives the order in which the nodes should be sorted. It should either be 'ascending' or 'descending'. The default is 'ascending'. The third argument gives the data type of the nodes. It should be 'text' or 'number' or a qualified name. The default is 'text'.

<exsl:function name="sort:position">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="order" select="'ascending'" />
   <xsl:param name="data-type" select="'text'" />
   <xsl:param name="expr" select="'.'" />
   <xsl:variable name="current" select="." />
   <xsl:choose>
      <xsl:when test="count($current | $node-set) = count($node-set)">
         <xsl:variable name="position">
            <xsl:for-each select="$node-set">
               <xsl:sort select="com:eval(., $expr)"
	                 order="{$order}"
		         data-type="{$data-type}" />
               <xsl:if test="count(.|$current) = 1">
                  <xsl:value-of select="position()" />
               </xsl:if>
            </xsl:for-each>
	 </xsl:variable>
	 <exsl:return select="number($position)" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return select="1 div 0" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

B.6 Other Document Functions

Function: node-set doc:key(string, object, node-set, node-set?)

The doc:key function is an extension of the XSLT key function that retrieves nodes in documents aside from the one the current node is in.

The first two arguments operate in the same way as the two arguments to key. The third and fourth arguments operate in the same way as the two arguments to document.

<exsl:function name="doc:key">
   <xsl:param name="key-name" />
   <xsl:param name="key-value" />
   <xsl:param name="documents" select="/.." />
   <xsl:param name="base-URI" select="document('')" />
   <xsl:choose>
      <xsl:when test="count($documents) > 1">
         <exsl:return select="doc:key($key-name, $key-value, 
	                              $documents[1], $base-URI) |
			      doc:key($key-name, $key-value,
			              $documents[position() != 1], 
				      $base-URI)" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="doc"
	               select="document($documents, $base-URI)" />
         <xsl:variable name="nodes">
  	    <xsl:for-each select="$doc">
	       <xsl:for-each select="key($key-name, $key-value)">
	          <node id="{generate-id()}" />
	       </xsl:for-each>
	    </xsl:for-each>
         </xsl:variable>
	 <xsl:variable name="doc-nodes" select="$doc//node() | $doc//@*" />
	 <exsl:return select="$doc-nodes[generate-id() = $nodes/node/@id]" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Note: this function would be vastly more efficient if the exsl:return element were permitted as a descendant of xsl:for-each:

<exsl:function name="doc:key">
   <xsl:param name="key-name" />
   <xsl:param name="key-value" />
   <xsl:param name="documents" select="/.." />
   <xsl:param name="base-URI" select="document('')" />
   <xsl:choose>
      <xsl:when test="count($documents) > 1">
         <exsl:return select="doc:key($key-name, $key-value, 
	                                 $documents[1], $base-URI) |
		   	         doc:key($key-name, $key-value,
			                 $documents[position() != 1], 
				         $base-URI)" />
      </xsl:when>
      <xsl:otherwise>
	 <xsl:for-each select="document($documents, $base-URI)">
	    <exsl:return select="key($key-name, $key-value)" />
	 </xsl:for-each>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
			

Function: node-set doc:id(object, node-set, node-set?)

The doc:id function is an extension of the XPath id function that retrieves nodes in documents aside from the one the context node is in.

The first argument operates in the same way as the argument to id. The second and third arguments operate in the same way as the two arguments to document.

<exsl:function name="doc:id">
   <xsl:param name="id" />
   <xsl:param name="documents" select="/.." />
   <xsl:param name="base-URI" select="document('')" />
   <xsl:choose>
      <xsl:when test="count($documents) > 1">
         <exsl:return select="doc:id($id, $documents[1], $base-URI) |
			      doc:id($id, $documents[position() != 1], 
				     $base-URI)" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="doc"
	               select="document($documents, $base-URI)" />
         <xsl:variable name="nodes">
  	    <xsl:for-each select="$doc">
	       <xsl:for-each select="id($id)">
	          <node id="{generate-id()}" />
	       </xsl:for-each>
	    </xsl:for-each>
         </xsl:variable>
	 <xsl:variable name="doc-nodes" select="$doc//node() | $doc//@*" />
	 <exsl:return select="$doc-nodes[generate-id() = $nodes/node/@id]" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

Note: this function would be vastly more efficient if the exsl:return element were permitted as a descendant of xsl:for-each:

<exsl:function name="doc:id">
   <xsl:param name="id" />
   <xsl:param name="documents" select="/.." />
   <xsl:param name="base-URI" select="document('')" />
   <xsl:choose>
      <xsl:when test="count($documents) > 1">
         <exsl:return select="doc:id($id, $documents[1], $base-URI) |
   			         doc:id($id, $documents[position() != 1], 
				        $base-URI)" />
      </xsl:when>
      <xsl:otherwise>
	 <xsl:for-each select="document($documents, $base-URI)">
	    <exsl:return select="id($id)" />
	 </xsl:for-each>
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
			

Function: string doc:unparsed-entity-uri(string, node-set, node-set?)

The doc:unparsed-entity-uri function is an extension of the XSLT unparsed-entity-uri function that retrieves the URL of an entity in documents aside from the one the context node is in. This function can be used to search in documents other than the one holding the context node, but will return the first URL that it finds; if the entity is defined with different URLs in different documents, then only the first document given in the node set specified as the second argument (when considered in document order) will be returned.

The first argument operates in the same way as the argument to unparsed-entity-uri. The second and third arguments operate in the same way as the two arguments to document.

<exsl:function name="doc:unparsed-entity-uri">
   <xsl:param name="entity-name" />
   <xsl:param name="documents" select="/.." />
   <xsl:param name="base-URI" select="document('')" />
   <xsl:variable name="entity-URI">
      <xsl:for-each select="document($documents[1], $base-URI)">
         <xsl:value-of select="unparsed-entity-uri($entity-name)" />
      </xsl:for-each>
   </xsl:variable>
   <xsl:choose>
      <xsl:when test="string($entity-URI)">
         <exsl:return select="string($entity-URI)" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:return 
	    select="doc:unparsed-entity-uri($entity-name,
                                            $documents[position() != 1],
					    $base-URI)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
		

B. Acknowledgements

This has been informed and inspired by discussions on XSL-List with:

David Carlisle
Joe English
Clark C. Evans
Dave Gomboc
Yevgeniy (Eugene) Kaganovich
Mike Kay
Steve Muench
Miloslav Nic
Francis Norton
Dimitre Novatchev
Uche Ogbuji
David Rosenborg