PhoenixmlDb.XSLT

XsltTransformer

Primary API for running XSLT transformations in .NET.

#XsltTransformer

Namespace: PhoenixmlDb.Xslt

Primary API for running XSLT transformations in .NET.

XsltTransformer provides a simple three-step workflow for XSLT processing: create an instance, load a stylesheet, and transform XML input. It supports XSLT 3.0 and 4.0 features including streaming, packages, higher-order functions, maps/arrays.

Invocation styles: XSLT defines three ways to start a transformation:

Thread safety: Instances are lightweight and not thread-safe. Create a new XsltTransformer for each transformation rather than sharing one across threads.

Basic usage:

csharp
var transformer = new XsltTransformer();
await transformer.LoadStylesheetAsync(myXsltString);
transformer.SetParameter("reportDate", "2025-01-15");
string result = await transformer.TransformAsync(inputXml);
            

Using initial-template invocation (no source document):

csharp
var transformer = new XsltTransformer();
await transformer.LoadStylesheetAsync(generatorStylesheet);
transformer.SetInitialTemplate("main");
string result = await transformer.TransformAsync(null);
            

#Properties

Name Description
AllowDtdProcessing Controls whether DTD processing is allowed when loading stylesheets. Default is false (DTDs are prohibited) for security.
AllowedOutputMethods The serialization methods a transformation may deliver results in, or null (the default) for any; see XsltTransformOptions.AllowedOutputMethods. Enforced against the method each result is actually serialized with, so it also covers the default-method rule and a run-time method on xsl:result-document.
DisableStreaming When true, the string and node transform overloads evaluate against an in-memory tree even when the stylesheet's initial mode is streamable, instead of taking the streaming pass. Off by default.
HasStreamableMode True when the loaded stylesheet declares at least one streamable mode (either an explicit <xsl:mode streamable="yes"/> declaration or a streamable default mode). The CLI uses this signal to auto-select the streaming transform path for file inputs β€” non-streamable stylesheets always run on the materialising path. Returns false if no stylesheet is loaded yet.
ImplicitTimezone The implicit timezone of each transformation: the timezone a date, time or dateTime with none of its own is taken to be in, and the timezone of current-dateTime(). Null, the default, is the timezone of the machine. TimeSpan.Zero gives the same result on every machine. See XsltTransformOptions.ImplicitTimezone.
MaxResultDocuments Maximum number of secondary result documents allowed per transformation. Default is 1000. Set to 0 for unlimited.
MessageListener Listener for xsl:message output. Receives the message text and a boolean indicating whether terminate="yes" was specified. When not set, messages are silently discarded.
MessageListenerWithLocation Extended listener for xsl:message that also receives source location (line, column). Takes precedence over XsltTransformer.MessageListener when set.
OutputDeclarations The loaded stylesheet's xsl:output declarations, named and unnamed, including those of imported and included modules (merged by import precedence). Empty before LoadStylesheetAsync. Declarations in packages brought in by xsl:use-package are not listed: they apply only to xsl:result-document within that package, and XsltTransformer.AllowedOutputMethods still governs them.
PreloadedResources Pre-fetched contents for URIs that xsl:import / xsl:include / fn:doc() would otherwise need to fetch over HTTP synchronously. Required on Blazor WebAssembly, which cannot block the calling thread; ignored on runtimes that can. Async-fetch the resources in your host code (via HttpClient with await or via JS interop) and assign before calling XsltTransformer.LoadStylesheetAsync. See XsltTransformer.PreloadedResources for a usage example.
PrincipalResultTree The principal result of the last transformation as a tree, with the type annotations that validation gave it, when the whole result was validated as it was built (an element with xsl:validation, validation= or type= at its top); null otherwise. The serialized result is text, and text read again is untyped.
RegexMatchTimeout Longest a single regular-expression operation may run before it is abandoned; see XsltTransformOptions.RegexMatchTimeout. Default: null (no engine timeout).
ResourcePolicy Optional resource security policy. When set, controls which URIs the stylesheet can access via doc(), unparsed-text(), collection(), xsl:result-document, and xsl:import/xsl:include. See ResourcePolicy.ServerDefault for a secure server configuration.
ResultDocumentHandler Optional callback that provides a TextWriter for each secondary result document produced by xsl:result-document. When set, secondary documents are written directly to the provided writer instead of accumulating in XsltTransformer.SecondaryResultDocuments.
SchemaAware Whether stylesheets are compiled as by a schema-aware processor. True by default: a type attribute and validation="strict" are accepted in any stylesheet, and validate against the built-in types and the schemas of XsltTransformer.SchemaProvider. Set to false for the behaviour of a basic XSLT processor (XSLT 3.0 Β§27.1): those attributes are refused with XTSE1660 in a stylesheet that has no xsl:import-schema. Read when a stylesheet is loaded.
SchemaProvider Schema provider for schema-aware processing. Defaults to a fresh XsdSchemaProvider with no schemas loaded; any xsl:import-schema declarations encountered while loading a stylesheet are routed to this provider via ISchemaProvider.ImportSchema.
SchemaSetForTest For a test: the schema set in use.
SecondaryResultDocuments Secondary result documents produced by xsl:result-document, keyed by the href attribute value.
SourceValidation Validation of the source document before the transformation (ValidationMode.Strict or ValidationMode.Lax), against the schemas of XsltTransformer.SchemaProvider: the schemas the stylesheet imports, and any the host added to the provider. The nodes of a validated source have the type annotations of the schema. Null, the default, leaves the source untyped. See XsltTransformOptions.SourceValidation.
StrictStreamability When true, a template rule of a streamable mode, or the body of an xsl:source-document with streamable="yes", that is not guaranteed-streamable makes the transformation fail with XTSE3430, as XSLT 3.0 Β§19.10 requires of a streaming processor. Off by default: the engine then runs it on a buffered copy of the matched subtree, or on a tree, and the result is the same. Has no effect together with XsltTransformer.DisableStreaming, which asks for tree evaluation.
TraceListener Sets a trace listener for debugging template matching, function calls, and built-in rule invocations during transformation.
UsesSharedSchemaSetForTest For a test: whether the schemas in use are a compiled set from the cache, which other transformers share.
WarningListener Receives processor warnings β€” currently the xsl:mode warning-on-no-match diagnostic, reported once per node processed with no matching template rule.
XQueryModules XQuery library modules for fn:load-xquery-module, by module namespace URI: the module files to load when a call names that namespace without location hints. Like a query's own module map, these are the host's choice and are not checked against XsltTransformer.ResourcePolicy.

