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" ] }
}
//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-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 }?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()*

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

101 Fun things to do with QT4

🫶🏼
juri@jinntec.de

QT4 @ XMLP 2026

By Juri Leino

QT4 @ XMLP 2026

  • 55