39C3 - Bits und Bäume Workshop
Juri Leino
XSLT 3.0 followed soon after in 2017
XSLT 1.0 was released 1999
XSLT 2.0 became W3C recommendation in 2007
start
finish
qt4cg.org — A Community Group under W3C
start
finish
qt4cg.org — A Community Group under W3C
qt4cg.org — A Community Group under W3C
start
finish
Sections with significant changes in the TOC
New functions
Previous/ Next
Links to Issue and PR
<root> <child>a</child> <child /> </root>
document - element(root) - element(child) - text(a) - element(child)
$document//child
( <child>a</child>, <child /> )
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
jtree()
jnode-content()
jnode-selector()
jnode-position()
[ "a", "b", "c"]
/*[1]
/..
/*[last()]
/string()jtree([ "a", "b", "c"])
/*[1]
/..
/*[last()]
/string(){ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //languages
{ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //child:get("languages")
{ "fr": { "capital": "Paris", "languages": [ "French" ] }, "de": { "capital": "Berlin", "languages": [ "German" ] } } //languages/jnode-content()
<node id="something">
content
</node>
{
'key': [1,2,3]
} { "this": "is", "a": "map" }map { "this": "is", "a": "map" }{ "test 1": 10 }?("test 1"){ 1: 10 }?1
{ "test": 10 }?test{ "test 1": 10 }?("test 1"){ "test 1": 10 }? "test 1"{ "test 1": 10 }?($property){ "test 1": 10 }? $propertyEXPR => my:f() => my:g()
my:g(my:f(EXPR))
1 to 3 => sum() => math:pow(2) => function ($i) { ``[The square of the sum is `{$i}`]`` }()
1 to 3 => for-each(math:pow(?,2)) => sum() => function ($i) { ``[The sum of squares is `{$i}`]`` }()
1 to 3 =!> math:pow(2) => sum() => function ($i) { ``[The sum of squares is `{$i}`]`` }()
1 to 3 ! math:pow(2,.)
1 to 3
=!> fn($a){math:pow(2, $a)}()
function argument order
1 to 3 ! (. + 2)
1 to 3
=!> fn($a){ $a + 2 }()
RHS has to be a function call
3 -> math:pow(2, .)
EXPR -> EXPR
{ "number": 1, "name": "template" }
-> `This is {?number} string {?name}`not a string constructor
let $g := fn() {}
let $f := function () {}return ($f(), $g())
let $id := fn{ . }
$id(1)
no arguments but the context value
if (COND) { "COND was true" }
braced-if (without else)
EXPR otherwise "fallback value"otherwise
fn ($e as element()) {
(: do something with the element :)
}element wildcards
fn ($e as element(*)) {
(: do something with the element :)
}element wildcards
fn ($e as element(*:div)) {
(: work with all the divs :)
}element wildcards
declare default namespace ##any; fn ($e as element(div)) { (: work with all the divs :) }
element wildcards
//element(div|section)
(: select divs and sections :)
UnionNodeTests
fn ($input as (map(*)|array(*))) { $input?1 }
ChoiceItemTypes
let ($head, $tail) := (1 to 3) return ($tail, $head)
Destructuring a sequence
let ($p, $q) := (1 to 3) return ($p, $q)
Destructuring a sequence
let $m := { "yksi": 1, "kaksi": 2 }
let ${$yksi} := $m
return $yksi
Destructuring a map
let $[$uno, $dos] := [ 23, 42 ] return $uno * $dos
Destructuring an array
$my:pool =?> area()
record =?> function(record, *)
$my:pool?area($my:pool)
fn:filter( $input as item()*, $predicate as fn(item(), xs:integer) as xs:boolean? ) as item()*
declare context value as document() external := document { (: the default :) }
declared namespaces: xs, fn, err
declare namespace array="???"; array:filter([1,-3,4,0], fn { math:abs(.) > 1 })
array:filter([1,-3,4,0], fn { math:abs(.) > 1 })
declared namespaces: xs, fn, err, map, array, math
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 ($n) { case "one" return xs:double(1) case "two" return xs:double(2) default return xs:double('NaN') }
switch with curly braces
switch ($n) {
case "one", "un" return xs:double(1)
case "two" return xs:double(2)
default return xs:double("NaN")
}multiple cases
declare type my:binary as (xs:base64binary | xs:hexBinary);
enum
declare type my:color as enum(
"red", "green", "blue"
)record
declare type my:rectangle as record(
height as xs:decimal,
width as xs:decimal
)record
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal
)extendable record
declare record my:rectangle (
height as xs:decimal,
width as xs:decimal,
*
)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
}
)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
}
)try-catch with error map
try {
error(QName("local:error"),
"I can't let you do that, Dave.",
{ "custom": true() })
} catch * {
trace($err:map)
}{
'code': 'local:error',
'description': 'I can't let you do that, Dave.',
'value': { "custom": true() },
'lineNumber': 2,
'columnNumber': 4,
'module': 'my-module.xq',
'stacktrace': …,
⋮
}try-catch with finally
try {
let $i := 0 return 1 div $i
} catch * {
$err:description
} finally {
message(`1 div {$i}`) }
<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>
<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>
<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>