How Does XSLT 3.0 Support Higher-Order Functions?
XSLT 3.0, in conjunction with XPath 3.0 and 3.1, fundamentally
modernizes XML transformations by treating functions as first-class
citizens. Through dynamic function items, inline function expressions,
and higher-order functions like fn:for-each,
fn:filter, and fn:fold-left, developers can
write modular, functional code directly within stylesheets without
relying on verbose template workarounds.
First-Class Functions and Named References
In earlier versions of XSLT and XPath, functions could only be called statically by name. XSLT 3.0 introduces function items as a discrete data type. This allows functions to be assigned to variables, passed as arguments to other functions or templates, and returned as results.
Named functions are referenced using the literal function reference
syntax QName#arity:
<!-- Storing a function reference in a variable -->
<xsl:variable name="cleaner" select="fn:normalize-space#1" />
<!-- Invoking the function item dynamically -->
<xsl:value-of select="$cleaner(' sample text ')" />The arity (#1 in the example above) is required because
XPath supports function overloading by argument count.
Inline Function Expressions
Inline functions (also known as anonymous functions or lambdas) allow
developers to declare functions locally without defining a standalone
xsl:function declaration.
The syntax for an inline function follows this structure:
function($parameter as type) as return-type { expression }
Inline functions capture their lexical scope (closures), meaning they can reference local variables and parameters from the surrounding template or stylesheet context:
<xsl:variable name="multiplier" select="10" />
<xsl:variable name="scale" select="function($val as xs:integer) as xs:integer { $val * $multiplier }" />
<xsl:value-of select="$scale(5)" /> <!-- Outputs 50 -->Standard Higher-Order Functions
XPath 3.0 and 3.1 provide built-in higher-order functions in the
fn namespace that accept function items as arguments to
manipulate sequences:
fn:for-each($seq, $f): Applies the function item$fto each item in$seqand returns the resulting sequence.fn:filter($seq, $f): Evaluates each item with a predicate function$f(returningxs:boolean) and keeps only matching items.fn:fold-left($seq, $zero, $f): Combines sequence elements from left to right using an accumulator function$f($accum, $item).fn:fold-right($seq, $zero, $f): Combines sequence elements from right to left.fn:apply($f, $array): Calls the function$fwith arguments supplied as an array.
Practical Transformation Example
Higher-order functions eliminate complex recursive templates when aggregating or filtering data:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
version="3.0">
<xsl:template match="orders">
<!-- Filter high-value items and double their value using inline functions -->
<xsl:variable name="prices" select="item/@price/xs:decimal(.)" />
<xsl:variable name="expensive"
select="fn:filter($prices, function($p) { $p gt 50.0 })" />
<xsl:variable name="discountedSum"
select="fn:fold-left($expensive, 0, function($total, $p) { $total + ($p * 0.9) })" />
<total-discounted-value>
<xsl:value-of select="$discountedSum" />
</total-discounted-value>
</xsl:template>
</xsl:stylesheet>By integrating first-class function items, closures, and higher-order sequence functions, XSLT 3.0 enables cleaner abstractions, higher code reuse, and true declarative functional programming across stylesheet pipelines.