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 (
objecttoValueTypetoint,string,DateTime, and so on). XPath treats date and time types as first-class values, not library types. Thecastable astest resemblesint.TryParse. Thetreat asassertion resembles a cast such as(int)value, which raisesInvalidCastExceptionon failure. Theinstance oftest resembles the C#isoperator. XPath function types resembleFunc<int, int>and similar delegate types.mapandarraytypes resembleDictionary<string, int>andList<string>. Thetype()function resemblesvalue.GetType().Name. Thedivoperator 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 asDateTime.AddDaysor 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 |
|---|---|---|---|
|
|
Arbitrary |
Exact |
|
|
|
Arbitrary |
Exact |
|
|
|
Β±3.4 Γ 10Β³βΈ |
~7 digits |
|
|
|
Β±1.7 Γ 10Β³β°βΈ |
~15 digits |
|
Promotion rules (automatic):
integer β decimal β float β double
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:
<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.
"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:
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:
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:
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
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:
"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:
"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:
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:
$value treat as xs:integer (: assert $value is an integer :)
#The instance of Test
Tests whether a value is of a given type:
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 |
|
|
|
Zero or one |
|
|
|
Zero or more |
|
|
|
One or more |
(no direct equivalent β non-empty enumerable) |
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:
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
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:
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:
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
(: @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
() = "" (: 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
"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
"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
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.