User-defined functions¶
A named template is an instruction: you reach it with
xsl:call-template, and it writes nodes to the result tree. An xsl:function
is different in kind — it returns a value, and you call it from inside any
XPath expression: in a select, in a predicate [...], inside value-of,
even as an argument to another function. That makes functions the right tool
whenever you need a value to compose into a larger expression rather than a
chunk of output to emit.
This is XSLT 2.0/3.0 territory — version="3.0", Saxon — so the typed
machinery below is fair game.
A function callable from XPath¶
xsl:function lives at the top level of the stylesheet. Its name must be a
namespaced QName, so we bind a prefix first.
- Bind a prefix to a namespace you control. User function names live here — never in the XSLT or XPath built-in namespaces.
as=declares the return type. The function promises anxs:decimal.- Each
xsl:paramcarries its ownas=type. Arguments are matched by position, not by name (unlikexsl:with-param). - The body usually ends in
xsl:sequence, which returns the computed value rather than emitting a text node. - And here is the point: the function is called straight inside an XPath
expression. No
call-template, nowith-param— justmy:with-tax(...).
The name must be namespaced
<xsl:function name="with-tax"> is a static error. A user function name
must carry a prefix bound to a non-null namespace (here
http://example.org/fn). This keeps your names from colliding with the
built-in fn: library.
Calling a function inside select and predicates¶
The real advantage shows when the function is woven into a bigger expression. Given this catalogue:
| catalog.xml | |
|---|---|
A function that builds a label, plus one that computes a gross price, used in a
select, a predicate, and a sort:
- This function takes an element (
element(cd)) and returns a string. Functions can accept and return any XPath type, including nodes. ||is the XPath 3.0 string-concatenation operator. The element references$cd/artistare atomised to their string values here.- A function inside a predicate. A named template could never appear here — predicates are pure XPath, and only value-returning functions fit.
- The same function drives the
xsl:sortkey. - And again inside attribute value templates. One definition, many call sites.
12.00 is filtered out
my:with-tax(12.00, 0.25) is 15, which is not gt 15, so Quiet
Harbour drops out. Open Road (9.50 → 11.875) and Slow Tide
(15.00 → 18.75) survive, then sort by their label.
Functions versus named templates¶
xsl:function |
xsl:template name="..." |
|
|---|---|---|
| Returns | a value (any sequence) | writes to the result tree |
| Called from | any XPath expression | only via xsl:call-template |
| In a predicate? | yes | no |
| Arguments | positional, typed | by name, with xsl:with-param |
| Built for | pure computation, composition | emitting output, the push model |
In short: if you want a result you can drop into a select, a predicate, or a
sort key, write a function. If you want to produce a block of output —
especially recursive output that builds nodes — reach for a
named template. The two complement each other: a function
computes the value, a template lays it out.
Recursion and a peek at higher-order functions¶
Functions may call themselves, which is the classic way to fold over a sequence without a mutable counter:
| fn-sum.xsl | |
|---|---|
xs:decimal*— the*means zero or more. Functions accept sequences, not just single values.- The recursive call peels off the first item and sums the rest.
In 3.0 you rarely need to hand-roll recursion
XPath 3.0 has inline anonymous functions —
function($x as xs:decimal) { $x * 1.25 } — and higher-order
functions that take them as arguments, like fold-left(...),
for-each(...) and filter(...). The whole my:running-total above is
just fold-left($prices, 0, function($a, $b) { $a + $b }). A topic for
another day, but worth knowing it exists.
Next¶
Filtering by a computed value naturally leads to collecting related items.
Grouping introduces xsl:for-each-group — XSLT 2.0's answer to
turning a flat list into nested structure.