101 Fun things to do with QT4

By Michael Kay on May 26, 2026 at 06:00p.m.

Here's a fun list of new features in the XPath 4.0, XQuery 4.0, and XSLT 4.0, that are available to play with in Saxon 13.

  1. Select an attribute, using a default value if absent

    @discount otherwise 0

  2. Use a conditional with no else branch

    if (@discount) { " (reduced!)" }

  3. Shorten function to fn

    for-each(//employee, fn($e){$e/@ssn})

  4. Use the context item to simplify arity-1 functions

    for-each(//employee, fn{@ssn})

  5. Drop the map keyword from map constructors

    let $map := {'x':1, 'y':2}

  6. Include conditional entries in a map constructor

    {'x': +@x, 'y': +@y, if (@z) {{'z': +@z}}}

  7. Include multiple entries in a map constructor

    {//data ! {@key : string(@value)}}

  8. Build a map from a sequence

    map:build(//employee, fn{@ssn})

  9. Select a range of entries from a sequence

    $input[1 to 5]

  10. Start talking about atomic items rather than atomic values

  11. Use QName literals

    node-name($node) = #xml:space

  12. Use underscore in numeric literals

    let $million := 1_000_000

  13. Use hex literals

    let $mask := 0xffff

  14. Use alternative names in an element test

    //element(chapter|appendix)

  15. Declare namespaces in XPath

    declare namespace p = "http:/my.ns"; //p:table

  16. Declare a default namespace in XPath

    declare default element namespace "http:/my.ns"; //table

  17. Use ##any to match elements by local name, ignoring namespace

    declare default element namespace "##any"; //table

  18. Use string templates

    `{$firstname} {$lastname}`

  19. Use × and ÷ for multiplication and division

    @price × 1.2

  20. Convert untyped atomic values implicitly to decimal where appropriate

    <a>1.1</a> = 1.1

  21. Write << and >> as precedes and follows

    para[. precedes $first-heading]

  22. Compare nodes using is-not

    *[. is-not $first-heading]

  23. Use the =!> operator to apply a function to all items in a sequence

    tokenize($str) =!> upper-case()

  24. Use the pipeline operator -> to set the context item for an expression

    $employee -> `First name: {@first} Last name: {@last}`

  25. Use a multi-character representation of "percent" in a decimal format

    percent = "%:pc"

  26. Start talking about coercion rules rather than function conversion rules

  27. Take advantage of down-casting in the coercion rules:

    declare function my:f($x as xs:positiveInteger){$x + 1}; my:f(3)

  28. Check that you always have a space after "{#" in a pragma

    {# saxon:extension xxx #}

  29. Take advantage of coercion in variable declarations

    let $x as xs:decimal := @price

  30. Declare functions with optional parameters

    declare function my:f($x as xs:integer := 0)

  31. Use keywords in function calls

    substring($value, start := 3)

  32. Declare functions with no namespace prefix

    declare function increment($x) {$x+1}; increment(42)

  33. Serialize output as canonical XML

    serialize($output, {'method': 'xml', canonical': true()})

  34. Take advantage of the functions in the bin and file namespaces. Previously defined in EXPath, these are now integrated into the specs.

  35. Watch out for decimal=double comparisons. These are now compared as decimals, not as doubles

    1.1e0 != 1.1

  36. Convert arbitrary XML to JSON using the element-to-map function

    element-to-map(doc('books.xml')/*)

  37. Process CSV input files using the new csv-doc and parse-csv functions

    csv-doc('data.csv')

  38. Process HTML input files

    parse-html('index.html')

  39. Use higher-order functions knowing they will be there in all implementations. Higher-order functions are no longer an optional feature.

  40. Use the second argument of callback functions to get the position of an item in a sequence

    filter($input, fn{$item, $pos){$pos gt 5})

  41. Get the last item of a sequence using foot()

    foot($input)

  42. Get selected items in a sequence using slice()

    slice($in, start := 5, end := 2, step := -2)

  43. Use the deep-equal() function with options to control the comparison

    deep-equal($p1, $p2, {'whitespace': 'normalize'})

  44. Use the all-different() function to check uniqueness of values

    all-different(//test-case/@name)

  45. Use the highest() and lowest() functions to find the items with the minimum or maximum value of some property

    highest(//employee, fn{@salary})

  46. Use the index-where() function to find the positions of all items with some property

    index-where(*, fn{exists(self::h2)})

  47. Use the partition() function to perform positional grouping

    partition(*, fn($group, $next){exists($next[self::h2])})

  48. Use the sort-by() function to sort a sequence using multiple sort keys

    sort-by(//person, ({'key': fn{@last}}, {'key': fn{@date-of-birth}, 'order': 'descending'}))

  49. Use the sort-with() function to sort using a comparator

    sort-with($input, compare#2),

  50. Use take-while to select items from a sequence until some condition is false

    take-while(*, fn{not(self::h2)})

  51. Use is-NaN to check whether an input item is NaN

    *[not(is-NaN(.))]

  52. Use round() with an explicit rounding mode

    round($value, precision := '2', mode := 'half-toward-zero')

  53. Read integers in hexadecimal notation

    parse-integer("feff", 16)

  54. Format integers in hexadecimal notation

    format-integer($value, '16^xxxxxxxx')

  55. Supply inline options to format-number()

    format-number($value, {'decimal-separator': ',', 'grouping-separator': '.'})

  56. Use new mathematical functions math:e(), math:sinh(), etc.

    math:cosh(1)

  57. Use the new collation for Unicode case-blind comparison

    compare($a, $b, "http://www.w3.org/2005/xpath-functions/collation/unicode-case-insensitive")

  58. Construct a collation with desired properties

    compare($a, $b, collation({'lang': 'se', 'numeric': true()}))

  59. Get the character with a given codepoint

    char(0xA0)

  60. Get the character with a given HTML entity name

    char("nbsp")

  61. Get all the characters in a string

    characters($input)

  62. Use lookahead and lookbehind in regular expressions

    matches($input, "Chapter(?=\s+[1-9])")

  63. Match word boundaries in a string

    matches($input, "\bthe\b")

  64. Compute replacement values for matching substrings

    replace($input, "(0-9)+", fn{.+1})

  65. Split a URI into its parts, and recombine them

    parse-uri(@href) => map:put('scheme', 'https') => build-uri()

  66. Build a date or time from its components

    build-dateTime({'year': $year, 'month': $month, 'day': $day})

  67. Get the civil timezone offset at a given time and place

    civil-timezone(current-dateTime(), 'Europe/London')

  68. Use @then and @else on xsl:if

    <xsl:if test="@type=1" then="@full-name" else="@abbreviation"/>

  69. Use @select on xsl:when and xsl:otherwise

    <xsl:choose><xsl:when test="@type=1" select="'gasket'"/></xsl:choose>

  70. Select options using the new xsl:switch instruction

    <xsl:switch select="@type"><xsl:when test="1" select="'gasket'"/></xsl:switch>

  71. Provide default values for XSLT function parameters

    <xsl:param name="options" as="map(*)" required="no" select="{}"/>

  72. Use XSLT functions in no namespace

    <xsl:function name="f">...</xsl:function>

  73. Use xsl:text in preference to xsl:value-of

    <xsl:text>{position()} of {last()}</xsl:text>

  74. Construct CDATA sections selectively in serialized output

    <xsl:text cdata="true" select="$example"/>

  75. Use capturing accumulators to capture a snapshot of an element as an accumulator value

    <xsl:accumulator-rule match="heading" phase="end" capture="yes" select="."/>

  76. Use explicit options to control XML parsing in the doc() and document() functions

    doc("input.xml", {'dtd-validation': true()})

  77. Build arrays in XSLT using xsl:array and xsl:array-member

    <xsl:array select="1 to 5"/>

  78. Control the handling of duplicate keys in xsl:map with the new duplicates option

    <xsl:map duplicates="'use-last'">...</xsl:map>

  79. Stop JSON output displaying / as \/

    escape-solidus="no"

  80. Serialize a sequence of maps in json-lines format (one JSON object per line, newline separated)

    json-lines="yes"

  81. Use different XSD schemas for validating the input and output of a transformation

    <xsl:import-schema role="output"/>, <xsl:result-document schema-role="output"/>

  82. Write all the template rules for a given mode as children of the xsl:mode element

    <xsl:mode name="M"><xsl:template match="A"/></xsl:mode>

  83. In an XSLT module, link to the main module to help IDEs locate all the variable and function declarations

    <xsl:transform main-module="main.xsl">...</xsl:transform>

  84. Use xsl:note elements for structured XSLT documentation

    <xsl:note>This is a comment</xsl:note>

  85. Use fixed-namespaces to reduce the amount of boilerplate on the xsl:transform element

    <xsl:transform fixed-namespaces="#standard">...</xsl:transform>

  86. Take advantage of new features for simplified stylesheets. The outermost element can now be any instruction, for example xsl:result-document or xsl:map.

  87. Make unprefixed name tests match elements in any namespace

    xpath-default-namespace="##any"

  88. Declare named item types

    <xsl:item-type name="binary" select="(xs:hexBinary | xs:base64Binary)"/>

  89. Use types in match patterns

    match="~map(xs:string, xs:integer)

  90. Use the separator attribute in xsl:for-each

    <xsl:for-each select="$input" separator=",">...</xsl:for-each>

  91. Use record types to declare maps with statically known fields

    as record(first as xs:string, middle as xs:string*, last as xs:string)

  92. Declare function parameters using choice types

    as (xs:date | xs:time | xs:dateTime)

  93. Declare function parameters using enumeration types

    as enum("red", "green", "blue")

  94. Use path expressions to navigate trees of maps and arrays (JNodes)

    $map/authors/*[1]/name/first ! string()

  95. Iterate over arrays with an enhanced FLWOR expression

    for member $m in $array ... return count($m)

  96. Iterate over maps with an enhanced FLWOR expression

    for key $key value $value in $map return `{$key} = {$value}`

  97. Use computed node constructors in XPath

    attribute #xlink:href {$target}

  98. Use enhanced for and let expressions in XPath

    for $x as xs:string at $pos in $X let $r := string-length($x) + $pos return $r + 1

  99. Use destructuring map assignments

    let {$height, $width} := $rectangle return $height × $width

  100. Use destructuring array assignments

    let [$first, $second, $third] := $array return ($first, $second, $third)

  101. Read the specifications at http://qt4cg.org/, and provide feedback at https://github.com/qt4cg/qtspecs