View Old View New View Both View Only Previous Next

This draft contains only sections that have differences from the version that it modified.

W3C

XSL Transformations (XSLT) Version 4.0

W3C Editor's Draft 218 February 2026

This version:
https://qt4cg.org/specifications/xslt-40/
Latest version:
https://qt4cg.org/specifications/xslt-40/
Most recent Recommendation of XSL Transformations (XSLT):
https://www.w3.org/TR/xslt-30/
Editor:
Michael Kay, Saxonica <http://www.saxonica.com/>

The following associated resources are available: Specification in XML format, XSD 1.1 Schema for XSLT 4.0 Stylesheets (non-normative), Relax-NG Schema for XSLT 4.0 Stylesheets (non-normative), Stylesheet for XML-to-JSON conversion (non-normative)


Abstract

This specification defines the syntax and semantics of XSLT 4.0, a language designed primarily for transforming XML documents into other XML documents.

XSLT 4.0 is a revised version of the XSLT 3.0 Recommendation [XSLT 3.0] published on 8 June 2017. Changes are presented in 1.2 What’s New in XSLT 4.0?.

XSLT 4.0 is designed to be used in conjunction with XPath 4.0, which is defined in [XPath 4.0]. XSLT shares the same data model as XPath 4.0, which is defined in [XDM 3.0], and it uses the library of functions and operators defined in [Functions and Operators 4.0]. XPath 4.0 and the underlying function library introduce a number of enhancements, for example the availability of union and record types.

This document contains hyperlinks to specific sections or definitions within other documents in this family of specifications. These links are indicated visually by a superscript identifying the target specification: for example XP for XPath 4.0, DM for the XDM data model version 4.0, FO for Functions and Operators version 4.0.

Status of this Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document has no official standing. It is produced by the editor as a proposal for community review. Insofar as it copies large amounts of text from the W3C XSLT 3.0 Recommendation, W3C copyright and similar provisions apply.

Dedication

The publications of this community group are dedicated to our co-chair, Michael Sperberg-McQueen (1954–2024).


5 Features of the XSLT Language

5.5 Named Types

XSLT 4.0 introduces two new and closely-related constructs allowing item types to be given names, and to be referred to by name anywhere that item types are used, for example in function declarations and variable declarations.

The xsl:item-type declaration allows any item type to be given a name. It is particularly useful to avoid repetitive use of the same choice types, enumeration types, or function types, and means that if the definition changes, the change only needs to be made in one place.

The xsl:record-type declaration takes this a step further. Like xsl:item-type, it allows a name to be given to any record type. In addition, though, it offers two further capabilities:

  • Named record types can be recursive, allowing definitions of recursive data structures such as lists and trees.

  • Declaring a named record type automatically establishes a constructor function for records of that type; the constructor function has the same name as the record type itself.

5.5.2 Named Record Types

Changes in 4.0  

  1. Named record types are introduced.   [Issue 1485  16 January 2025]

<!-- Category: declaration -->
<xsl:record-type
  name = eqname
  constructor? = boolean〔'no'〕
  extensible? = boolean〔'no'〕
  visibility? = "private" | "public"〔'private'〕 >
  <!-- Content: (xsl:field*) -->
</xsl:record-type>

<xsl:field
  name = ncname
  as? = sequence-type〔'item()*'〕
  required? = boolean〔'yes'〕
  default? = expression />

An xsl:record-type declaration associates a name with a record type, and allows the record type to be referenced by name throughout the stylesheet package.

The following example declares a named record type for complex numbers, and uses it in a variable declaration and a function declaration.

<xsl:record-type name="cx:complex">
    <xsl:field name="r" as="xs:double"/>
    <xsl:field name="i" as="xs:double"/>
</xsl:record-type>
   
<xsl:variable name="i" as="cx:complex" select="cx:complex(0, 1)"/>
   
<xsl:function name="cx:add" as="cx:complex">
  <xsl:param name="x" as="cx:complex"/>
  <xsl:param name="y" as="cx:complex"/>
  <xsl:sequence select="cx:complex($x?r + $y?r, $x?i + $y?i)"/>
</xsl:function>

Note how the record type declaration has implicitly declared a constructor function cx:complex that can be used to create instances of the item type.

[ERR XTSE4050] It is a static error if the names of the fields in an xsl:record-type declaration are not distinct.

[ERR XTSE4051] It is a static error if an xsl:field element has a default attribute unless it specifies required="no".

An xsl:record-type declaration has two effects:

  • In the same way as xsl:item-type, it defines a named item type in the static context, allowing the record type to be referenced by name anywhere an ItemType can appear, for example in the declarations of functions and variables. Unlike types declared in xsl:item-type, however, named record types can be recursive.

  • An xsl:record-type declaration also implicitly defines a constructor function, of the same name, and adds this to the set of statically known function definitionsXP in the static context.

