XPath and XQuery 4.0 Tutorial
XML Prague 2026
Juri Leino — Jinntec GmbH
#AMA

Thx

- Intro
- brief Overview of QT4CG activities
- Summaries of changed specs
- XPath
- Functions and operators
- XQuery
- XSLT*
Agenda
- 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
- final feedback starting in Q1 of 2027
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 + Operators
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
- new ‘pipelines’ => -> =!>
Functions and Operators
Functions and Operators
- mandatory higher-order functions
- EXPath File and Binary modules adopted and refined
- 99 new functions
- 71 functions extended or changed
- 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
- Declare context values
XSLT
XSLT
- Syntactic additions to make stylesheets cleaner
- Enclosing modes
- Declared item/record types
- Better support for maps/arrays, including template matching
A late breaking change...
Nodes
Trees 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
JNodes
XNodes
GNodes
JTree
XTree
GTree
JNodes
parent a reference to the parent JNode in the tree
value the value of the array member or map entry
key the map key or array index
position needed to handle "mixed" content
JNodes
jtree()
jnode-value()
jnode-key()
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" ] }
}
//languagesJNodes
{
"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-value()
JNode
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 }?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()*
Optional Parameters
declare function my:fn ($a, $b) { $a, $b };
declare function my:fn ($a) { my:fn($a, ()) };
Optional Parameters
declare function my:fn ($a, $b) { $a, $b };
declare function my:fn ($a) { my:fn($a, ()) };
my:fn(12)
Named Parameters
declare function local:many-parameters (
$a := 1, $b := 1, $c := 1, $d :=1
) {
$a + $b * $c * $d
};
local:many-parameters()Named Parameters
declare function local:many-parameters (
$a := 1, $b := 1, $c := 1, $d :=1
) {
$a + $b * $c * $d
};
local:many-parameters(c:=2, a:=0)Options maps/records
doc($uri, $options)
parse-csv("a|b|c", {
"field-delimiter": "|"
})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
Text
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>
101 Fun things to do with QT4
🫶🏼
juri@jinntec.de
QT4 @ XMLP 2026
By Juri Leino
QT4 @ XMLP 2026
- 55