#Methods

#AddInitialFunctionArgument(Object)

Adds a positional argument for the initial function call.

Parameters:

  • value β€” The argument value. Arguments are positional β€” add them in the same order as the xsl:param declarations in the target xsl:function.

Only used with call-function invocation (see

). Call this method once per function parameter, in declaration order.

#BuildTransformOptions(Boolean,PhoenixmlDb.Xslt.Engine.RawResultBox,Threading.CancellationToken)

Builds the

from the transformer's current configuration (parameters, initial-template / mode / function selection, listeners, resource policy, preloaded resources). Centralized so the four

TransformAsync

/

TransformToValueAsync

overloads stay in sync as new options are added.

#EnableXInclude(Boolean,PhoenixmlDb.Core.Xml.IXmlResourceResolver)

Enables XInclude 1.0 expansion of the principal source document. When on, any

xi:include

(

parse="xml"

) elements in the input passed to

are expanded before the document is transformed. Off by default.

Parameters:

  • allowRemote β€” When true, remote (http:/https:) include targets may be fetched. Defaults to false (only file:/relative targets resolve).

  • resolver β€” Optional host resolver for include targets; when null a built-in local-file resolver honoring allowRemote is used.

A source base URI is required so relative

href

s resolve β€” set it via

before transforming.

#ExtractNonNodeHeadFromSequence(PhoenixmlDb.Xdm.XdmSequence)

Returns the head item of

when it isn't an

β€” typically a map, array, atomic value or function-item produced by a previous transform's

parse-json

/

map:*

/

array:*

call. The caller routes node items through

instead.

#ExtractSourceFromSequence(PhoenixmlDb.Xdm.XdmSequence)

Pulls the first node item out of a sequence (to use as principal source) and the store backing the sequence's node items. Returns (null, null) for null or empty input β€” the transformation will run source-less.