Because of its dual role, the name of an xsl:record-type declaration must be valid both as an item type name and as a function name. This means the name must be distinct from other type names and function names in the static context. It also means that the name must be in a namespace.

Considered as an item type, the xsl:record-type declaration is equivalent to an xsl:item-type declaration formed by the following template rule, evaluated in the context of a namespace alias <xsl:namespace-alias stylesheet-prefix="t" result-prefix="xsl"/>:

<xsl:template match="xsl:record-type" expand-text="yes">
   <t:item-type name="{@name}"
                visibility="{@visibility otherwise 'private'}">
     <xsl:attribute name="as">
        <xsl:text>record(</xsl:text>
        <xsl:for-each select="xsl:field" separator=", ">
           <xsl:variable name="optional"
                         as="xs:boolean"
                         select="normalize-space(@required) = ('no','false','0')"/>
           {@name}{'?'[$optional]} as {@as otherwise 'item()*'}
        </xsl:for-each>
        <xsl:if test="normalize-space(@extensible) = ('yes','true','1')">
           <xsl:text>, *</xsl:text>
        </xsl:if>
        <xsl:text>)</xsl:text>
      </xsl:attribute>  
   </t:item-type>
</xsl:template>

This generated xsl:item-type declaration must meet all the constraints placed on user-written xsl:item-type declarations, including the rule requiring names to be unique, but not including the rule disallowing recursive references.

For example, the declaration:

<xsl:record-type name="cx:complex" extensible="yes">
    <xsl:field name="r" as="xs:double"/>
    <xsl:field name="i" as="xs:double" required="no" default="0"/>
</xsl:record-type>

produces the equivalent item type declaration:

<xsl:item-type name="cx:complex"
       as="record(r as xs:double, i? as xs:double, *)"/>

Considered as a function declaration, an xsl:record-type declaration is equivalent to an xsl:function declaration formed by the following template rule, evaluated in the context of a namespace alias <xsl:namespace-alias stylesheet-prefix="t" result-prefix="xsl"/>:

<xsl:template match="xsl:record-type">
    <t:function name="{@name}"
                as="{@name}"
                visibility="{(@visibility otherwise 'private')}">
                
      <!-- if the record type is extensible, choose a name for the options parameter -->
      <xsl:variable name="options-param" as="xs:string?">
         <xsl:if test="normalize-space(@extensible) = ('yes', 'true', 'a')"
                 then="('options', (1 to count(xsl:field)) ! `options{.}`)
                         [not(. = current()/xsl:field/@name)][1]"/>
      </xsl:variable>                   
      
      <!-- declare the parameters -->
      <xsl:for-each select="xsl:field">
         <t:param name="{@name}"
                  required="{@required otherwise 'yes'}">
            <xsl:attribute name="as">
               <xsl:variable name="as" 
                             select="normalize-space(@as otherwise 'item()*')"/>
               <xsl:variable name="optional" 
                             select="normalize-space(@required) = ('no', 'false', '0')"/>
               <xsl:choose>
                  <xsl:when test="not($optional)" select="$as"/>
                  <!-- for optional fields, amend the required type to allow () -->
                  <xsl:when test="matches($as, '[?*]$')" select="$as"/>
                  <xsl:when test="matches($as, '\+$')" select="replace($as, '\+$', '*')"/>
                  <xsl:otherwise select="$as || '?'"/>
               </xsl:choose>
            </xsl:attribute>
            <xsl:if test="@default">
               <xsl:attribute name="select" select="@default"/>
            </xsl:if>   
         </t:param>
      </xsl:for-each>
      
      <!-- for an extensible record type, declare the options parameter -->
      <xsl:if test="exists($options-param)">
         <t:param name="{$options-param}" as="map(*)" required="no" select="{}"/>
      </xsl:if>           
      
      <!-- function body: construct a map -->
      <t:map duplicates="fn($first, $second){{$first}}">
         <xsl:for-each select="xsl:field">
            <xsl:variable name="optional" 
                          select="normalize-space(@required) = ('no', 'false', '0')"/>
            <xsl:choose>
              <xsl:when test="$optional and not(@default)">
                 <!-- omit map entries for optional fields if no value is supplied -->
                 <t:if test="exists(${@name})">
                    <t:map-entry key="'{@name}'" select="${@name}"/>
                 </t:if>
              </xsl:when>
              <xsl:otherwise>
                <t:map-entry key="'{@name}'" select="${@name}"/>
              </xsl:otherwise>  
            </xsl:choose>  
         </xsl:for-each>
        
         <!-- if the record type is extensible, 
              add entries from the options parameter -->
              
         <xsl:if test="exists($options-param)">
            <t:sequence select="${$options-param}"/>
         </xsl:if>   

      </t:map>
    </t:function>          
</xsl:template>

For example, the declaration:

<xsl:record-type name="cx:complex">
    <xsl:field name="r" as="xs:double"/>
    <xsl:field name="i" as="xs:double" required="no" default="0"/>
