XSLT 2.0 and 3.0 beyond version 1.0
Grouping, regular expressions, functions, maps and error handling are the features that make the upgrade worth the engine change.
Grouping, sequences and functions
<xsl:for-each-group select="item" group-by="@category">
<section>
<h2><xsl:value-of select="current-grouping-key()"/> (<xsl:value-of select="count(current-group())"/>)</h2>
<xsl:for-each select="current-group()">
<p><xsl:value-of select="title"/></p>
</xsl:for-each>
</section>
</xsl:for-each-group>
<!-- Sequence operations replace the 1.0 node-set gymnastics -->
<xsl:value-of select="string-join(//item/title, ', ')"/>
<xsl:value-of select="distinct-values(//item/@category)"/>
<xsl:value-of select="sum(//item/price)"/>
<xsl:value-of select="(//item order by price descending)[1]/title"/>
<!-- Regex -->
<xsl:value-of select="replace(@slug, '[^a-z0-9-]', '-')"/>
<xsl:if test="matches(title, '^\s*\[draft\]')">draft</xsl:if>
<xsl:value-of select="tokenize(@tags, '\s*,\s*')[1]"/>
<!-- A user-defined function -->
<xsl:function name="local:initials" as="xs:string">
<xsl:param name="name" as="xs:string"/>
<xsl:sequence select="string-join(for $w in tokenize($name, '\s+') return substring($w, 1, 1), '')"/>
</xsl:function>Maps, arrays and error handling
<xsl:variable name="labels" as="map(xs:string, xs:string)" select="map {
'en': 'Contents',
'fr': 'Sommaire'
}"/>
<xsl:value-of select="$labels('en')"/>
<xsl:variable name="rows" as="array(xs:string)" select="['a', 'b', 'c']"/>
<xsl:value-of select="$rows(2)"/>
<!-- Structured error handling replaces terminate="yes" for recoverable cases -->
<xsl:try>
<xsl:variable name="total" select="sum(//item/price)"/>
<xsl:sequence select="$total"/>
<xsl:catch>
<xsl:message>price sum failed: <xsl:value-of select="$err:description"/></xsl:message>
<xsl:sequence select="0"/>
</xsl:catch>
</xsl:try>| Feature | 1.0 workaround | 2.0 / 3.0 form |
|---|---|---|
| Grouping | Muenchian keys and generate-id() | for-each-group |
| Distinct values | A key plus a counting trick | distinct-values() |
| Joining strings | Recursive named template | string-join() |
| Regex | Extension functions | matches, replace, tokenize |
| Sorting a sequence | Keys and position tricks | order by |
| Custom functions | Named templates with parameters | xsl:function |
| Error recovery | terminate="yes" only | xsl:try and xsl:catch |
| Lookup tables | Document nodes and keys | Maps and arrays |
⚠️
In an XSLT 2.0 or 3.0 stylesheet without
version="1.0", several 1.0 idioms change meaning: 1 div 0 is an error, comparing two multi-node sequences is an error, and numeric literals are decimals rather than doubles. Test the whole stylesheet after the upgrade, not just the new templates.FAQ
Can I use XSLT 2.0 in a browser?
Not natively. SaxonJS compiles a stylesheet to JavaScript and runs in the browser, but it is a separate runtime you have to ship and start.
Does the version attribute change parsing or behaviour?
It selects the language version. version="1.0" on a 2.0 engine gives you backwards-compatible semantics, and version="3.0" on a 1.0 engine is treated as 1.1 with a warning.
Related
XSLT versions, engines and where transformations run Streaming very large XML documents
Last refreshed 2026-09-18.