XPath

Data Types

XPath's type system β€” atomic types, sequences, type casting, and schema interaction

#Data Types

XPath has a richer type system than JSON or most dynamically typed languages. Understanding this type system prevents subtle bugs in comparisons, function calls, and XSLT template matching.

For C# developers: XPath's type hierarchy parallels the .NET type hierarchy (object to ValueType to int, string, DateTime, and so on). XPath treats date and time types as first-class values, not library types. The castable as test resembles int.TryParse. The treat as assertion resembles a cast such as (int)value, which raises InvalidCastException on failure. The instance of test resembles the C# is operator. XPath function types resemble Func<int, int> and similar delegate types. map and array types resemble Dictionary<string, int> and List<string>. The type() function resembles value.GetType().Name. The div operator always returns a decimal or double, unlike C#'s /, which returns an integer when both operands are integers. XPath date arithmetic uses standard operators, while C# requires methods such as DateTime.AddDays or manual subtraction.

#Contents


#The Type Hierarchy

Every value in XPath is an item in a sequence. Items are either nodes (from the XML tree) or atomic values (strings, numbers, dates, etc.).

item()
β”œβ”€β”€ node()
β”‚   β”œβ”€β”€ document-node()
β”‚   β”œβ”€β”€ element()
β”‚   β”œβ”€β”€ attribute()
β”‚   β”œβ”€β”€ text()
β”‚   β”œβ”€β”€ comment()
β”‚   β”œβ”€β”€ processing-instruction()
β”‚   └── namespace-node()
β”œβ”€β”€ xs:anyAtomicType
β”‚   β”œβ”€β”€ xs:string
β”‚   β”œβ”€β”€ xs:boolean
β”‚   β”œβ”€β”€ xs:decimal
β”‚   β”‚   └── xs:integer
β”‚   β”‚       β”œβ”€β”€ xs:long
β”‚   β”‚       β”‚   └── xs:int
β”‚   β”‚       β”‚       └── xs:short
β”‚   β”‚       β”‚           └── xs:byte
β”‚   β”‚       └── xs:nonNegativeInteger
β”‚   β”‚           └── xs:positiveInteger
β”‚   β”œβ”€β”€ xs:double
β”‚   β”œβ”€β”€ xs:float
β”‚   β”œβ”€β”€ xs:date
β”‚   β”œβ”€β”€ xs:dateTime
β”‚   β”œβ”€β”€ xs:time
β”‚   β”œβ”€β”€ xs:duration
β”‚   β”‚   β”œβ”€β”€ xs:dayTimeDuration
β”‚   β”‚   └── xs:yearMonthDuration
β”‚   β”œβ”€β”€ xs:anyURI
β”‚   β”œβ”€β”€ xs:QName
β”‚   β”œβ”€β”€ xs:hexBinary
β”‚   β”œβ”€β”€ xs:base64Binary
β”‚   └── xs:untypedAtomic
β”œβ”€β”€ function(*)
β”œβ”€β”€ map(*)
└── array(*)
                                    

#Atomic Types in Practice

#Numeric Types

XPath has four numeric types with automatic promotion:

Type

Range

Precision

C# Equivalent

xs:integer

Arbitrary

Exact

BigInteger (unbounded)

xs:decimal

Arbitrary

Exact

decimal

xs:float

Β±3.4 Γ— 10³⁸

~7 digits

float

xs:double

Β±1.7 Γ— 10³⁰⁸

~15 digits

double

Promotion rules (automatic):

integer β†’ decimal β†’ float β†’ double
                                      
xpath
5 + 3           => 8          (: integer + integer = integer :)
5 + 3.0         => 8.0        (: integer + decimal = decimal :)
5 + 3.0e0       => 8.0e0      (: integer + double = double :)
                                      

The xs:untypedAtomic behavior: A value read from an XML element or attribute without a schema has the type xs:untypedAtomic, not xs:string. XPath automatically casts xs:untypedAtomic values to the required type in comparisons and arithmetic:

