xslt 1.6.8
dotnet tool install --global xslt --version 1.6.8
dotnet new tool-manifest
dotnet tool install --local xslt --version 1.6.8
#tool dotnet:?package=xslt&version=1.6.8
nuke :add-package xslt --version 1.6.8
xslt
Command-line XSLT 3.0/4.0 processor for .NET. Transform XML documents from the terminal using the PhoenixmlDb XSLT engine.
Installation
dotnet tool install -g xslt
Usage
# Transform XML with a stylesheet
xslt stylesheet.xsl input.xml
# Write output to a file
xslt -o result.html report.xsl data.xml
# Start from a named template (no source needed)
xslt -it main generate.xsl
# Pass parameters
xslt -p year=2026 -p title="Report" style.xsl data.xml
# Read source from stdin
cat data.xml | xslt transform.xsl
# Show timing breakdown
xslt --timing style.xsl large-input.xml
# Validate a stylesheet without running
xslt --dry-run style.xsl
# Stream large files (lower memory)
xslt --stream style.xsl large-input.xml
Features
- XSLT 3.0/4.0 — packages, streaming, maps/arrays, higher-order functions, JSON output
- Multiple output methods — XML, HTML, XHTML, text, JSON, adaptive
- Streaming — process large files without loading into memory
- xsl:result-document — generate multiple output files in one transform
- Parameters — pass values from the command line
- Timing — built-in performance profiling
- Tracing — log template matching, function calls, and built-in rules
Documentation
Full documentation at phoenixml.dev
License
Apache-2.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.8 | 0 | 8/26/2026 |
| 1.6.7 | 51 | 8/24/2026 |
| 1.6.6 | 56 | 8/23/2026 |
| 1.6.5 | 53 | 8/22/2026 |
| 1.6.4 | 82 | 8/16/2026 |
| 1.6.3 | 66 | 8/14/2026 |
| 1.6.2 | 57 | 8/13/2026 |
| 1.6.1 | 71 | 8/4/2026 |
| 1.6.0 | 73 | 7/31/2026 |
| 1.5.0 | 74 | 7/27/2026 |
| 1.4.25 | 77 | 7/17/2026 |
| 1.4.24 | 72 | 7/14/2026 |
| 1.4.23 | 90 | 7/12/2026 |
| 1.4.22 | 83 | 7/10/2026 |
| 1.4.21 | 75 | 7/9/2026 |
| 1.4.20 | 65 | 7/9/2026 |
| 1.4.19 | 80 | 7/9/2026 |
| 1.4.18 | 71 | 7/8/2026 |
| 1.4.17 | 77 | 7/7/2026 |
| 1.4.16 | 83 | 6/30/2026 |
Four engine fixes, two of them from Martin Honnen's XSpec testing, and a conformance harness
that turned out to be measuring nothing at all. XSpec suites completing end to end went from
24 to 60.
### A typed template's `xsl:copy` of an attribute went to the caller's element
Reported by Martin Honnen against XSpec 4.0.3, which failed with
`XTTE0505: Template 'identity' return value does not match declared type Node: expected
exactly one item, got 0`.
The attribute was never lost — it was delivered to the wrong element. `xsl:copy` of an
attribute joined a typed template's result sequence only when nothing else was collecting
attributes, but XSpec's identity template is invoked while the CALLER's `xsl:copy` is still
collecting its own. So `@status` was appended to the caller's element and the template
returned nothing, and the error blamed the template rather than naming where the item went.
Found while fixing it: `<xsl:copy/>` with no content on a document node produced nothing at
all, because both code paths required content. A shallow copy with no content is legal.
### One implementation of global content binding, not two
The engine had two implementations of "bind a global variable from a content sequence
constructor". The eager, dependency-ordered pass honoured the declared `as` type; the lazy
on-demand pass stored the serialized content as a raw `xs:string` and ignored `as` entirely.
So one declaration bound a document node or a string depending on which pass reached it
first — and which pass ran depended on whether the dependency analysis spotted the reference.
It does not recognise EQName (`$Q{uri}local`) references, which XSpec generates throughout.
The eager path already carried a fix for this exact `XPTY0020`, from Martin's earlier DocBook
report. It had been applied to one of the two paths.
### A typed variable with empty content is `()`, not `""`
XSLT 3.0 §9.3 gives the zero-length-string rule for a variable with "neither a select
attribute nor an as attribute". The `as` clause was missing from the implementation, so every
typed empty binding held a string: `<xsl:variable as="empty-sequence()"/>` reported a count of
1, and `<xsl:variable as="element(x)*"/>` held a string rather than elements.
### Accumulators: untypedAtomic is converted to the declared type
`CoerceAtomicValue` had no arm for `XsUntypedAtomic` — what atomizing a node actually yields —
so an accumulator declared `as="xs:double"` whose rule selected `@amount` silently kept its
seed and reported it as the answer. The sibling `sum` accumulator was correct throughout,
because arithmetic operators atomize; one stylesheet gave a right sum and a wrong min.
Accumulator errors are deferred per spec, but "deferred" had been implemented as
`catch (Exception)` at five sites with three different behaviours — swallowing cancellation
and CLR faults, which is how the type bug stayed invisible. Now only genuine dynamic errors
defer, and rethrow preserves the original stack.
### `XsltException` carries an `ErrorCode`
The engine asked "which error is this?" by searching message text in seven places, five with
`Contains` — so an error merely mentioning `XTDE0640` in its prose matched as that error.
### The conformance suite was measuring nothing
Not a shipped-engine bug, but worth recording: the XSLT conformance run reported
"257 passed" having executed zero cases. The fixture checked `Directory.Exists` against the
empty directory skeleton the build creates, so every test set loaded no cases and each
reported "all filtered by dependencies" — a conclusion the code never verified. Fixed, along
with the harness refusing DTDs the corpus ships and declaring streaming unsupported when it
is 95.9% conformant.
Xslt.Tests 1289 passed, 0 failed, 1 skipped (1204 at 1.6.7)
XSLT conformance 10232/10630 = 96.26% — the first figure measured against a corpus
that was actually loaded
XSpec: 60 of 284 suites complete end to end, up from 24