#LoadStylesheet(String,Uri,Collections.Generic.Dictionary<String,String>,Collections.Generic.Dictionary<String,Collections.Generic.List<ValueTuple<String,String>>>,PhoenixmlDb.Xslt.PackageVersionResolution)

Loads and compiles a stylesheet, returning on the calling thread. The synchronous form of

; see it for the parameters and errors.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

The calling thread is blocked until the stylesheet is loaded and does not change: use this from a UI thread that has no

, where

await LoadStylesheetAsync(...)

would resume on a thread-pool thread. Not available on browser WebAssembly, which cannot block.

#LoadStylesheetAsync(String,Uri,Collections.Generic.Dictionary<String,String>,Collections.Generic.Dictionary<String,Collections.Generic.List<ValueTuple<String,String>>>,PhoenixmlDb.Xslt.PackageVersionResolution)

Compiles and loads an XSLT stylesheet from its XML source text.

Parameters:

  • stylesheetXml β€” The complete XSLT stylesheet as an XML string. Must be well-formed XML with an xsl:stylesheet or xsl:transform root element.

  • baseUri β€” Base URI used to resolve relative references in xsl:import, xsl:include, and the doc() / document() functions. If null, relative URIs cannot be resolved and will cause an error.

  • staticParams β€” XSLT 3.0 static parameters (xsl:param with static="yes"). These are evaluated at compile time and can influence conditional compilation via xsl:use-when and static-params. Keys are parameter local names; values are string representations.

  • packageCatalog β€” Maps XSLT 3.0 package names to available versions and file paths, enabling xsl:use-package to locate and load package dependencies. Each key is a package name URI; the value is a list of (Version, FilePath) tuples.

  • packageVersionResolution β€” Policy for choosing among multiple package versions that satisfy an xsl:use-package/@package-version range. Defaults to PackageVersionResolution.Highest.

Returns: A task that completes when the stylesheet is loaded. HTTP-resolved imports are fetched first. The parse, including static expressions such as use-when, then runs on a dedicated large-stack thread (as transformations do), so the calling thread is not held while it runs. On browser-wasm, which cannot start threads, the parse runs inline.

Exceptions:

  • ArgumentNullException β€” stylesheetXml is null.

  • XsltException β€” The stylesheet contains syntax errors or invalid XSLT constructs.

Which thread your code resumes on.

Since 2.6.0 the parse runs on another thread, so code after

await

resumes on the captured

when there is one, otherwise on a thread-pool thread. In 2.5.1 the parse ran inline and the task usually completed on the calling thread, so a host with no synchronization context (a plugin called from a native message loop) stayed on its UI thread; it no longer does, and must marshal UI work back itself, exactly as after