xpath
<price>39.99</price>
(: //price is xs:untypedAtomic, automatically cast to xs:double for comparison :)
//price > 30   => true
                                      

This is convenient but means type errors surface at runtime, not compile time.

#String Type

Strings in XPath are sequences of Unicode characters. XPath strings are immutable.

xpath
"hello"                        (: string literal :)
'hello'                        (: also valid β€” single or double quotes :)
""                             (: empty string :)
                                      

String vs untypedAtomic: An element's text content is xs:untypedAtomic, not xs:string. In most contexts this does not matter because they convert automatically. However, xs:string values compare using collation, while xs:untypedAtomic values promote to the type of the other operand.

#Boolean Type

XPath booleans follow effective boolean value rules that automatically convert other types to boolean:

xpath
boolean("hello")   => true    (: non-empty string :)
boolean("")        => false   (: empty string :)
boolean(42)        => true    (: non-zero :)
boolean(0)         => false   (: zero :)
                                      

#Date and Time Types

These are first-class types, not strings:

xpath
xs:date("2026-03-19")                    (: date :)
xs:time("14:30:00")                      (: time :)
xs:dateTime("2026-03-19T14:30:00")       (: date + time :)
xs:duration("P1Y2M3D")                   (: duration :)
xs:dayTimeDuration("PT5H30M")            (: days/hours/minutes/seconds :)
xs:yearMonthDuration("P1Y6M")            (: years/months :)
                                      

Date arithmetic works natively:

xpath
xs:date("2026-03-19") + xs:dayTimeDuration("P7D")
=> 2026-03-26          (: add 7 days :)
xs:date("2026-12-31") - xs:date("2026-01-01")
=> P365D               (: difference as duration :)
xs:date("2026-03-19") > xs:date("2025-12-25")
=> true                (: date comparison :)
                                      

XPath uses standard operators for this arithmetic instead of separate methods.


#Type Casting

#Explicit Casting with Constructor Functions

xpath
xs:integer("42")           => 42
xs:date("2026-03-19")     => typed date
xs:double(42)              => 42.0e0
xs:string(42)              => "42"
                                        

See Type Constructors for the full list.

#The cast as Expression

An alternative syntax for type casting:

xpath
"42" cast as xs:integer       => 42
42 cast as xs:string          => "42"
"true" cast as xs:boolean     => true
                                        

#The castable as Test

Tests whether a cast would succeed without performing it:

xpath
"42" castable as xs:integer     => true
"abc" castable as xs:integer    => false
"2026-03-19" castable as xs:date => true
                                        

Practical pattern β€” safe type casting:

xpath
if ("42" castable as xs:integer)
then xs:integer("42")
else 0
                                        

#The treat as Assertion

Asserts a type at compile time without converting the value. Raises an error if the type does not match at runtime:

xpath
$value treat as xs:integer    (: assert $value is an integer :)
                                        

#The instance of Test

Tests whether a value is of a given type:

xpath
42 instance of xs:integer         => true
42 instance of xs:string          => false
"hello" instance of xs:string     => true
(1, 2, 3) instance of xs:integer+ => true  (: sequence of one or more integers :)
                                        

#Sequence Types

Sequence types describe the structure of sequences. Function signatures, variable declarations, and type tests all use sequence types.

#Occurrence Indicators

Indicator

Meaning

C# Equivalent

(none)

Exactly one

T

?

Zero or one

T? or Nullable<T>

*

Zero or more

IEnumerable<T>

+

One or more

(no direct equivalent β€” non-empty enumerable)

xpath
xs:integer             (: exactly one integer :)
xs:integer?            (: zero or one integer :)
xs:integer*            (: zero or more integers :)
xs:integer+            (: one or more integers :)
item()*                (: any sequence of any items :)
node()                 (: exactly one node :)
element(book)          (: exactly one element named "book" :)
                                          

#Function Types

Functions are first-class values with typed signatures:

xpath
function(xs:integer) as xs:integer         (: function taking and returning an integer :)
function(xs:string, xs:string) as xs:boolean (: predicate on two strings :)
function(*) as item()*                      (: any function :)
                                          

#Map and Array Types

xpath
map(xs:string, xs:integer)     (: map from strings to integers :)
map(*)                          (: any map :)
array(xs:string)               (: array of strings :)
array(*)                        (: any array :)
                                          

#Type Testing

# instance of

Runtime type check:

xpath
let $value := //price/text()
return
  if ($value instance of xs:decimal) then "decimal"
  else if ($value instance of xs:string) then "string"
  else "unknown"
                                            

# type() (XPath 4.0)

Returns the type name as a string:

xpath
type(42)                   => "xs:integer"
type("hello")              => "xs:string"
type(xs:date("2026-03-19")) => "xs:date"
type(//price)              => type of the price node's value
                                            

#Common Type Pitfalls

#1. Untyped Attribute Comparisons

xpath
(: @price is xs:untypedAtomic, not a number :)
//item[@price > 10]          (: works β€” untypedAtomic auto-casts to double :)
//item[@price > "10"]        (: WRONG β€” string comparison! "9" > "10" is true :)
                                              

Rule: XPath casts the untyped value to a number when the comparison uses a number literal. XPath performs a string comparison when the comparison uses a string literal. Use an explicit cast to control the comparison type.

#2. Empty Sequence vs Empty String

xpath
() = ""                      (: false β€” empty sequence is not empty string :)
string(())                   => ""   (: but converting empty sequence gives empty string :)
//nonexistent = ""           (: false β€” no nodes, not empty text :)
//nonexistent/text() = ""   (: false for the same reason :)
                                              

#3. Numeric String Comparison

xpath
"9" > "10"                   => true   (: string comparison β€” "9" sorts after "1" :)
9 > 10                       => false  (: numeric comparison :)
xs:integer("9") > xs:integer("10")  => false  (: explicit numeric :)
                                              

Rule: For a numeric comparison, make at least one operand a number.

#4. Date String Comparison

xpath
"2026-03-19" > "2025-12-25"  => true   (: works by accident β€” ISO format sorts correctly :)
xs:date("2026-03-19") > xs:date("2025-12-25")  => true  (: correct β€” typed date comparison :)
                                              

ISO 8601 date strings happen to sort correctly as strings, but this is coincidental. Always use typed dates for reliable comparison, especially with non-ISO formats.

#5. Division Returns Decimal, Not Integer

xpath
10 div 3     => 3.333...   (: not 3! :)
10 idiv 3    => 3           (: use idiv for integer division :)
                                              

XPath's div always returns a decimal or double result, even when both operands are integers.