Extensions to XSLT 1.0 (EXSLT 1.0) - Functions

This version:
    exslt-functions-010310.html
Most Recent version:
    http://www.jenitennison.com/xslt/exslt/functions/
Previous versions:
    exslt-functions-010306.html
    exslt-common-010302.html
    exslt-common-010225.html
Author: Jeni Tennison

Abstract

This document describes EXSLT 1.0 - Functions. EXSLT 1.0 is a set of extension elements and functions that XSLT authors may find helpful when creating stylesheets. EXSLT 1.0 - Functions are those extension elements and functions that allow users to define their own functions for use in expressions and patterns in XSLT.

Other parts of EXSLT 1.0 include:

Status of this Document

This document is a third 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.

This document has no official standing and has not been considered nor approved by any organization.

Contents

1. Introduction
2. Namespace
3. Defining Extension Functions
   3.1 Function Arguments
   3.2 Function Results
      3.2.1 Result Values

Appendices

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
D. Changes from previous version

1. Introduction

This document describes EXSLT 1.0 - Functions. EXSLT 1.0 is a set of extension elements and functions that XSLT authors may find helpful when creating stylesheets. EXSLT 1.0 - Functions are those extension elements and functions that allow users to define their own functions for use in expressions and patterns in XSLT.

The extension elements and functions defined within this document are governed by the general rules about extensions to XSLT covered in [14. Extensions] in [XSLT 1.0].

XSLT processors are free to support any number of the extension elements and functions described in this document. However, an XSLT processor must not claim to support EXSLT 1.0 - Common unless all the extensions described within this document are implemented by the processor. An implementation of an extension element or function in an EXSLT namespace must conform to the behaviour described in this document.

2. Namespace

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

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

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

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.

In the majority of cases, sophisticated manipulation of objects in XSLT can be done within a template, stored in a variable and then used in an XPath. However, there are situations in which expressions and patterns are evaluated without this ability, in particular in the match attribute of xsl:template, the match and use attributes of xsl:key and the select attribute of xsl:sort. This limits the sophistication of template matching, the definition of keys and the things that a node set can be sorted on.

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 instructions - 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.

One way of defining extension functions using XSLT instructions would be to simply use named templates (see [6. Named Templates]). However, named templates can only be used to generate result tree fragments and many useful functions involve returning other value types. Result tree fragments can be coerced into strings, numbers and booleans, and with the exsl:node-set function into node sets, but the node sets produced through coercion of result tree fragment consist of newly-generated nodes rather than nodes that already exist. Node sets generated via result tree fragments can only contain copies of nodes in the source document; these copies have different properties - different ancestry, preceding and following nodes, base URIs, languages and so on.

Thus, an extension element is required to allow users to define functions that involve returning node sets consisting of existing nodes.

<-- Category: top-level-element -->
<exsl:function
   name = QName>
   <!-- Content: (xsl:param*, template) -->
</exsl:function>
      

Issue: exsl:function content - should the content of exsl:function be a template? Templates are instantiated to create RTFs, but exsl:function has an 'escape' in exsl:result that means that the result of instantiating a function body can be something other than an RTF. This is a departure from the XSLT 1.0 processing model. An alternative is described in [FXPath].

