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>
Feature1.0 workaround2.0 / 3.0 form
GroupingMuenchian keys and generate-id()for-each-group
Distinct valuesA key plus a counting trickdistinct-values()
Joining stringsRecursive named templatestring-join()
RegexExtension functionsmatches, replace, tokenize
Sorting a sequenceKeys and position tricksorder by
Custom functionsNamed templates with parametersxsl:function
Error recoveryterminate="yes" onlyxsl:try and xsl:catch
Lookup tablesDocument nodes and keysMaps 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.

XSLT versions, engines and where transformations run Streaming very large XML documents

Last refreshed 2026-09-18.