</xsl:record-type>

produces the equivalent function declaration:

<xsl:function name="cx:complex" as="cx:complex" visibility="private">
    <xsl:param name="r" as="xs:double"/>
    <xsl:param name="i" as="xs:double" required="no" select="0"/>
    <xsl:map>
       <xsl:map-entry key="'r'" select="$r"/>
       <xsl:map-entry key="'i'" select="$i"/>
    </xsl:map>
</xsl:function>

No entry is generated in the constructed map for a field that is declared as optional with no default. So the declaration:

<xsl:record-type name="cx:complex" visibility="public">
    <xsl:field name="r" as="xs:double"/>
    <xsl:field name="i" as="xs:double" required="no"/>
</xsl:record-type>

produces the equivalent function declaration:

<xsl:function name="cx:complex" as="cx:complex" visibility="public">
    <xsl:param name="r" as="xs:double"/>
    <xsl:param name="i" as="xs:double?" required="no"/>
    <xsl:map>
       <xsl:map-entry key="'r'" select="$r"/>
       <xsl:if test="exists($i)">
          <xsl:map-entry key="'i'" select="$i"/>
       </xsl:if>   
    </xsl:map>
</xsl:function>

If the record type is extensible, the generated function includes an optional options parameter So the declaration:

<xsl:record-type name="cx:complex" extensible="yes">
    <xsl:field name="r" as="xs:double"/>
    <xsl:field name="i" as="xs:double"/>
</xsl:record-type>

produces the equivalent function declaration:

<xsl:function name="cx:complex" as="cx:complex" visibility="private">
    <xsl:param name="r" as="xs:double"/>
    <xsl:param name="i" as="xs:double"/>
    <xsl:param name="options" as="map(*)" required="no" default="{}"/>
    <xsl:map duplicates="fn($first, $second){$first}">
       <xsl:map-entry key="'r'" select="$r"/>
       <xsl:map-entry key="'i'" select="$i"/>
       <xsl:sequence select="$options"/>   
    </xsl:map>
</xsl:function>

This generated xsl:function declaration must meet all the constraints placed on user-written xsl:function declarations, including the rule requiring the combination of name and arity to be unique.

The generated xsl:function declaration has the import precedence associated with the stylesheet module in which the xsl:record-type declaration appears, and it may be overridden by another xsl:function declaration with higher import precedence. If the visibility is public then it can also be overridden using xsl:override in another package.

The scope of a named record type is the package in which it is declared. If it is declared with visibility="public" then it also becomes available for use in using packages.

The name of the record type is the expanded name formed by resolving the name attribute. Because function names are always in a namespace, the name must be prefixed.

If two xsl:record-type declarations in a package have the same name, then the one with higher import precedence is used.

A record type declaration may refer directly or indirectly to itself if it satisfies the conditions defined in Section 3.2.8.3.1 Recursive Record TypesXP. This allows types to be declared that match recursive data structures such as linked lists and trees.

A named record type with visibility private may be used in the definition of a component (such as a variable, a function, a template, or another named record type) that is itself public. Another package may reference such a component even though it cannot reference the types used in its definition. The fact that the record type is private, however, means that it is impossible to override a variable or function that references the record type.

Example: Defining a Binary Tree

This example illustrates the definition of a recursive record type. The record type represents a node of a binary tree containing a payload value in each node, together with optional references to left and right subtrees. As well as the data fields, the record type defines a depth function that returns the maximum depth of the tree, by making recursive calls on its subtrees.

<xsl:record-type name="my:binary-tree">
   <xsl:field name="left" as="my:binary-tree?"/>
   <xsl:field name="payload" as="item()*"/>
   <xsl:field name="right" as="my:binary-tree?"/>
   <xsl:field name="depth" as="%method fn(my:binary-tree) as xs:integerfn(my:binary-tree) as xs:integer"
      default="%method fn() fn {1 + max((?left ?> depth(), ?right ?> depth())) otherwise 0)}"/>
</xsl:record-type>

TheMethod %methodcalls annotation is(using the operator ?>) are described in Section 4.5.6.1 MethodsSection 4.13.4 Method CallsXP. ItsThe effect is to give a function item contained in a map access to the containing map. It thus mimicsThey thus mimic method invocation in object-oriented languages, though there is no inheritance or encapsulation.

The following code builds a simple tree and calculates its depth:

let $tree := my:binary-tree(
               my:binary-tree((), 17, ()),
               18,
               my:binary-tree((), 19, 
                 my:binary-tree((), 20, ())))
return $tree =?> depth()
let $tree := my:binary-tree(
               my:binary-tree((), 17, ()),
               18,
               my:binary-tree((), 19, 
                 my:binary-tree((), 20, ())))
return $tree ?> depth()

Returning the result 3.

A more detailed example that uses a recursive named record type appears in the following section.