Issue: exsl:function return type - should the RTF returned by a function (if exsl:return isn't used) be automatically converted to other object types, as indicated by an attriute? This option is used in fx:template-function [FXPath].

The exsl:function element can only occur at the top level of the stylesheet. The exsl:function element declares an extension function that is visible everywhere: the extension function is added to the function library available to the expressions and patterns used in the XSLT stylesheet.

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.

It is an error if a stylesheet contains more than one exsl:function element with the same name and the same import precedence. An XSLT processor may signal the error; if it does not signal the error, it must recover by using the function definition that occurs last in the stylesheet.

When an extension function defined with exsl:function is called, the content of the exsl:function is instantiated to give the result of the function (see [3.2 Function Results]).

3.1 Defining Function Arguments

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

Issue: exsl:arg - should arguments be specified with an extension element (e.g. exsl:arg or exsl:argument) rather than xsl:param? xsl:param is fairly overloaded as it is.

When an extension function is called, 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.

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.

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. The type of the value passed into the function can be tested with the exsl:object-type function.

3.2 Function Results

The content of the exsl:function element is a template. When the function is called, the template is instantiated to give the result of the function. The template is instantiated with the context node from the expression in which the function was called as the current node, and with the context node list from the expression in which the function was called as the current node list.

If the instantiation of the template results in the generation of result nodes, then the result of the function is a result tree fragment consisting of those nodes. For example a call to my:func as below will generate a result tree fragment consisting of a root node with a single foo element child.

<exsl:function name="my:func">
   <foo />
</exsl:function>
      

It is an error if a member of the sequence of nodes created by instantiating the template is an attribute node or a namespace node, since a root node cannot have an attribute node or a namespace node as a child. An XSLT processor may signal the error; if it does not signal the error, it must recover by not adding the attribute node or namespace node.

The result tree fragment that is the result of the function may be converted to a node set using the exsl:node-set function, to a string using the string function, or to a number using the number function.

Note: Applying the boolean function to the result tree fragment will always evaluate to true.

As discussed earlier in this section, generating result tree fragments only is not sufficient for many functions that involve manipulation of node sets. To enable functions to return node sets (and booleans), the instantiation of the content of the exsl:function element may involve the instantiation of an exsl:result element.

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

When an exsl:result element is instantiated, it sets the result of the function to its value. The value of the exsl:result element is determined in a similar way to variable-binding elements as described in [11.2 Values of Variables and Parameters] of [XSLT] (see [3.2.1 Result Values]).

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

Issue: exsl:reference-of - should it be possible to generate node set results by building them up gradually through an extension element such as exsl:reference-of (or exsl:append or multiple exsl:result elements)? It would help give results consisting of multiple nodes from a function. This is achievable by building an RTF with IDs pointing to the relevant nodes instead.

Issue: exsl:result parent - should the use of exsl:result be restricted to within exsl:function?

It is an error if an exsl:result element is instantiated if result nodes have been generated prior to its instantiation. Thus, the following is an unrecoverable error:

<exsl:function name="my:func">
   <foo />
   <exsl:result select="'foo'" />
</exsl:function>
      

It is an error if result nodes are generated following the instantiation of an exsl:result element. An XSLT processor may signal the error; if it does not signal the error, then it must recover by ignoring the result nodes. Thus, the following is an error:

<exsl:function name="my:func">
   <exsl:result select="'foo'" />
   <foo />
</exsl:function>
      

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

<exsl:function name="my:func">
   <exsl:result select="'foo'" />
</exsl:function>
      

Issue: RTF after exsl:result error - should generating result nodes after instantiating an exsl:result be an unrecoverable error, or a recoverable one?

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

Issue: Multiple exsl:result error - should instantiating exsl:result more than once be an unrecoverable error, or a recoverable one?

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:result 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:result select="true()" />
   </xsl:if>
   <exsl:result 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:result select="true()" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="false()" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

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

<exsl:function name="my:func2">
   <exsl:result>
      <exsl:result select="." />
   </exsl:result>
</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:result element. Thus the following is an error:

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

If no result nodes are generated during the instantiation of the function content, and no exsl:result element is instantiated, then the result of the function is an empty result tree fragment.

3.2.1 Result Values

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

A. References

FXPath
David Rosenborg. FXPath - Functional XPath. See http://www.pantor.com/fxpath
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:result select="$true" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="$false" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

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:result 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:result
      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:result select="$node-set1[count(.|$node-set2) = count($node-set2)]" />
</exsl:function>
      

Function: node-set set:distinct(node-set)

The set:distinct function returns the nodes within the node set passed as the first argument that are the first nodes in document order whose string value is their string value.

<exsl:function name="set:distinct">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="distinct" select="/.." />
   <xsl:choose>
      <xsl:when test="not($node-set)">
         <exsl:result select="/.." />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="distinct" 
                       select="set:distinct($node-set[position() > 1])" />
         <exsl:result select="$distinct | $node-set[1][. != $distinct]" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

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

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 passed as the second argument. If the first node in the second node set is not contained in the first node set, then an empty node set is returned. If the second node set is empty, then the first node set is returned.

<exsl:function name="set:leading">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="end-node-set" select="/.." />
   <xsl:variable name="end-node" select="$end-node-set[1]" />
   <xsl:choose>
      <xsl:when test="not($end-node) or not($node-set)">
         <exsl:result select="$node-set" />
      </xsl:when>
      <xsl:when test="not(set:has-same-node($node-set, $end-node)) or 
                      count($node-set[1] | $end-node) = 1">
         <exsl:result select="/.." />
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="$node-set[1] | 
                              set:leading($node-set[position() > 1], $end-node)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

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

The set:trailing function returns the nodes in the node set passed as the first argument that follow, in document order, the first node in the node set passed as the second argument. If the first node in the second node set is not contained in the first node set, then an empty node set is returned. If the second node set is empty, then the first node set is returned.

<exsl:function name="set:trailing">
   <xsl:param name="node-set" select="/.." />
   <xsl:param name="end-node-set" select="/.." />
   <xsl:variable name="end-node" select="$end-node-set[1]" />
   <xsl:choose>
      <xsl:when test="not($end-node) or not($node-set)">
         <exsl:result select="$node-set" />
      </xsl:when>
      <xsl:when test="not(set:has-same-node($node-set, $end-node))">
         <exsl:result select="/.." />
      </xsl:when>
      <xsl:when test="count($node-set[1] | $end-node) = 1">
         <exsl:result select="$node-set[position() > 1]" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="set:trailing($node-set[position() > 1], $end-node)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

B.3 Numerical Functions

Function: number math:max(node-set)

The math:max function returns the maximum, for each node in the argument node-set, of the result of converting the string-values of the node to a number using the number function. The numbers are compared as with the > operator. If the node set is empty, NaN is returned.

<exsl:function name="math:max">
   <xsl:param name="node-set" select="/.." />
   <exsl:result select="number($node-set[not($node-set > .)])" />
</exsl:function>
      

Function: number math:min(node-set)

The math:min function returns the maximum, for each node in the argument node-set, of the result of converting the string-values of the node to a number using the number function. The numbers are compared as with the < operator. If the node set is empty, NaN is returned.

<exsl:function name="math:min">
   <xsl:param name="node-set" select="/.." />
   <exsl:result select="number($node-set[not($node-set &lt; .)])" />
</exsl:function>
      

Function: node-set math:highest(node-set)

The math:highest function returns the nodes for which the result of converting the string-value of the node to a number is maximum value for the node set, as calculated with math:max.

<exsl:function name="math:highest">
   <xsl:param name="node-set" select="/.." />
   <exsl:result select="$node-set[not($node-set > .)]" />
</exsl:function>
      

Function: node-set math:lowest(node-set)

The math:lowest function returns the nodes for which the result of converting the string-value of the node to a number is minimum value for the node set, as calculated with math:min.

<exsl:function name="math:lowest">
   <xsl:param name="node-set" select="/.." />
   <exsl:result select="$node-set[not($node-set &lt; .)]" />
</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:result select="/.." />
      </xsl:when>
      <xsl:when test="number($start) = number($end)">
         <xsl:variable name="rtf">
            <node><xsl:value-of select="$start" /></node>
         </xsl:variable>
         <exsl:result 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>
         <exsl:result select="exsl:node-set($rtf)" />
      </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:result 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:result select="gen:tokens($string, $char)" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="rtf">
            <xsl:choose>
               <xsl:when test="contains($string, $delimiters)">
                  <node>
                     <xsl:value-of select="substring-before($string, $char)" />
                  </node>
                  <xsl:copy-of 
                     select="gen:tokens(substring-after($string, $char),
                                        $char)" />
               </xsl:when>
               <xsl:otherwise>
                  <node><xsl:value-of select="$string" /></node>
               </xsl:otherwise>
            </xsl:choose>
         </xsl:variable>
         <exsl:result select="exsl:node-set($rtf)">
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

