What is happening with XSLT
39C3 - Bits und Bäume Workshop
Juri Leino
- Intro
- Brief History
- Current Drama
- Overview of QT4CG activities
- Summaries of changed specs
- A late breaking change
Agenda
Brief History
XSLT 3.0 followed soon after in 2017
XSLT 1.0 was released 1999
XSLT 2.0 became W3C recommendation in 2007
- Started 6th September 2022
- 140 weekly (Zoom) meetings thus far
- ~12 active members
- >2000 issues and pull requests
- ~ 1 year to final feedback and completion
start
finish
QT4 Community Group
qt4cg.org — A Community Group under W3C
start
finish
- Started 6th September 2022
- 140 weekly (Zoom) meetings thus far
- ~12 active members
- >2000 issues and pull requests
- ~ 1 year to final feedback and completion
QT4 Community Group
qt4cg.org — A Community Group under W3C


QT4 Community Group
qt4cg.org — A Community Group under W3C
start
finish
- Started 6th September 2022
- 140 weekly (Zoom) meetings thus far
- ~12 active members
- >2000 issues and pull requests
- ~ 1 year to final feedback and completion


How to find changes?
Sections with significant changes in the TOC
New functions

Previous/ Next
Links to Issue and PR
How to find changes?
- Process (nested) maps and arrays as easily as node trees
- Full processing of (deep) JSON as a target Syntax and function additions to improve ease of use
- Minimal backwards incompatibility
Moving from 3.1 to 4.0
XPath
Functions + 0perators
XQuery
XSLT
XPath
XPath
-
Better support for nested maps/arrays
- JNode – an array/map entry with ‘axis-relationships’
- Deep lookup operators
- Maps are now ordered
- Structured Record (map) types
- Improved ‘pipelines’ => -> =!>
Functions and Operators
Functions and Operators
- mandatory higher-order functions
- EXPath File and Binary modules adopted and refined
- ~90 new functions
- ~60 functions extended
- improved consistency
- options records
XQuery
XQuery
- Declare custom records and enums
- Declare functions without prefix
- Use Qname literals in computed element constructors
- Optional function parameters
XSLT
XSLT
-
Syntactic additions to make stylesheets cleaner
- Enclosing modes
- Declared item/record types
- Better support for maps/arrays, including template matching
- Potential freezing of streaming support
A late breaking change...
Nodes
Tree of Nodes
<root> <child>a</child> <child /> </root>
document - element(root) - element(child) - text(a) - element(child)
$document//child
( <child>a</child>, <child /> )
XNodes
XTrees
XNodes
GNodes
GNodes
JNodes
XNodes
GTree
JTree
XTree
JNodes
parent a reference to the parent JNode in the tree
content the value of the array member or map entry
selector the map key or array index
position needed to handle "mixed" content
JNodes
jtree()
jnode-content()
jnode-selector()
jnode-position()
JNodes
[ "a", "b", "c"]
/*[1]
/..
/*[last()]
/string()JNodes
jtree([ "a", "b", "c"])
/*[1]
/..
/*[last()]
/string()JNodes
{ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //languages
JNodes
{ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //child:get("languages")
JNodes
{ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //languages/jnode-content()
JNodes
XNodes
<node id="something">
content
</node>
{
'key': [1,2,3]
}Fiddle
QT4 Tutorial
XPath
XPath
The map-Keyword
{ "this": "is", "a": "map" }mapXPath
The map-Keyword
{ "this": "is", "a": "map" }XPath
Lookups with ?
{ "test 1": 10 }?("test 1"){ 1: 10 }?1
{ "test": 10 }?testXPath
Lookups with ?
{ "test 1": 10 }?("test 1")XPath
Lookups with ?
{ "test 1": 10 }? "test 1"XPath
Lookups with ?
{ "test 1": 10 }?($property)XPath
Lookups with ?
{ "test 1": 10 }? $propertyXPath
Arrow operator
EXPR => my:f() => my:g()
my:g(my:f(EXPR))
XPath
Arrow operator
1 to 3 => sum() => math:pow(2) => function ($i) { ``[The square of the sum is `{$i}`]`` }()
XPath
1 to 3 => for-each(math:pow(?,2)) => sum() => function ($i) { ``[The sum of squares is `{$i}`]`` }()
Arrow operator
XPath
ArrowBang
1 to 3 =!> math:pow(2) => sum() => function ($i) { ``[The sum of squares is `{$i}`]`` }()
XPath
Arrow shortfalls
1 to 3 ! math:pow(2,.)
1 to 3
=!> fn($a){math:pow(2, $a)}()
function argument order
XPath
Arrow shortfalls
1 to 3 ! (. + 2)
1 to 3
=!> fn($a){ $a + 2 }()
RHS has to be a function call
XPath
Pipeline operator
3 -> math:pow(2, .)
EXPR -> EXPR
XPath
string templates
{ "number": 1, "name": "template" }
-> `This is {?number} string {?name}`not a string constructor
XPath
fn-Keyword
let $g := fn() {}
let $f := function () {}return ($f(), $g())
XPath
Focus Functions
let $id := fn{ . }
$id(1)
no arguments but the context value
XPath
Conditionals I
if (COND) { "COND was true" }
braced-if (without else)
XPath
Conditionals II
EXPR otherwise "fallback value"otherwise
XPath
type checks I
fn ($e as element()) {
(: do something with the element :)
}element wildcards
XPath
type checks I
fn ($e as element(*)) {
(: do something with the element :)
}element wildcards
XPath
type checks II
fn ($e as element(*:div)) {
(: work with all the divs :)
}element wildcards
XPath
type checks II
declare default namespace ##any; fn ($e as element(div)) { (: work with all the divs :) }
element wildcards
XPath
type checks III
//element(div|section)
(: select divs and sections :)
UnionNodeTests
XPath
type checks IV
fn ($input as (map(*)|array(*))) { $input?1 }
ChoiceItemTypes
XPath
Destructuring I
let ($head, $tail) := (1 to 3) return ($tail, $head)
Destructuring a sequence
XPath
Destructuring I
let ($p, $q) := (1 to 3) return ($p, $q)
Destructuring a sequence
XPath
Destructuring II
let $m := { "yksi": 1, "kaksi": 2 }
let ${$yksi} := $m
return $yksi
Destructuring a map
XPath
Destructuring III
let $[$uno, $dos] := [ 23, 42 ] return $uno * $dos
Destructuring an array
XPath
Methods
$my:pool =?> area()
record =?> function(record, *)
$my:pool?area($my:pool)
Functions and Operators
Position Parameter
fn:filter( $input as item()*, $predicate as fn(item(), xs:integer) as xs:boolean? ) as item()*
XQuery
Context I
- context item -> context value
- set context default value(s)
declare context value as document() external := document { (: the default :) }
Context II
declared namespaces: xs, fn, err
declare namespace array="???"; array:filter([1,-3,4,0], fn { math:abs(.) > 1 })
Context II
array:filter([1,-3,4,0], fn { math:abs(.) > 1 })
declared namespaces: xs, fn, err, map, array, math
Context II
declare option output:method "json";
array:filter([1,-3,4,0], fn {
math:abs(.) > 1
})declared namespaces: xs, fn, err, map, array, math, output
switch I
switch ($n) { case "one" return xs:double(1) case "two" return xs:double(2) default return xs:double('NaN') }
switch with curly braces
switch II
switch ($n) {
case "one", "un" return xs:double(1)
case "two" return xs:double(2)
default return xs:double("NaN")
}multiple cases
Declare Custom Types
declare type my:binary as (xs:base64binary | xs:hexBinary);
enum
declare type my:color as enum(
"red", "green", "blue"
)Declare Custom Types
record
declare type my:rectangle as record(
height as xs:decimal,
width as xs:decimal
)Declare Custom Types
record
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal
)Declare Custom Types
extendable record
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal,
*
)Declare Custom Types
record with a method
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal,
area as fn(my:rectangle) as xs:decimal := fn ($self) {
$self?height × $self?width
}
)Declare Custom Types
record with a method that is a focus function
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal,
area as fn(my:rectangle) as xs:decimal := fn {
?height × ?width
}
)Declare Custom Types
try-catch with error map
try {
error(QName("local:error"),
"I can't let you do that, Dave.",
{ "custom": true() })
} catch * {
trace($err:map)
}Errors
{
'code': 'local:error',
'description': 'I can't let you do that, Dave.',
'value': { "custom": true() },
'lineNumber': 2,
'columnNumber': 4,
'module': 'my-module.xq',
'stacktrace': …,
⋮
}Errors
try-catch with finally
try {
let $i := 0 return 1 div $i
} catch * {
$err:description
} finally {
message(`1 div {$i}`) }
Finally
XSLT
<xsl:stylesheet
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:err="http://www.w3.org/2005/xqt-errors"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="4.0"
>
...
<xsl:choose> <xsl:when test="$n ge 0"> <xsl:sequence select="1"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="-1"/> </xsl:otherwise> </xsl:choose>
<xsl:if test="$n ge 0" then="1" else="-1" /><xsl:variable name="n" select="a"/> <xsl:choose> <xsl:when test="$n = (1, 2, 3, 4)"> <xsl:sequence select="../small"/> </xsl:when> <xsl:when test="$n = (5 to 20)"> <xsl:sequence select="../medium"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="../large"/> </xsl:otherwise> </xsl:choose>
Switch
<xsl:variable name="n" select="a"/> <xsl:switch select="$n"> <xsl:when test="1, 2, 3, 4" select="../small"/> <xsl:when test="5 to 20" select="../medium"/> <xsl:otherwise select="../large"/> </xsl:switch>
Switch
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" fixed-namespaces="#default" exclude-result-prefixes="#all" version="4.0"> ...
<xsl:choose> <xsl:when test="$n lt $low"> <xsl:sequence select="../below"/> </xsl:when> <xsl:when test="$n gt $high"> <xsl:sequence select="../above"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="../within"/> </xsl:otherwise> </xsl:choose>
<xsl:choose> <xsl:when test="$n lt $low" select="../below"/> <xsl:when test="$n gt $high" select="../above"/> <xsl:otherwise select="../within"/> </xsl:choose>
<xsl:function
name="f:book-to-json"
as="map(*)">
<xsl:param name="book" as="element(book)">
<xsl:sequence select="{
'title' : string($book/title),
'author' : array { $book/author ! string() },
'date' :
let $d := $book/publication
return
if (current-date() lt xs:date($d))
then $d
else 'Don't release'
}"/>
</xsl:function><xsl:function
name="f:book-to-json"
as="map(*)">
<xsl:param name="book" as="element(book)">
<xsl:sequence select="{ 'title' : string($book/title), 'author' : array { $book/author ! string() }, 'date' : let $d := $book/publication return if (current-date() lt xs:date($d)) then $d else 'Don't release' }"/>
</xsl:function><xsl:function name="f:book-to-json" as="map(*)"> <xsl:param name="book" as="element(book)"> <xsl:select> { 'title' : string($book/title), 'author' : array { $book/author ! string() }, 'date' : let $d := $book/publication return if (current-date() lt xs:date($d)) then $d else 'Don't release' } </xsl:select> </xsl:function>
<xsl:for-each-group
select="1 to 4, 5, 7 to 9, 10 to 12, 14, 15, 20 to 22"
split-when="$next ne ($group[last()] + 1)">
<group>{ current-group() }</group>
</xsl:for-each-group><xsl:for-each-group
select="1 to 4, 5, 7 to 9, 10 to 12, 14, 15, 20 to 22"
split-when="$next ne ($group[last()] + 1)">
<group>{ current-group() }</group>
</xsl:for-each-group><xsl:for-each-group
select="1 to 4, 5, 7 to 9, 10 to 12, 14, 15, 20 to 22"
split-when="$next ne ($group[last()] + 1)">
<group>{ current-group() }</group>
</xsl:for-each-group><group>1 2 3 4 5</group> <group>7 8 9 10 11 12</group> <group>14 15</group> <group>20 21 22</group>
<xsl:function name="f:inRange"> <xsl:param name="n" as="element()"/> <xsl:param name="low" required="no" select="0"/> <xsl:param name="high" required="no"> <xsl:call-template name="findNumberLimit"/> </xsl:param> <!-- the actual function --> </xsl:function>
#AMA
What is happening with XSLT?
By Juri Leino
What is happening with XSLT?
- 13