5.5.3 Example: Defining an Atomic Set

This example demonstrates a library package that provides a new data type to manipulate sets of atomic items.

A stylesheet that incorporates this package (by referencing it in an xsl:use-package declaration) can use constructs such as:

  • <xsl:variable name="empty" 
           as="set:atomic-set"
           select="set:build(())"/>

  • <xsl:variable name="evens"
          as="set:atomic-set"                        
          select="set:build((1 to 100)[. mod 2 = 0])"/>

  • <xsl:variable name="odds"
          as="set:atomic-set"
          select="set:build((1 to 100)) ? except($evens)"/>

  • <xsl:function name="my:is-even" as="xs:boolean">
       <xsl:param name="n" as="xs:integer"/>
       <xsl:sequence select="$evens ? contains($n)"/>
    </xsl:function>

Here is the implementation of the package. It relies heavily on the use of the function annotation %method, which gives a function item contained in a map access to the containing map: methods are described in Section 4.5.6.1 MethodsXP.

<xsl:package name="http://qt4cg.org/atomic-set"
             package-version="1.0.0"
             xmlns:set="http://qt4cg.org/atomic-set"
             xmlns:map="http://www.w3.org/2005/xpath-functions/map"
             xmlns:xs="http://www.w3.org/2001/XMLSchema">
   
   <xsl:note>
      This package defines a type set:atomic-set which represents
      a set of distinct atomic items. Atomic items are considered
      distinct based on the comparison function fn:atomic-equal.
      
      An instance of an atomic set can be constructed using a function
      call such as set:build((1, 3, 5, 7, 9)).
      
      If $A and $B are instances of set:atomic-set, then they
      can be manipulated using methods including:
      
      $A?size() - returns the number of items in the set
      $A?empty() - returns true if the set is empty
      $A?contains($k) - determines whether $k is a member of the set
      $A?contains-all($B) - returns true if $B is a subset of $A
      $A?values() - returns the items in $A, as a sequence
      $A?add($k) - returns a new atomic set containing an additional item
      $A?remove($k) - returns a new atomic set in which the given item is absent
      $A?union($B) - returns a new atomic set holding the union of $A and $B
      $A?intersect($B) - returns a new atomic set holding the intersection of $A and $B
      $A?except($B) - returns a new atomic set holding the difference of $A and $B
   
      This package defines a type set:atomic-set which represents
      a set of distinct atomic items. Atomic items are considered
      distinct based on the comparison function fn:atomic-equal.
      
      An instance of an atomic set can be constructed using a function
      call such as set:build((1, 3, 5, 7, 9)).
      
      If $A and $B are instances of set:atomic-set, then they
      can be manipulated using methods including:
      
      $A ?> size() - returns the number of items in the set
      $A ?> empty() - returns true if the set is empty
      $A ?> contains($k) - determines whether $k is a member of the set
      $A ?> contains-all($B) - returns true if $B is a subset of $A
      $A ?> values() - returns the items in $A, as a sequence
      $A ?> add($k) - returns a new atomic set containing an additional item
      $A ?> remove($k) - returns a new atomic set in which the given item is absent
      $A ?> union($B) - returns a new atomic set holding the union of $A and $B
      $A ?> intersect($B) - returns a new atomic set holding the intersection of $A and $B
      $A ?> except($B) - returns a new atomic set holding the difference of $A and $B
   </xsl:note>
   
   <xsl:record-type name="set:atomic-set" 
                    visibility="public"
                    extensible="yes">
      <xsl:field name="_data" 
                 as="map(xs:anyAtomicType, xs:boolean)"/>
      <xsl:field name="size" 
                 as="%method fn() as xs:integerfn(set:atomic-set) as xs:integer"/>
      <xsl:field name="empty" 
                 as="%method fn() as xs:booleanfn(set:atomic-set) as xs:boolean"/>
      <xsl:field name="contains" 
                 as="%method fn($value as xs:anyAtomicType) as xs:booleanfn(set:atomic-set, xs:anyAtomicType) as xs:boolean"/> 
      <xsl:field name="contains-all" 
                 as="%method fn($value as set:atomic-set) as xs:booleanfn(set:atomic-set, set:atomic-set) as xs:boolean"/> 
      <xsl:field name="add" 
                 as="%method fn($item as xs:anyAtomicType) as set:atomic-setfn(set:atomic-set, xs:anyAtomicType) as set:atomic-set"/> 
      <xsl:field name="remove" 
                 as="%method fn($item as xs:anyAtomicType) as set:atomic-setfn(set:atomic-set, xs:anyAtomicType) as set:atomic-set"/>
      <xsl:field name="union" 
                 as="%method fn($value as set:atomic-set) as set:atomic-setfn(set:atomic-set, set:atomic-set) as set:atomic-set"/>
      <xsl:field name="intersect" 
                 as="%method fn($value as set:atomic-set) as set:atomic-setfn(set:atomic-set, set:atomic-set) as set:atomic-set"/> 
      <xsl:field name="except" 
                 as="%method fn($value as set:atomic-set) as set:atomic-setfn(set:atomic-set, set:atomic-set) as set:atomic-set"/> 
   </xsl:record-type>
   
   <xsl:variable name="DATA" select="'_data'" visibility="private"/>
   
   <xsl:note>
      The private function set:replaceData processes the internal map
      by applying a supplied function, and returns a new atomic set
      with the resulting internal map 
   </xsl:note>
   
   <xsl:function name="set:replaceData" 
                 visibility="private" 
                 as="map(xs:anyAtomicType, xs:boolean)">
      <xsl:param name="input" as="set:atomic-set"/>
      <xsl:param name="update" as="fn(map(*)) as map(*)"/>
      <xsl:sequence select="map:put($input, $DATA, $update($input?$DATA))"/>
   </xsl:function>   
   
   <xsl:function name="set:build" as="set:atomic-set" visibility="public">
      <xsl:param name="values" as="xs:anyAtomicType*" default="()"/>
      <xsl:record as="set:atomic-set"
         _data=
            "map:build($values, values:=true#0, {'duplicates': 'use-first'})"
         size=
            "%method fn() as xs:integer 
                     fn($this as set:atomic-set) as xs:integer 
                     { map:size($this?$DATA) }"
         empty=
            "%method fn() as xs:boolean 
                     fn($this as set:atomic-set) as xs:boolean 
                     { map:empty($this?$DATA) }"
         contains=
            "%method fn($value as xs:anyAtomicType) as xs:boolean 
                     fn($this as set:atomic-set, $value as xs:anyAtomicType) as xs:boolean 
                     { map:contains($this?$DATA, $value) }"
         contains-all=
            "%method fn($other as set:atomic-set) as xs:boolean 
                     fn($this as set:atomic-set, $other as set:atomic-set) as xs:boolean 
                     { every($value$other, map:contains($this?$DATA, ?)) }"
         values=
            "%method fn() as xs:anyAtomicType* 
                     fn($this as set:atomic-set) as xs:anyAtomicType* 
                     { keys($this?$DATA) }"
         add=
            "%method fn($value as xs:anyAtomicType) as xs:anyAtomicType*
                     fn($this as set:atomic-set, $value as xs:anyAtomicType) as xs:anyAtomicType*
                     { set:replaceData(.$this, map:put(?, $value, true())) }"
         remove=
            "%method fn($value as xs:anyAtomicType) as xs:anyAtomicType* 
                     fn($this as set:atomic-set, $value as xs:anyAtomicType) as xs:anyAtomicType* 
                     { set:replaceData(.$this, map:remove(?, $value)) }"
         union=
            "%method fn($other as set:atomic-set) as set:atomic-set 
                     fn($this as set:atomic-set, $other as set:atomic-set) as set:atomic-set 
                     { set:replaceData(.$this, fn($this$m) {map:merge(($this$m, $other?$DATA),
                                                    {'duplicates': 'use-first'})})
                     }"
         intersect=
            "%method fn($other as set:atomic-set) as set:atomic-set 
                     fn($this as set:atomic-set, $other as set:atomic-set) as set:atomic-set 
                     { set:replaceData(.$this, map:filter(?, $other?contains)) }"
         except=
            "%method fn($other as set:atomic-set) as set:atomic-set 
                     fn($this as set:atomic-set, $other as set:atomic-set) as set:atomic-set 
                     { set:replaceData(.$this, map:remove(?, $other?values())) }"/>
      
   </xsl:function>
   
   