B.5 Sorting Functions

Function: number sort:position(node-set, 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 according to a sort value. If the third argument is 'number' then the sort value is the string value of each node converted to a number as by the number function. Otherwise, the sort value of a node is its string value.

The second argument gives the order in which the nodes should be sorted. It should either be 'ascending' or 'descending'. The default is 'ascending'.

<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:variable name="current" select="." />
   <xsl:choose>
      <xsl:when test="count($current | $node-set) = count($node-set)">
         <xsl:for-each select="$node-set">
            <xsl:sort order="{$order}"
                         data-type="{$data-type}" />
            <xsl:if test="count(.|$current) = 1">
               <exsl:result select="position()" />
            </xsl:if>
         </xsl:for-each>
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="number('NaN')" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

B.6 Other Document Functions

Function: node-set doc:key(string, object, object, 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="exsl:object-type($documents) = 'string' or
                         count($documents) = 1">
         <xsl:for-each select="document($documents, $base-URI)">
            <exsl:result select="key($key-name, $key-value)" />
         </xsl:for-each>
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="doc:key($key-name, $key-value, 
                                         $documents[1], $base-URI) |
                                 doc:key($key-name, $key-value,
                                         $documents[position() != 1], 
                                         $base-URI)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

Function: node-set doc:id(object, object, 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="exsl:object-type($documents) = 'string' or
                         count($documents) = 1">
         <xsl:for-each select="document($documents, $base-URI)">
            <exsl:result select="id($id)" />
         </xsl:for-each>
      </xsl:when>
      <xsl:otherwise>
         <exsl:result select="doc:id($id, $documents[1], $base-URI) |
                                 doc:id($id, $documents[position() != 1], 
                                        $base-URI)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

Function: string doc:unparsed-entity-uri(string, object, 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:result select="string($entity-URI)" />
      </xsl:when>
      <xsl:otherwise>
         <exsl:result 
            select="doc:unparsed-entity-uri($entity-name,
                                            $documents[position() != 1],
                                            $base-URI)" />
      </xsl:otherwise>
   </xsl:choose>
</exsl:function>
      

C. Acknowledgements

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

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

D. Changes from Previous Version