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" }
map

XPath

The map-Keyword

   { "this": "is", "a": "map" }

XPath

Lookups with ?

{ "test 1": 10 }?("test 1")
{ 1: 10 }?1
{ "test": 10 }?test

XPath

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 }? $property

XPath

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&apos;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&apos;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&apos;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