PhoenixmlDb.Xslt
1.6.8
dotnet add package PhoenixmlDb.Xslt --version 1.6.8
NuGet\Install-Package PhoenixmlDb.Xslt -Version 1.6.8
<PackageReference Include="PhoenixmlDb.Xslt" Version="1.6.8" />
<PackageVersion Include="PhoenixmlDb.Xslt" Version="1.6.8" />
<PackageReference Include="PhoenixmlDb.Xslt" />
paket add PhoenixmlDb.Xslt --version 1.6.8
#r "nuget: PhoenixmlDb.Xslt, 1.6.8"
#:package PhoenixmlDb.Xslt@1.6.8
#addin nuget:?package=PhoenixmlDb.Xslt&version=1.6.8
#tool nuget:?package=PhoenixmlDb.Xslt&version=1.6.8
PhoenixmlDb.Xslt
XSLT 4.0 transformation engine for PhoenixmlDb — transform XML documents into HTML, JSON, CSV, text, or other XML formats.
Features
- XSLT 3.0/4.0 — template matching, streaming, accumulators, packages, maps/arrays
- All output methods — HTML5, XML, XHTML, JSON, text, CSV, adaptive
- Multiple outputs —
xsl:result-documentfor multi-file generation - Full XPath 4.0 — 240+ built-in functions available in all expressions
- Packages — reusable stylesheet libraries with visibility control
Quick example
using PhoenixmlDb.Xslt;
var transformer = new XsltTransformer();
await transformer.LoadStylesheetAsync(
File.ReadAllText("style.xsl"),
new Uri(Path.GetFullPath("style.xsl")));
transformer.SetParameter("title", "My Report");
var html = await transformer.TransformAsync(
File.ReadAllText("data.xml"));
// Handle secondary outputs (xsl:result-document)
foreach (var (href, content) in transformer.SecondaryResultDocuments)
File.WriteAllText(Path.Combine(outputDir, href), content);
See the full API overview in the README.
Related packages
| Package | Description |
|---|---|
| PhoenixmlDb.Core | Core types and XDM data model (dependency) |
| PhoenixmlDb.XQuery | XQuery 4.0 query engine (dependency) |
| PhoenixmlDb.Xslt.Cli | xslt command-line tool |
Documentation
Full documentation at phoenixml.dev
License
Apache 2.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- PhoenixmlDb.Core (>= 1.6.7)
- PhoenixmlDb.XQuery (>= 1.6.8)
-
net8.0
- PhoenixmlDb.Core (>= 1.6.7)
- PhoenixmlDb.XQuery (>= 1.6.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.8 | 30 | 8/26/2026 |
| 1.6.7 | 54 | 8/24/2026 |
| 1.6.6 | 61 | 8/23/2026 |
| 1.6.5 | 82 | 8/22/2026 |
| 1.6.4 | 117 | 8/16/2026 |
| 1.6.3 | 88 | 8/14/2026 |
| 1.6.2 | 86 | 8/13/2026 |
| 1.6.1 | 106 | 8/4/2026 |
| 1.6.0 | 108 | 7/31/2026 |
| 1.5.0 | 127 | 7/27/2026 |
| 1.4.25 | 106 | 7/17/2026 |
| 1.4.24 | 88 | 7/14/2026 |
| 1.4.23 | 131 | 7/12/2026 |
| 1.4.22 | 107 | 7/10/2026 |
| 1.4.21 | 111 | 7/9/2026 |
| 1.4.20 | 103 | 7/9/2026 |
| 1.4.19 | 100 | 7/9/2026 |
| 1.4.18 | 101 | 7/8/2026 |
| 1.4.17 | 104 | 7/7/2026 |
| 1.4.16 | 130 | 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