Moving to XSLT 2.0 and 3.0¶
You know XSLT 1.0 well. The good news is that everything you have learned still
applies — 2.0 and 3.0 are supersets. The same templates, the same
apply-templates, the same XPath. What changes is that several things that were
awkward or impossible in 1.0 become natural, and a few rough edges (result tree
fragments, no user functions, no regex) simply disappear.
This page is a map of what is new and why it matters. The pages that follow go into each topic in depth.
Some of these features need a paid Saxon edition
Most of this section runs in the free, open-source Saxon-HE — including
higher-order functions, maps and arrays,
JSON, error handling, accumulators, and
xsl:iterate. But three features require a licensed (commercial) Saxon
edition and will not run on Saxon-HE:
- Streaming — Saxon-EE only.
- Packages (
xsl:package/xsl:use-package) — Saxon-EE only. - Schema-aware processing — validating against an XSD
so data carries typed values, and the
schema-element()tests — Saxon-EE only. The plainas=type syntax itself works in HE.
Each affected page repeats this at the top. Source: the Saxon 12 feature matrix.
Declaring the version¶
You switch versions with one attribute on the stylesheet element:
version="3.0"(or"2.0") instead of"1.0". That is the whole switch.- The
xs:namespace is what lets you name types likexs:stringandxs:integerinas=attributes (see below).
Browsers do 1.0 only; you need Saxon
Web browsers ship a 1.0 processor and nothing newer, so a stylesheet
declaring version="2.0" or "3.0" will not run in the browser. For
2.0/3.0 you run the transformation through a modern processor — Saxon is
the reference implementation and the one these pages assume. The version
declaration is also a request, not a guarantee: a 1.0 engine handed a
version="3.0" stylesheet will fall back to 1.0 behaviour rather than error.
The headline change: sequences¶
XSLT 1.0 worked on node-sets — unordered collections of nodes, and nothing else. A value was either a node-set, a string, a number, or a boolean, and these lived in separate worlds.
2.0 and 3.0 replace the node-set with the sequence: an ordered list of items, where each item is either a node or an atomic value, and the two may be mixed freely. A single string is just a sequence of length one. This one idea removes a surprising amount of friction.
<xsl:variable name="nums" select="(3, 1, 2)"/> <!-- (1)! -->
<xsl:variable name="mixed" select="('total', sum(catalog/cd/price))"/> <!-- (2)! -->
<xsl:variable name="titles" select="catalog/cd/title"/> <!-- (3)! -->
- A sequence of three integers, in the order written.
- A sequence mixing a string and a number — impossible as a 1.0 node-set.
- A sequence of nodes, just like an old node-set, but now ordered.
The most practical payoff: a temporary tree built with the content form of
xsl:variable is a real, navigable node tree. In 1.0 the same construct gave
you a result tree fragment you could copy into the output but not apply XPath
steps to without a node-set() extension (see Variables). In
2.0/3.0 that restriction is gone — you can select into it directly:
<xsl:variable name="cheap"> <!-- (1)! -->
<cd><title>Sample One</title><price>5.50</price></cd>
<cd><title>Sample Two</title><price>7.25</price></cd>
</xsl:variable>
<xsl:value-of select="$cheap/cd[price < 6]/title"/> <!-- (2)! -->
- A temporary tree — the content form, exactly as in 1.0.
- In 1.0 this XPath into
$cheapwould have been an error. In 2.0/3.0 it just works.
Sample One
Typing values with as=¶
In 1.0 nothing had a declared type; a wrong assumption surfaced late, as
mangled output. 2.0/3.0 let you state the sequence type of a variable,
parameter, or function result with an as= attribute. The processor then checks
it, so mistakes show up earlier and with a clear message.
A sequence type is an item type plus an optional cardinality marker:
| Marker | Means |
|---|---|
| (none) | exactly one |
? |
zero or one |
* |
zero or more |
+ |
one or more |
The item type can be an atomic type (xs:string, xs:integer, xs:decimal,
xs:date, …) or a node test such as element(cd).
- Exactly one integer. If the
selectproduced something else, the processor complains here rather than later. - A typed parameter, with a default; a caller may still override it.
- Zero or more
cdelements —element(cd)is the item type,*the cardinality.
Types catch mistakes early
as="xs:integer" on a variable you intend to count with means a stray
decimal or empty sequence is reported at the point of binding, with a type
error, instead of silently flowing into your output. Adding as= to the
public parameters and functions of a stylesheet is cheap insurance.
xsl:sequence vs xsl:value-of¶
xsl:value-of has always produced a text node: it takes its value and
flattens it to a string. 2.0/3.0 add xsl:sequence, which returns the actual
items — nodes or atomic values — without that flattening. It is how you
return a real result from a function or a template, rather than its string form.
<xsl:value-of select="catalog/cd[1]"/> <!-- (1)! -->
<xsl:sequence select="catalog/cd[1]"/> <!-- (2)! -->
- Emits the string value of the first
cd— its text, run together. - Returns the
cdelement itself — still a node, with structure intact.
New XPath expressions¶
XPath grew a small set of expressions you will reach for constantly:
for $p in catalog/cd/price return $p * 1.1 <!-- (1)! -->
let $n := count(catalog/cd) return $n div 2 <!-- (2)! -->
some $p in catalog/cd/price satisfies $p > 10 <!-- (3)! -->
every $p in catalog/cd/price satisfies $p > 0 <!-- (4)! -->
catalog/cd/price => sum() <!-- (5)! -->
for … returnmaps over a sequence, producing a new sequence.let … returnbinds a value inline (2.0 in XQuery, available in XSLT viaxsl:variable; the inlineletis an XPath 3.0 expression).some … satisfiesis true if any item matches.every … satisfiesis true if all items match.- The
=>arrow operator (3.0) feeds the left value as the first argument of the call on the right —catalog/cd/price => sum()issum(catalog/cd/price), but reads left-to-right and chains cleanly.
What else is new¶
A quick map of the bigger features, each covered on its own page:
| Feature | One-liner | Page |
|---|---|---|
| Sequences & types | The as= type system that underpins everything below |
sequences and types |
| User-defined functions | Write your own XPath functions with xsl:function |
functions |
| Higher-order functions | Functions as values: fold-left, filter, => |
higher-order functions |
| New functions | The 2.0/3.0/3.1 library additions, as a reference | new functions |
| Maps & arrays | Dictionaries and nested lists, the 3.1 data structures | maps and arrays |
| Reading & writing JSON | parse-json, maps & arrays, method="json" |
JSON |
| Grouping | xsl:for-each-group replaces the 1.0 "Muenchian" trick |
grouping |
| Regex & strings | matches, replace, tokenize, xsl:analyze-string |
regex |
| Non-XML text I/O | unparsed-text, parse-xml, serialize, CSV ⇄ XML |
text and parsing |
| Dates & times | Real date types, arithmetic, and format-date |
dates and times |
| Tunnel parameters | Pass values through a template chain invisibly | tunnel parameters |
| Conditional output | xsl:where-populated, xsl:on-empty/on-non-empty |
conditional output |
| Modern identity & text | Declarative xsl:mode identity and text value templates |
modern identity and text |
| Dynamic XPath | xsl:evaluate runs an XPath supplied at run time |
dynamic XPath |
| Multiple outputs | xsl:result-document writes many files in one run |
result documents |
| Error handling | xsl:try/xsl:catch, xsl:assert |
error handling |
| Streaming | Process documents too large to fit in memory | streaming |
| Packages | xsl:package — real modules with visibility |
packages |
1.0 vs 2.0/3.0 at a glance¶
| XSLT 1.0 | XSLT 2.0 / 3.0 | |
|---|---|---|
| Core value | Node-set (unordered) | Sequence (ordered, may mix nodes + atomics) |
| Temporary tree | Result tree fragment — needs node-set() to navigate |
Real node tree, navigable directly |
| Typing | None | as= sequence types, checked early |
| Returning items | xsl:value-of (text only) |
xsl:value-of or xsl:sequence (items) |
| Own functions | Named templates only | xsl:function (callable in XPath) |
| Grouping | Hand-rolled (Muenchian keys) | xsl:for-each-group |
| String tools | translate, substring-before/after |
matches, replace, tokenize, upper-case, … |
| XPath | Location paths, basic predicates | for / let / some / every, => (3.0) |
| Runs in | Every browser and processor | Saxon and other modern processors only |
Next¶
Sequences and types — the model under everything on
this list: what a sequence is, and how as= types turn silent bugs into
compile-time errors.