Tools
xquery CLI
Command-line XQuery processor — query XML documents from the terminal
#xquery CLI
The xquery command-line tool executes XQuery expressions against XML files, directories, or URLs. Think of it as jq for XML — but with the full power of XQuery 4.0.
#Installation
dotnet tool install -g PhoenixmlDb.XQuery.Cli
After installation, the xquery command is available globally.
#Usage
xquery [options] <expression> [sources...]
xquery [options] -f <query-file> [sources...]
command | xquery [options] <expression>
#Arguments
|
Argument |
Description |
|---|---|
|
|
Inline XQuery expression to execute |
|
|
XML files, directories, or URLs to query |
#Options
|
Option |
Short |
Description |
|---|---|---|
|
|
|
Read XQuery from a file instead of inline |
|
|
|
Output method: |
|
|
Read XML input from stdin (waits indefinitely) |
|
|
|
Stdin auto-detection timeout in ms (default: 200) |
|
|
|
Show parse/compile/execute timing breakdown |
|
|
|
Show the execution plan before running |
|
|
|
Parse and compile only — validate without executing |
|
|
|
|
Show detailed error information |
|
|
|
Show help message |
|
|
Show version information |
#Exit Codes
|
Code |
Meaning |
|---|---|
|
0 |
Success |
|
1 |
Usage error |
|
2 |
Parse error |
|
3 |
Runtime error |
#Source Documents
The xquery tool supports three types of source input:
|
Source |
Description |
|---|---|
|
File |
Path to an XML file — loaded as a |
|
Directory |
Path to a directory — all |
|
URL |
HTTP/HTTPS URL — XML fetched from the network |
When a single source is provided, it becomes the context item (.), so you can write XPath directly:
xquery '//title' books.xml
All sources are available via doc($uri) by their path or URL. collection() returns all loaded documents.
#Examples
#Quick Queries
The most common use — XPath expressions against a file:
# Select all titles
xquery '//title' catalog.xml
# Count elements
xquery 'count(//product)' catalog.xml
# Filter by attribute
xquery '//product[@category="electronics"]/name' catalog.xml
# Get attribute values
xquery '//product/@id' catalog.xml
C# developer note: This is like running LINQ queries from the command line — no project setup, no compilation, just query and get results.
#FLWOR Queries
Use single quotes to wrap multi-line expressions:
xquery '
for $book in //book
where $book/price > 30
order by $book/price descending
return concat($book/title, " — $", $book/price)
' catalog.xml
#Query Files
For complex queries, write them in a .xq file:
xquery -f report.xq catalog.xml
report.xq:
for $cat in distinct-values(//product/@category)
let $products := //product[@category = $cat]
order by $cat
return <category name="{ $cat }">
<count>{ count($products) }</count>
<avg-price>{ format-number(avg($products/price), '#.00') }</avg-price>
</category>
#Multiple Source Files
Query across multiple documents:
# Multiple files
xquery 'count(collection()//product)' products.xml orders.xml
# An entire directory
xquery 'collection()//error' ./logs/
# Join data across files
xquery '
for $order in doc("orders.xml")//order
let $customer := doc("customers.xml")//customer[@id = $order/@customer-id]
return concat($order/@id, ": ", $customer/name)
' orders.xml customers.xml
#Reading from stdin
Pipe XML from another command:
curl https://api.example.com/data.xml | xquery '//item/name'
cat data.xml | xquery 'count(//error)'
#JSON Output
Use --output json (or -o json) to serialize maps and arrays as JSON instead of the default adaptive output:
xquery -o json 'map { "name": "test", "count": 42 }'
xquery -f query-with-json-option.xq data.xml
The second form works because --output json is also auto-detected from a declare option output:method "json" declaration in the query file. When that option is present, the CLI picks up the serialization method without requiring an explicit flag.
An unrecognised method is an error, on the command line or in the prolog: -o htm exits with
code 1, and declare option output:method "htm" raises SEPM0016. Before 2.4.0, both silently
fell back to adaptive output and exited 0, so a typo produced wrong output that looked like
success. Scripts that relied on that fallback now fail visibly.
For more complex results:
xquery '
array {
for $p in //product
return map {
"name": string($p/name),
"price": number($p/price)
}
}
' catalog.xml
#Working with JSON Input
Query JSON files using json-doc():
xquery '
let $data := json-doc("config.json")
return $data?settings?theme
'
#Execution Plan
See how the query engine plans to execute your query:
xquery --plan 'for $x in //product order by $x/price return $x/name' catalog.xml
This shows the query plan before execution — useful for understanding performance characteristics of complex queries.
#Performance Profiling
xquery --timing '//product[price > 50]' large-catalog.xml
Output (to stderr):
parse: 1.2 ms
compile: 3.4 ms
execute: 12.8 ms (15 results)
total: 17.4 ms
#Validation Only
Check that a query compiles without running it:
xquery --dry-run -f complex-query.xq
#Real-World Workflows
#Data Extraction
# Extract all email addresses from an XML dataset
xquery '//contact/email' contacts.xml
# Get unique categories
xquery 'distinct-values(//product/@category)' catalog.xml
# Find duplicates
xquery '
for $name in //product/name
group by $n := string($name)
where count($name) > 1
return concat($n, " (", count($name), " occurrences)")
' catalog.xml
#Data Validation
# Find products with missing prices
xquery '//product[not(price)]/@id' catalog.xml
# Find orders with negative amounts
xquery '//order[total < 0]' orders.xml
# Validate email format
xquery '//email[not(matches(., "^[^@]+@[^@]+\.[^@]+$"))]' contacts.xml
#Format Conversion
# XML to CSV
xquery '
string-join((
"id,name,price",
for $p in //product
return string-join(($p/@id, $p/name, $p/price), ",")
), " ")
' -o text catalog.xml > products.csv
# XML to Markdown
xquery '
string-join((
"# Products",
"",
for $p in //product
return concat("- **", $p/name, "** — $", $p/price)
), " ")
' -o text catalog.xml > products.md
#Aggregation and Reporting
# Summary statistics
xquery '
let $products := //product
return map {
"count": count($products),
"total": sum($products/price),
"average": round(avg($products/price), 2),
"min": min($products/price),
"max": max($products/price)
}
' catalog.xml
#Shell Pipeline Integration
# Combine with other tools
xquery '//product/name' catalog.xml | sort | uniq -c | sort -rn
# Feed into jq for further JSON processing
xquery 'array { //product ! map { "name": string(name), "price": number(price) } }' catalog.xml \
| jq '.[] | select(.price > 50)'
# Watch for changes
watch -n 5 'xquery "count(//error)" /var/log/app/*.xml'
#Output Encoding
Output is written as UTF-8, with no byte-order mark, on every platform, whether it goes to the terminal or is redirected to a file.
Before 2.4.0, Windows consoles used their legacy code page (typically 1252 or 437). Characters
outside that page were replaced, both in the terminal and in redirected output, so
xquery -f q.xq > out.xml could write a file whose bytes didn't match its UTF-8 declaration. If
you have files produced that way, regenerate them.
#Comparison with Other Tools
|
Task |
xquery |
jq |
xmlstarlet |
grep |
|---|---|---|---|---|
|
Query XML |
Native |
No XML support |
Basic XPath 1.0 |
Text only |
|
Query JSON |
|
Native |
No JSON support |
Text only |
|
FLWOR queries |
Full XQuery |
Limited |
No |
No |
|
Cross-file joins |
|
Requires scripting |
Single file |
No |
|
Format conversion |
XML/JSON/CSV/text |
JSON only |
XML only |
No |
|
Type-aware |
xs:date, xs:decimal, etc. |
Limited |
No |
No |
#Using from .NET Code
The same engine is available as a library. See XQuery Extensibility for programmatic usage including setting variables, registering extension functions, and reading results.