How to Define Custom Functions Using xsl:function in XSLT?
Defining custom stylesheet functions with
<xsl:function> in XSLT 2.0 and later allows
developers to encapsulate reusable logic, perform complex calculations,
and return strongly typed data directly within XPath expressions. Unlike
traditional named templates, custom functions can be invoked seamlessly
within XPath select attributes, predicates, and test conditions without
disrupting the structural flow of the transformation. This guide covers
how to declare a custom namespace, define function parameters, enforce
return types, and implement custom functions in real-world
scenarios.
Declaring Required Namespaces
Every user-defined function in XSLT 2.0 and above must reside in a non-default namespace to prevent naming collisions with built-in XPath and XSLT functions. This requirement is enforced by the XSLT processor.
To declare a custom namespace, add a namespace prefix mapping on the
root <xsl:stylesheet> or
<xsl:transform> element:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:custom="http://example.com/custom-functions"
exclude-result-prefixes="xs custom">The exclude-result-prefixes attribute prevents namespace
declarations from leaking into the output XML, HTML, or text
document.
Structure of the xsl:function Element
The <xsl:function> element is a top-level
declaration that defines the name, parameters, and evaluation logic of a
reusable routine.
Key Attributes and Child Elements
name(Required): The QName (qualified name) of the function, prefixed with your custom namespace (e.g.,custom:calculate-tax).as(Optional but Recommended): Defines the expected return data type using standard XML Schema types (such asxs:string,xs:decimal,xs:boolean) or sequence types (likeelement()*oritem()+).<xsl:param>: Declares each parameter passed to the function. Each parameter should also specify its type via theasattribute.- Function Body: Contains the computation
instructions, such as
<xsl:sequence>,<xsl:value-of>, or conditional statements like<xsl:choose>.
Recommended Return Strategy: xsl:sequence
While <xsl:value-of> converts results into text
nodes, <xsl:sequence> preserves the underlying data
type and sequence structure without adding extra text node wrappers. For
mathematical operations and structural node handling,
<xsl:sequence> is the standard practice.
<xsl:function name="custom:celsius-to-fahrenheit" as="xs:double">
<xsl:param name="temp-c" as="xs:double"/>
<xsl:sequence select="($temp-c * 9 div 5) + 32"/>
</xsl:function>Step-by-Step Implementation Example
Consider an XML document containing an inventory list with item prices and tax categories:
<inventory>
<item id="101">
<name>Mechanical Keyboard</name>
<price>120.00</price>
<category>standard</category>
</item>
<item id="102">
<name>Prescription Glasses</name>
<price>250.00</price>
<category>exempt</category>
</item>
</inventory>Complete XSLT Stylesheet
The following stylesheet defines a function to calculate final prices based on categorical tax rates:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:custom="http://example.com/shop"
exclude-result-prefixes="xs custom">
<xsl:output method="xml" indent="yes"/>
<!-- Custom Function Definition -->
<xsl:function name="custom:apply-tax" as="xs:decimal">
<xsl:param name="base-price" as="xs:decimal"/>
<xsl:param name="category" as="xs:string"/>
<xsl:variable name="rate" as="xs:decimal">
<xsl:choose>
<xsl:when test="$category = 'standard'">0.20</xsl:when>
<xsl:when test="$category = 'reduced'">0.05</xsl:when>
<xsl:otherwise>0.00</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:sequence select="$base-price * (1 + $rate)"/>
</xsl:function>
<!-- Template Invoking the Function in XPath -->
<xsl:template match="/inventory">
<priced-inventory>
<xsl:for-each select="item">
<product id="{@id}">
<name><xsl:value-of select="name"/></name>
<total-price>
<xsl:value-of select="custom:apply-tax(xs:decimal(price), category)"/>
</total-price>
</product>
</xsl:for-each>
</priced-inventory>
</xsl:template>
</xsl:stylesheet>Key Considerations for Using xsl:function
- No Context Item Inside Functions: Unlike
<xsl:template>, an<xsl:function>does not have an implicit context node (.). All required data nodes must be passed explicitly as parameters. - Direct XPath Usability: Functions can be used
inside filter predicates (e.g.,
item[custom:is-eligible(.)]), sorting keys (<xsl:sort select="custom:format-date(date)"/>), and variable selections. - Side-Effect Free: Functions in XSLT are purely functional; they calculate and return values without altering global state or producing direct document side effects outside their return sequence.