xslt 1.6.8

dotnet tool install --global xslt --version 1.6.8
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local xslt --version 1.6.8
                    
This package contains a .NET tool you can call from the shell/command line.
#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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed

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