</xsl:package>
Editorial note 

The example is not yet tested. It could also be written using the new xsl:record instruction, which might be more concise.

E Pattern Syntax Summary (Non-Normative)

This appendix gives the grammar for XSLT patterns. The top-level rule for patterns is Pattern40.

This is an extension of the grammar for XPath expressions. The extended BNF notation is explained at Section A.1.1 NotationXP.

Productions that are identical to their counterparts in XPath 4.0 are suffixed XP and link to the corresponding production in the XPath 4.0 specification. Productions whose names end with P are restrictions of the corresponding XPath production: for example, ArgumentListP is a restricted form of the XPath production ArgumentList.

AbbrevForwardStepP::=("@" NodeTestXP) | SimpleNodeTestXP
AbbreviatedStep::=".." | ("@" NodeTestXP) | SimpleNodeTestXP
AbsolutePathExpr::=("/" RelativePathExprXP?) | ("//" RelativePathExprXP)
AdditiveExpr::=MultiplicativeExprXP (("+" | "-") MultiplicativeExprXP)*
AndExpr::=ComparisonExprXP ("and" ComparisonExprXP)*
AnyArrayType::="array" "(" "*" ")"
AnyFunctionType::=("function" | "fn") "(" "*" ")"
AnyItemTest::="item" "(" ")"
AnyMapType::="map" "(" "*" ")"
AnyNodeKindTest::="node" "(" ")"
AnyRecordType::="record" "(" "*" ")"
Argument::=ExprSingleXP | ArgumentPlaceholderXP
ArgumentList::="(" ((PositionalArgumentsXP ("," KeywordArgumentsXP)?) | KeywordArgumentsXP)? ")"
ArgumentListP::="(" (ArgumentP ** ",") ")"
ArgumentP::=VarRefXP | LiteralXP
ArgumentPlaceholder::="?"
ArrayConstructor::=SquareArrayConstructorXP | CurlyArrayConstructorXP
ArrayType::=AnyArrayTypeXP | TypedArrayTypeXP
ArrowExpr::=UnaryExprXP (SequenceArrowTargetXP | MappingArrowTargetXP)*
ArrowTarget::=FunctionCallXP | RestrictedDynamicCallXP
AttributeName::=EQNameXP
AttributeTest::="attribute" "(" (NameTestUnionXP ("," TypeNameXP)?)? ")"
Axis::=("ancestor" | "ancestor-or-self" | "attribute" | "child" | "descendant" | "descendant-or-self" | "following" | "following-or-self" | "following-sibling" | "following-sibling-or-self" | "namespace" | "parent" | "preceding" | "preceding-or-self" | "preceding-sibling" | "preceding-sibling-or-self" | "self") "::"
AxisStep::=(AbbreviatedStepXP | FullStepXP) PredicateXP*
AxisStepP::=ForwardStepPPredicateXP*
BracedAction::=EnclosedExprXP
CastableExpr::=CastExprXP ("castable" "as" CastTargetXP "?"?)?
CastExpr::=PipelineExprXP ("cast" "as" CastTargetXP "?"?)?
CastTarget::=TypeNameXP
ChoiceItemType::="(" (ItemTypeXP ++ "|") ")"
CommentTest::="comment" "(" ")"
ComparisonExpr::=OtherwiseExprXP ((ValueCompXP | GeneralCompXP | NodeCompXP) OtherwiseExprXP)?
ContextValueRef::="."
CurlyArrayConstructor::="array" EnclosedExprXP
DocumentTest::="document-node" "(" (ElementTestXP | SchemaElementTestXP | NameTestUnionXP)? ")"
DynamicFunctionCall::=PostfixExprXPPositionalArgumentListXP
ElementName::=EQNameXP
ElementTest::="element" "(" (NameTestUnionXP ("," TypeNameXP "?"?)?)? ")"
EnclosedExpr::="{" ExprXP? "}"
EnumerationType::="enum" "(" (StringLiteralXP ++ ",") ")"
EQName::=QName | URIQualifiedNameXP
Expr::=(ExprSingleXP ++ ",")
ExprSingle::=ForExprXP
| LetExprXP
| QuantifiedExprXP
| IfExprXP
ExtensibleFlag::="," "*"
FieldDeclaration::=FieldNameXP "?"? ("as" SequenceTypeXP)?
FieldName::=NCNameXP | StringLiteralXP
FilterExpr::=PostfixExprXPPredicateXP
FilterExprAM::=PostfixExprXP "?[" ExprXP "]"
ForBinding::=ForItemBindingXP | ForMemberBindingXP | ForEntryBindingXP
ForClause::="for" (ForBindingXP ++ ",")
ForEntryBinding::=((ForEntryKeyBindingXPForEntryValueBindingXP?) | ForEntryValueBindingXP) PositionalVarXP? "in" ExprSingleXP
ForEntryKeyBinding::="key" VarNameAndTypeXP
ForEntryValueBinding::="value" VarNameAndTypeXP
ForExpr::=ForClauseXPForLetReturnXP
ForItemBinding::=VarNameAndTypeXPPositionalVarXP? "in" ExprSingleXP
ForLetReturn::=ForExprXP | LetExprXP | ("return" ExprSingleXP)
ForMemberBinding::="member" VarNameAndTypeXPPositionalVarXP? "in" ExprSingleXP
ForwardAxisP::=("child" "::")
| ("descendant" "::")
| ("attribute" "::")
| ("self" "::")
| ("descendant-or-self" "::")
| ("namespace" "::")
ForwardStepP::=(ForwardAxisPNodeTestXP) | AbbrevForwardStepP
FullStep::=AxisXPNodeTestXP
FunctionBody::=EnclosedExprXP
FunctionCall::=EQNameXPArgumentListXP
/* xgs: reserved-function-names */
/* gn: parens */
FunctionCallP::=OuterFunctionNameArgumentListP
FunctionItemExpr::=NamedFunctionRefXP | InlineFunctionExprXP
FunctionSignature::="(" ParamListXP ")" TypeDeclarationXP?
FunctionType::=AnyFunctionTypeXP
| TypedFunctionTypeXP
GeneralComp::="=" | "!=" | "<" | "<=" | ">" | ">="
GNodeType::="gnode" "(" ")"
IfExpr::="if" "(" ExprXP ")" (UnbracedActionsXP | BracedActionXP)
InlineFunctionExpr::=MethodAnnotationXP* ("function" | "fn") FunctionSignatureXP? FunctionBodyXP
InstanceofExpr::=TreatExprXP ("instance" "of" SequenceTypeXP)?
IntersectExceptExpr::=InstanceofExprXP (("intersect" | "except") InstanceofExprXP)*
IntersectExceptExprP::=PathExprP (("intersect" | "except") PathExprP)*
ItemType::=RegularItemTypeXP | FunctionTypeXP | TypeNameXP | ChoiceItemTypeXP
JNodeType::="jnode" "(" SequenceTypeXP? ")"
KeySpecifier::=NCNameXP | IntegerLiteral | StringLiteralXP | VarRefXP | ParenthesizedExprXP | LookupWildcardXP
KeywordArgument::=EQNameXP ":=" ArgumentXP
KeywordArguments::=(KeywordArgumentXP ++ ",")
LetArrayBinding::="$" "[" (VarNameAndTypeXP ++ ",") "]" TypeDeclarationXP? ":=" ExprSingleXP
LetBinding::=LetValueBindingXP | LetSequenceBindingXP | LetArrayBindingXP | LetMapBindingXP
LetClause::="let" (LetBindingXP ++ ",")
LetExpr::=LetClauseXPForLetReturnXP
LetMapBinding::="$" "{" (VarNameAndTypeXP ++ ",") "}" TypeDeclarationXP? ":=" ExprSingleXP
LetSequenceBinding::="$" "(" (VarNameAndTypeXP ++ ",") ")" TypeDeclarationXP? ":=" ExprSingleXP
LetValueBinding::=VarNameAndTypeXP ":=" ExprSingleXP
Literal::=NumericLiteralXP | StringLiteralXP | QNameLiteralXP
Lookup::="?" KeySpecifierXP
LookupExpr::=PostfixExprXPLookupXP
LookupWildcard::="*"
MapConstructor::="map"? "{" (MapConstructorEntryXP ** ",") "}"
MapConstructorEntry::=ExprSingleXP (":" ExprSingleXP)?
MappingArrowTarget::="=!>" ArrowTargetXP
MapType::=AnyMapTypeXP | TypedMapTypeXP
MethodAnnotation::="%method"
MethodCall::=PostfixExprXP "?>" NCNameXPPositionalArgumentListXP
MultiplicativeExpr::=UnionExprXP (("*" | "×" | "div" | "÷" | "idiv" | "mod") UnionExprXP)*
NamedFunctionRef::=EQNameXP "#" IntegerLiteral
/* xgs: reserved-function-names */
NamespaceNodeTest::="namespace-node" "(" ")"
NameTest::=EQNameXP | WildcardXP
NameTestUnion::=(NameTestXP ++ "|")
NodeComp::="is" | "is-not" | NodePrecedesXP | NodeFollowsXP
NodeFollows::=">>" | "follows"
NodeKindTest::=DocumentTestXP
| ElementTestXP
| AttributeTestXP
| SchemaElementTestXP
| SchemaAttributeTestXP
| PITestXP
| CommentTestXP
| TextTestXP
| NamespaceNodeTestXP
| AnyNodeKindTestXP
NodePattern::=UnionExprP
NodePrecedes::="<<" | "precedes"
NodeTest::=UnionNodeTestXP | SimpleNodeTestXP
NumericLiteral::=IntegerLiteral | HexIntegerLiteral | BinaryIntegerLiteral | DecimalLiteral | DoubleLiteral
OccurrenceIndicator::="?" | "*" | "+"
/* xgs: occurrence-indicators */
OrExpr::=AndExprXP ("or" AndExprXP)*
OtherwiseExpr::=StringConcatExprXP ("otherwise" StringConcatExprXP)*
OuterFunctionName::=EQNameXP
ParamList::=(VarNameAndTypeXP ** ",")
ParenthesizedExpr::="(" ExprXP? ")"
ParenthesizedExprP::="(" UnionExprP ")"
PathExpr::=AbsolutePathExprXP
| RelativePathExprXP
/* xgs: leading-lone-slash */
PathExprP::=RootedPath
| ("/" RelativePathExprP?)
| ("//" RelativePathExprP)
| RelativePathExprP
/* xgs: leading-lone-slash */
Pattern40::=PredicatePattern | TypePattern | NodePattern
PipelineExpr::=ArrowExprXP ("->" ArrowExprXP)*
PITest::="processing-instruction" "(" (NCNameXP | StringLiteralXP)? ")"
PositionalArgumentList::="(" PositionalArgumentsXP? ")"
PositionalArguments::=(ArgumentXP ++ ",")
PositionalVar::="at" VarNameXP
PostfixExpr::=PrimaryExprXP | FilterExprXP | DynamicFunctionCallXP | LookupExprXP | MethodCallXP | FilterExprAMXP
PostfixExprP::=(FunctionCallP | ParenthesizedExprP) PredicateXP*
Predicate::="[" ExprXP "]"
PredicatePattern::="." PredicateXP*
PrimaryExpr::=LiteralXP
| VarRefXP
| ParenthesizedExprXP
| ContextValueRefXP
| FunctionCallXP
| FunctionItemExprXP
| MapConstructorXP
| ArrayConstructorXP
| StringTemplateXP
| UnaryLookupXP
QNameLiteral::="#" EQNameXP
QuantifiedExpr::=("some" | "every") (QuantifierBindingXP ++ ",") "satisfies" ExprSingleXP
QuantifierBinding::=VarNameAndTypeXP "in" ExprSingleXP
RangeExpr::=AdditiveExprXP ("to" AdditiveExprXP)?
RecordType::=AnyRecordTypeXP | TypedRecordTypeXP
RegularItemType::=AnyItemTestXP | NodeKindTestXP | GNodeTypeXP | JNodeTypeXP | MapTypeXP | ArrayTypeXP | RecordTypeXP | EnumerationTypeXP
RelativePathExpr::=StepExprXP (("/" | "//") StepExprXP)*
RelativePathExprP::=StepExprP (("/" | "//") StepExprP)*
RestrictedDynamicCall::=(VarRefXP | ParenthesizedExprXP | FunctionItemExprXP | MapConstructorXP | ArrayConstructorXP) PositionalArgumentListXP
RootedPath::=VarRefXPPredicateXP* (("/" | "//") RelativePathExprP)?
SchemaAttributeTest::="schema-attribute" "(" AttributeNameXP ")"
SchemaElementTest::="schema-element" "(" ElementNameXP ")"
Selector::=EQNameXP | WildcardXP | ("get" "(" ExprXP ")")
SequenceArrowTarget::="=>" ArrowTargetXP
SequenceType::=("empty-sequence" "(" ")")
| (ItemTypeXPOccurrenceIndicatorXP?)
SimpleMapExpr::=PathExprXP ("!" PathExprXP)*
SimpleNodeTest::=TypeTestXP | SelectorXP
SquareArrayConstructor::="[" (ExprSingleXP ** ",") "]"
StepExpr::=PostfixExprXP | AxisStepXP
StepExprP::=PostfixExprP | AxisStepP
StringConcatExpr::=RangeExprXP ("||" RangeExprXP)*
StringTemplate::="`" (StringTemplateFixedPartXP | StringTemplateVariablePartXP)* "`"
/* ws: explicit */
StringTemplateFixedPart::=((Char - ('{' | '}' | '`')) | "{{" | "}}" | "``")*
/* ws: explicit */
StringTemplateVariablePart::=EnclosedExprXP
/* ws: explicit */
TextTest::="text" "(" ")"
TreatExpr::=CastableExprXP ("treat" "as" SequenceTypeXP)?
TypedArrayType::="array" "(" SequenceTypeXP ")"
TypeDeclaration::="as" SequenceTypeXP
TypedFunctionParam::=("$" EQNameXP "as")? SequenceTypeXP
TypedFunctionType::=("function" | "fn") "(" (TypedFunctionParamXP ** ",") ")" "as" SequenceTypeXP
TypedMapType::="map" "(" ItemTypeXP "," SequenceTypeXP ")"
TypedRecordType::="record" "(" (FieldDeclarationXP ** ",") ExtensibleFlagXP? ")"
TypeName::=EQNameXP
TypePattern::=(WrappedItemTest | AnyItemTestXP | FunctionTypeXP | MapTypeXP | ArrayTypeXP | RecordTypeXP | EnumerationTypeXP) PredicateXP*
TypeTest::=RegularItemTypeXP | ("type" "(" SequenceTypeXP ")")
UnaryExpr::=("-" | "+")* ValueExprXP
UnaryLookup::=LookupXP
UnbracedActions::="then" ExprSingleXP "else" ExprSingleXP
UnionExpr::=IntersectExceptExprXP (("union" | "|") IntersectExceptExprXP)*
UnionExprP::=IntersectExceptExprP (("union" | "|") IntersectExceptExprP)*
UnionNodeTest::="(" (SimpleNodeTestXP ++ "|") ")"
ValueComp::="eq" | "ne" | "lt" | "le" | "gt" | "ge"
ValueExpr::=SimpleMapExprXP
VarName::="$" EQNameXP
VarNameAndType::="$" EQNameXPTypeDeclarationXP?
VarRef::="$" EQNameXP
Wildcard::="*"
| (NCName ":" "*")
| ("*" ":" NCName)
| (BracedURILiteral "*")
/* ws: explicit */
WrappedItemTest::="type" ChoiceItemTypeXP