(xslt#309).

#LooksLikeXml(String)

Heuristic: the result starts with a tag, suggesting XML markup.

#ParseExternalParamValue(String)

Same value-parsing heuristics as

StylesheetParser.PopulateExternalStaticParams

: recognise XPath-shaped literals, bare booleans, and numeric strings, falling through to

xs:untypedAtomic

for free-form strings. Keeps the runtime-side variable in sync with what the static-param resolver decided about the same value.

#ResolveSchemaImports(PhoenixmlDb.Xslt.Ast.XsltStylesheet,Uri)

Forwards every captured

xsl:import-schema

declaration to the registered

. Schema-location URIs are resolved against the stylesheet base URI before being handed to the provider so relative hints work.

#SetBaseOutputUri(Uri)

Sets the base output URI β€” where the principal result will be written (XSLT 3.0 Β§2.3).

Parameters:

  • uri β€” Absolute URI of the principal output destination.

This is what

fn:current-output-uri()

reports while the principal result is being produced, and what a relative

xsl:result-document/@href

resolves against. Leave it unset when the result has no URI β€” writing to stdout or to a string β€” in which case

current-output-uri()

correctly returns the empty sequence.

#SetCollection(String,Collections.Generic.List<String>)

Registers a named collection of document file paths, making them available to the XPath

fn:collection()

function during transformation.

Parameters:

  • uri β€” The collection URI that the stylesheet passes to fn:collection(). Use an empty string for the default collection (called with no arguments).

  • documentPaths β€” File system paths to XML documents that comprise the collection.

#SetInitialFunction(String,String)

Sets the initial function to call, using the XSLT 3.0 "call function" invocation style.

Parameters:

  • name β€” The function name. Must match a public xsl:function declared with visibility="public" (or the default visibility in XSLT 3.0).

  • namespaceUri β€” The namespace URI of the function. Stylesheet functions must be in a non-null namespace.

When using call-function invocation, supply arguments via XsltTransformer.AddInitialFunctionArgument in the order declared by the function's xsl:param elements. No source document is required β€” pass null to XsltTransformer.TransformAsync.

#SetInitialMode(String,String)

Sets the initial mode, determining which set of templates is applied to the source document.

Parameters:

  • mode β€” The mode name. Use "#unnamed" to explicitly select the unnamed (default) mode, or a specific mode name to select templates declared with that mode attribute.

  • namespaceUri β€” The namespace URI of the mode, or null for modes in no namespace.

Modes allow a stylesheet to define multiple sets of template rules for the same input nodes. For example, a stylesheet might have a "toc" mode for generating a table of contents and a default mode for the main output.

If no initial mode is set, the unnamed (default) mode is used. The stylesheet's xsl:stylesheet/@default-mode attribute can also influence this behavior.

#SetInitialModeSelect(String)

Sets an XPath expression to determine the initial match selection for the initial mode.

Parameters:

  • select β€” An XPath expression evaluated with the source document as context. Templates from the initial mode are applied to each item in the resulting sequence, rather than to the document root.

This corresponds to the XSLT 3.0 initial-match-selection concept. It allows you to apply templates to a computed set of nodes rather than the single root node. For example, "//chapter" would apply templates to every chapter element in the source document.

#SetInitialTemplate(String,String)

Sets the initial named template for call-template invocation.

Parameters:

  • name β€” The template name. For templates in a namespace, use either a prefixed name (e.g., "my:main") with the namespace URI in namespaceUri, or just the local name with the namespace URI.

  • namespaceUri β€” The namespace URI of the template, or null for templates in no namespace.

When an initial template is set, the transformation begins by calling that named template rather than applying templates to the source document. This means the inputXml parameter of XsltTransformer.TransformAsync can be null β€” the template generates output without needing a source document.

The conventional entry-point template name is "xsl:initial-template" (in the XSLT namespace), which is the XSLT 3.0 default initial template.

#SetInitialTemplateParameter(PhoenixmlDb.Core.QName,Object)

Sets an initial template parameter, passed via

xsl:with-param

to the initial template when using call-template invocation.

Parameters:

  • name β€” The qualified name matching an xsl:param in the initial template.

  • value β€” The value to bind to the parameter.

These parameters are distinct from stylesheet-level parameters set via

. They correspond to parameters declared inside the named template specified by

.

#SetInitialTunnelParameter(PhoenixmlDb.Core.QName,Object)

Sets an initial template tunnel parameter, which propagates through the call chain without being explicitly declared at each level.

Parameters:

  • name β€” The qualified name of the tunnel parameter.

  • value β€” The value to bind to the tunnel parameter.

Tunnel parameters are an XSLT 2.0+ feature that allows values to "tunnel" through intermediate template calls to deeply nested templates that declare matching

xsl:param tunnel="yes"

parameters.

#SetParameter(PhoenixmlDb.Core.QName,Object)

Sets a global stylesheet parameter whose name is NAMESPACED, preserving its typed value.

Parameters:

  • name β€” The qualified name of a top-level xsl:param. Pass a QName rather than a string: the string overloads take a LOCAL name and have no spelling for a namespace.

  • value β€” The typed value to bind, or null for the empty sequence.

#SetParameter(String,Object)

Sets a stylesheet parameter with a typed value, preserving its XDM type.

Parameters:

  • name β€” The local name of an xsl:param declared at the top level of the stylesheet.

  • value β€” The typed value to bind. .NET types are mapped to XDM types: Int32 and Int64 become xs:integer, Double becomes xs:double, Boolean becomes xs:boolean, Decimal becomes xs:decimal, and String becomes xs:untypedAtomic. An XDocument or XmlDocument becomes a document node, and an XElement or XmlElement an element node, copied into the transformation so XPath can navigate it. Pass null to set the parameter to an empty sequence.

Parameters must match

xsl:param

declarations in the stylesheet. If the stylesheet declares a parameter with

as="xs:integer"

and you supply a string, a type error will occur at runtime.

#SetParameter(String,String)

Sets a stylesheet parameter with a string value.

Parameters:

  • name β€” The local name of an xsl:param declared at the top level of the stylesheet.

  • value β€” The string value to bind. The value is supplied as xs:untypedAtomic, which means it will be automatically promoted during comparisons β€” for example, comparing it to an xs:double will succeed without an explicit cast.

If you need the parameter to carry a specific XDM type (e.g., xs:integer or xs:boolean), use the XsltTransformer.SetParameter overload with a typed .NET value such as Int32 or Boolean.

#SetSourceDocumentUri(Uri)

Sets the URI of the source document, used for

base-uri()

and

document-uri()

resolution during transformation.

Parameters:

  • uri β€” The URI to associate with the source document. This does not load the document from the URI β€” it only sets the base URI metadata on the document node created from the inputXml string passed to XsltTransformer.TransformAsync.

Setting a source document URI is important when the stylesheet uses relative URIs in

doc()

or

document()

calls that should resolve relative to the source document's location rather than the stylesheet's base URI.

#SetSourceSelect(String)

Sets an XPath expression to select the initial context node from the source document.

Parameters:

  • select β€” An XPath expression evaluated against the source document. The result becomes the initial context item for the transformation. For example, "/doc" selects the document element named doc instead of the document root node.

By default, the initial context item is the document node (root) of the source document. Use this method when the stylesheet expects a specific element or node as its starting context rather than the root.

#Transform(IO.TextReader,IO.TextWriter,Threading.CancellationToken)

Transforms XML read from

and writes the result to

, returning on the calling thread. The synchronous form of

.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

#Transform(PhoenixmlDb.Xdm.XdmSequence,Threading.CancellationToken)

Transforms an XDM sequence and returns the serialized result on the calling thread. The synchronous form of

.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

#Transform(String,Threading.CancellationToken)

Transforms

and returns the serialized result on the calling thread. The synchronous form of

.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

The calling thread is blocked until the transformation finishes and does not change; the transformation itself runs on the engine's large-stack thread, so deep recursion is as safe as with the async method. Nothing is queued to the thread pool unless the transformation performs real I/O. Not available on browser WebAssembly.

#TransformAsync(IO.Stream,IO.Stream,Threading.CancellationToken)

Transforms XML from a

and writes to a

. When the loaded stylesheet's initial mode is streamable, the input stream is fed directly to the streaming engine without an intervening

ReadToEndAsync

, bounding peak memory. Non-streaming stylesheets retain the original buffered path.

#TransformAsync(IO.Stream,IO.TextWriter,Threading.CancellationToken)

Streams XML from a

and writes the serialized primary result incrementally to a

. Requires the stylesheet's initial mode to be streamable; otherwise an

is thrown by the engine.

Cancellation is not transactional β€” content already delivered to

before cancellation cannot be retracted.

#TransformAsync(IO.Stream,Threading.CancellationToken)

Transforms XML from a

source.

#TransformAsync(IO.TextReader,IO.TextWriter,Threading.CancellationToken)

Transforms XML from a

and writes to a

.

#TransformAsync(IO.TextReader,Threading.CancellationToken)

Transforms XML from a

source.

#TransformAsync(PhoenixmlDb.Xdm.XdmSequence,Threading.CancellationToken)

Transforms an

source β€” pass the typed result of one transformation directly into another, without serializing through XML markup. The engine reads the sequence's backing

Store

to navigate any node items.

When source is null or empty, the transformation runs without a principal source document β€” appropriate for xsl:initial-template / xsl:initial-function invocation. Otherwise the first node item in the sequence becomes the principal source.

If the sequence contains node items, its XdmSequence.Store must be a compatible XdmInMemoryStore β€” typically a sequence produced by a previous call to XsltTransformer.TransformToSequenceAsync.

#TransformAsync(String,IO.TextWriter,Threading.CancellationToken)

Transforms XML and writes the primary result to a

.

#TransformAsync(String,Threading.CancellationToken)

Transforms an XML string using the loaded stylesheet and returns the serialized primary result document.

Parameters:

  • inputXml β€” The source XML document to transform, or null when using call-template or call-function invocation that does not require a source document. When null, an empty placeholder document is used internally.

  • ct β€” Cancellation token for aborting long-running transformations. When cancelled, an OperationCanceledException is thrown.

Returns: The serialized primary result document as a string. The serialization format (XML, HTML, text, JSON, or adaptive) is determined by the stylesheet's xsl:output declaration.

Exceptions:

  • InvalidOperationException β€” No stylesheet has been loaded. Call XsltTransformer.LoadStylesheetAsync first.

  • XsltException β€” A runtime error occurred during transformation, such as a type error, missing template, or evaluation failure.

  • OperationCanceledException β€” The ct cancellation token was triggered.

After this method returns, any secondary outputs produced by xsl:result-document instructions are available in XsltTransformer.SecondaryResultDocuments.

The transformation runs on a dedicated thread with a 256 MB stack, so deep recursion does not overflow the caller's stack. The calling thread is not held while it runs: the returned task completes when the transformation does. After the transformation first awaits real I/O (an HTTP document, for instance), the rest of it runs on the thread pool. On browser-wasm, which cannot start threads, it runs inline on the caller's stack.

Which thread your code resumes on. Because the work happens on another thread, the task completes there, and code after await resumes wherever the awaiter sends it: on the captured SynchronizationContext when there is one (WinForms, WPF, MAUI with their context installed), otherwise on a thread-pool thread. A host that calls from a UI thread WITHOUT a synchronization context β€” a plugin invoked from a native message loop, say β€” is therefore on a pool thread after the await and must marshal UI work (dialogs, editor calls) back to the UI thread itself.

#TransformToSequence(PhoenixmlDb.Xdm.XdmSequence,Threading.CancellationToken)

Transforms an XDM sequence and returns the result sequence on the calling thread. The synchronous form of

.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

#TransformToSequenceAsync(PhoenixmlDb.Xdm.XdmSequence,Threading.CancellationToken)

Transforms an

and returns the raw XDM result wrapped in another

that carries the engine's node-store, so the result can be passed directly to another

or XQuery call without serialization.

#TransformToValue(String,Threading.CancellationToken)

Transforms

and returns the raw result value on the calling thread. The synchronous form of

.

Exceptions:

  • PlatformNotSupportedException β€” Called on browser WebAssembly.

#TransformToValueAsync(String,Threading.CancellationToken)

Transforms

and returns the RAW XDM value of the transformation β€” the typed result (xs:boolean, xs:integer, map, array, node, …) preserved end-to-end, not serialized to a string and reparsed. Used by

fn:transform()

with

delivery-format='raw'

from XQuery, where the caller wants to consume the typed result directly rather than as XML markup.

Returns: The raw XDM value: a single item, an object?[] for sequences, or null for the empty sequence. A transformation whose result was CONSTRUCTED rather than returned β€” a literal result element, xsl:element, most stylesheets β€” has no typed value to hand back, because those nodes are written to the output buffer and never reach the sequence collector. For that case this returns the serialized markup as a String and the caller re-parses it, which is the same tiering XsltTransformEngine.TransformRawAsync uses. This used to be documented as honoured only for XsltTransformer.SetInitialFunction invocations, and it returned null for everything else β€” silently, so fn:transform from a query produced an empty ?output and exit code 0 for every node-constructing stylesheet (Martin Honnen xslt#173). The limitation was real, but describing it in the contract is not the same as it being correct.

#TransformToValueOrMarkupAsync(String,Threading.CancellationToken)

, with a constructed result tree marked as

so fn:transform can tell it from a typed xs:string result.

#TryParseToXdmDocument(String,PhoenixmlDb.Xslt.XdmInMemoryStore)

Parses serialized XML markup into an

registered with

. Returns null if parsing fails (the caller falls back to keeping the raw string).

#Fields

Name Description
_typedParametersByQName Global parameters supplied by QNAME rather than by local name. Separate from _typedParameters because that one is keyed by the string a caller passed and is wrapped as a no-namespace QName; there is no string spelling of a namespaced name that survives that wrapping.

#See also

  • XsltException