API Reference
Schemas and Validation
XSD schemas in XQuery, XSLT and .NET hosts: import schema, xsl:import-schema, XsdSchemaProvider, and the PhoenixmlDb.Core.Schema layer
#Schemas and Validation
The XQuery and XSLT engines compile and validate XSD 1.0 schemas through one shared layer in
PhoenixmlDb.Core, the PhoenixmlDb.Core.Schema namespace. That layer reads every schema
document, applies the limits, compiles, caches and validates, and it reports problems the same
way whichever engine asked. XSD 1.1 is not supported. The only 1.1 features the layer handles
are the vc: version-control markers and the 1.1 duration types (see
XSD 1.1). A schema that requires XSD 1.1 is refused with a message that says so.
This page describes PhoenixmlDb.Core 2.4.0, PhoenixmlDb.XQuery 2.8.0 and PhoenixmlDb.Xslt 2.8.0.
#Contents
#Using schemas from XQuery
#In the query
import schema namespace o = "urn:example:order" at "order.xsd";
validate strict { <o:order><o:qty>3</o:qty></o:order> } instance of schema-element(o:order)
-
import schemaloads the schema when the query is compiled. A relativeathint is resolved against the query's base URI (CompilationOptions.BaseUri), not against the current directory. Each hint is tried in turn. If none loads, the error isXQST0059, and it lists why each hint failed. Under a resource policy, each hint must be allowed for import, and so must every document the schema itself includes or imports. The exception is a document that the policy's resource resolver supplies. -
Static rules come first: binding
xmlorxmlnsisXQST0070, a prefix with an empty target namespace isXQST0057, and two imports of the same namespace areXQST0058. -
validate(strict,lax, ortype) validates a node against the imported schemas.validate strictof an element that has no declaration isXQDY0084.
Once a node has been validated, it carries its schema type:
-
data()returns values of the schema type, including restrictions, lists and unions. Atomizing a node whose type has element-only content isFOTY0012. -
Validated nodes match
element(*, p:T),schema-element(p:e)andschema-attribute(p:a), including through substitution groups. -
Each simple type the schema declares has a constructor function, and an atomic value keeps its schema type:
o:size('8') instance of o:sizeis true. Casts to schema-defined list and union types work. -
fn:idreffinds nodes that the schema types asxs:IDREForxs:IDREFS.
Nodes that were not validated are unaffected.
#XsdSchemaProvider
QueryEngine uses an ISchemaProvider (namespace PhoenixmlDb.XQuery) for schema features.
If you don't pass one, it creates a new, empty XsdSchemaProvider. If you pass null, schema
features are switched off.
using PhoenixmlDb.XQuery;
using PhoenixmlDb.XQuery.Execution;
var store = new XdmDocumentStore();
var engine = new QueryEngine(
nodeProvider: store,
documentResolver: store,
schemaProvider: new XsdSchemaProvider(compiledSchema)); // compiled once, see below
|
Constructor |
What it does |
|---|---|
|
|
Creates an empty provider. |
|
|
Creates a provider with these files loaded, as |
|
|
Creates a provider on a schema that the Core layer compiled. The compiled schema is shared and never changes. An |
|
Method |
Loads |
|---|---|
|
|
A schema file. |
|
|
Schema text with no location. A relative |
|
|
Same as above, from a string. |
|
|
Schema text. The documents it includes, imports or redefines are read only where |
|
|
Same as above, from a string. |
|
|
Schema text that came from |
|
|
What |
The overloads that take no policy resolve a schema's own references with no restriction: any
file the process can read, and any URL it can reach. If you vetted the schema text but not what
it points to, use a policy overload. ResourcePolicy is in PhoenixmlDb.XQuery.Security.
var provider = new XsdSchemaProvider { PatternMatchTimeout = TimeSpan.FromSeconds(1) };
provider.AddFromString("urn:example:order", xsdText,
new Uri("file:///srv/schemas/order.xsd"), ResourcePolicy.ServerDefault);
|
Property |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
|
Where |
|
|
|
|
Says where the documents a schema names are really read from (see XmlCatalog). What it maps a name to is still read under the load's resource policy. |
|
|
|
|
The most time one match of an |
ImportCache. A schema loaded from files is compiled once and shared by every provider that
imports the same files. On each use, the cache checks the size and time of every document of
the schema, includes and imports as well as the root, and asks that use's resource policy about
each one. A changed schema file or included file is therefore seen by the next query. A schema
is cached only when all of these hold:
-
its root is a file;
-
the provider has no
Catalog; -
the policy has no resource resolver;
-
the provider holds nothing the cache did not compile.
Anything else is compiled for that provider alone.
Pattern time limits. A pattern match that runs past PatternMatchTimeout throws
RegexMatchTimeoutException from the provider's own methods. A query reports it as FOER0000. A
schema whose own enumeration, default or fixed values run past the limit is refused with
XQST0059. A query that runs with QueryExecutionLimits.RegexMatchTimeout lowers the provider's
limit, and the limit stays lowered. A provider made from a CompiledSchema can have its limit
lowered but not raised or removed.
import schema compiles its schema while the query is being compiled, before any run-time limit
applies. To bound that step, set CompilationOptions.RegexMatchTimeout:
var result = engine.Compile(query, new CompilationOptions
{
BaseUri = "file:///srv/queries/report.xq",
RegexMatchTimeout = TimeSpan.FromSeconds(1),
});
If you compile queries you didn't write, set it to the same value as
QueryExecutionLimits.RegexMatchTimeout.
#Using schemas from XSLT
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="urn:example:order">
<xsl:import-schema namespace="urn:example:order" schema-location="order.xsd"/>
<xsl:template name="xsl:initial-template">
<o:order xsl:validation="strict"><o:qty>2</o:qty></o:order>
</xsl:template>
</xsl:stylesheet>
-
xsl:import-schemain any module, whether imported or included, is passed toXsltTransformer.SchemaProviderwhen the stylesheet loads. A relativeschema-locationis resolved against the stylesheet's base URI. Under a resource policy, each location must be allowed for import. If none is allowed, the error isXTSE0220. -
An inline
xs:schemachild ofxsl:import-schemais read through the provider. A relativeschemaLocationinside it is relative to the stylesheet. A provider that cannot take schema text givesXTSE0220. -
A bare
<xsl:import-schema/>, with no namespace and no location, loads nothing but makes the stylesheet schema-aware. In a stylesheet with noxsl:import-schemaat all,validation="strict"or"type"anddefault-validation="strict"areXTSE1660. -
validation=onxsl:documentandxsl:copy-ofis applied wherever the result goes.xsl:validationon a literal result element andvalidation=onxsl:elementare applied only when the element is written to the principal result; see Known limits. -
Constructor functions of imported schema types can be called (
o:size('8'),o:size#1), andfunction-availableanswers for them.cast as o:sizeandcastable as o:sizework.
The schema-aware typing described for XQuery applies to XPath in stylesheets.
#XsltTransformer.SchemaProvider
public PhoenixmlDb.XQuery.ISchemaProvider? SchemaProvider { get; set; }
By default this is a new XsdSchemaProvider that already holds a schema for the XML namespace,
so a schema can xs:import that namespace with no schemaLocation. Replace it before calling
LoadStylesheetAsync. If you set it to null, every schema-element/schema-attribute
reference becomes XPST0008 and every validation="strict" raises a run-time error.
Because the default provider is never empty, it doesn't share compiled schemas through
XsdSchemaProvider.ImportCache. Each transformer compiles its own xsl:import-schema. To
compile once and share across transformers, give each one a provider on a CompiledSchema. An
xsl:import-schema for a namespace that schema declares is then satisfied without loading
anything:
var transformer = new XsltTransformer { SchemaProvider = new XsdSchemaProvider(compiledSchema) };
await transformer.LoadStylesheetAsync(stylesheetXml);
XsltTransformer.RegexMatchTimeout is passed to the provider before the first schema import, so
it also bounds pattern facets while the stylesheet's schemas compile.
#The Core schema layer
Hosts that compile or validate directly use the PhoenixmlDb.Core.Schema namespace (package
PhoenixmlDb.Core). A compilation has two steps:
-
Read the closure. The layer reads the roots, then every document they include, import or redefine, each one once, within the limits.
-
Compile in memory. Nothing is fetched in this step. If the compiler asks for a document that step 1 didn't read, the compilation fails.
#End-to-end example
This example compiles a schema from text, with no file or network access, validates a document against it, and prints the diagnostics.
using PhoenixmlDb.Core.Schema;
const string Xsd = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:example:order" xmlns="urn:example:order"
elementFormDefault="qualified">
<xs:element name="order">
<xs:complexType>
<xs:sequence>
<xs:element name="qty" type="xs:positiveInteger"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
""";
var schemaUri = new Uri("urn:example:order.xsd");
CompiledSchema schema;
try
{
schema = await SchemaCompiler.CompileAsync(
[schemaUri],
SchemaAccessGate.SuppliedOnly,
[SchemaSource.FromText(Xsd, schemaUri)]);
}
catch (SchemaCompilationException ex)
{
foreach (var diagnostic in ex.Diagnostics)
Console.WriteLine(diagnostic);
return;
}
var result = SchemaValidator.Validate(
schema,
"""<order xmlns="urn:example:order"><qty>0</qty></order>""",
new Uri("urn:example:order-1.xml"));
Console.WriteLine(result.IsValid ? "valid" : "invalid");
foreach (var diagnostic in result.Diagnostics)
Console.WriteLine($"{diagnostic.Code ?? "-"} {diagnostic}");
This prints invalid, then one error with code cvc-datatype-valid.1.2.1, located at
urn:example:order-1.xml(1,42).
#SchemaSource
SchemaSource is a schema document that the host supplies itself. It is always named by an
absolute URI. That URI is what an xs:include or xs:import elsewhere refers to it by, and
the base its own relative references resolve against. A relative URI is an ArgumentException.
|
Factory |
Use |
|---|---|
|
|
Schema text. The version token is a SHA-256 of the text. |
|
|
A manifest resource. A missing resource is an |
|
|
Content the host reads on demand, such as a schema held in a database. |
Properties: Uri, and Version (the token a compiled schema is compared by).
A document that is supplied as a source is never passed to the gate. A schema on disk or on the network needs no source: it is named by URI and read through the gate.
#ISchemaAccessGate
The gate is the only route by which the layer reads a document the host did not supply: a root
named by URI, and every xs:include, xs:import and xs:redefine in the whole closure. Nothing
in the layer opens a file or a connection itself.
public interface ISchemaAccessGate
{
string Identity { get; }
ValueTask<SchemaDocumentContent?> OpenAsync(SchemaDocumentRequest request, CancellationToken cancellationToken);
ValueTask<string?> GetVersionAsync(SchemaDocumentRequest request, CancellationToken cancellationToken);
}
-
SchemaDocumentRequest(Uri Uri, string Location, Uri? Referrer)gives the absolute URI, the location as it was written, and the referring document.IsRootis true whenReferreris null. -
OpenAsyncreturnsnew SchemaDocumentContent(stream, version), ornull. A refused document and a missing one both returnnull, so a refusal doesn't reveal whether the document exists. The layer disposes the stream. -
GetVersionAsyncreturns the version token without the content. The cache uses it. -
Identitynames the gate's rules, so that differently configured gates get separate cache entries. It does not grant access by itself. -
Within one compilation each document is opened at most once, so a gate can count opens against a budget. An exception thrown by a gate reaches the caller unchanged.
|
Gate |
Admits |
|---|---|
|
|
Nothing. A schema can be made only of the supplied |
|
|
|
The layer has no gate that fetches from the network. A host that wants one implements it.
#SchemaCompiler
ValueTask<CompiledSchema> CompileAsync(IEnumerable<Uri> roots, ISchemaAccessGate gate,
IEnumerable<SchemaSource>? sources = null, SchemaCompileOptions? options = null,
CancellationToken cancellationToken = default);
CompiledSchema Compile(IEnumerable<Uri> roots, ISchemaAccessGate gate,
IEnumerable<SchemaSource>? sources = null, SchemaCompileOptions? options = null);
Roots must be absolute URIs, and at least one is required. Compile blocks the calling thread.
Use it only when your gate and sources answer at once (files, text, resources) or when blocking a
thread is acceptable. The cancellation token is passed to the gate and the sources, and is checked
between documents. The compile step itself can't be interrupted.
Failures throw SchemaCompilationException. That includes a document that isn't available, is
too large, isn't well-formed, has a document type declaration or nests too deeply, as well as a
schema that doesn't compile. SchemaCompiler.MaxNestingDepth is 512.
CompiledSchema never changes. If its documents change, the schema is compiled again into a new instance.
|
Member |
Meaning |
|---|---|
|
|
The compiled |
|
|
The URIs the schema was requested by. |
|
|
Every document that was read, as |
|
|
Warnings from the compilation. A schema with errors is never constructed. |
|
|
The identity of the gate its documents were read through. |
|
|
The pattern limit it was compiled with, or |
#SchemaCompileOptions
A record. SchemaCompileOptions.Default is used when you pass null.
|
Property |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
16 MiB |
The largest single schema document. The layer never takes more than this from a stream. |
|
|
|
64 MiB |
The total size of all documents in one compilation. |
|
|
|
1,024 |
The most documents one compilation may read, roots included. |
|
|
|
|
Reads a schema written for XSD 1.1 as a 1.0 processor should (see XSD 1.1). With |
|
|
|
|
Consulted for each root, for each |
|
|
|
|
The most time one match of an |
If you compile schemas you didn't write, or validate values you didn't write against patterns that
may backtrack, set PatternMatchTimeout. With a limit set, a schema whose own enumeration,
default or fixed values run past it does not compile.
#SchemaCache
SchemaCache keeps compiled schemas so that each one is compiled once and used until any
document of its closure changes, not only a root. Schemas are looked up by their roots, the
gate's identity and the compile options. A supplied SchemaSource is compared by its Version.
Every other document is checked with the gate's GetVersionAsync.
var cache = new SchemaCache(new SchemaCacheOptions { CheckInterval = TimeSpan.FromSeconds(5) });
var gate = SchemaAccessGate.LocalFiles("/srv/schemas");
CompiledSchema schema = await cache.GetAsync([new Uri("file:///srv/schemas/order.xsd")], gate);
cache.Invalidate(new Uri("file:///srv/schemas/common.xsd")); // drops every schema that uses it
|
|
Default |
Meaning |
|---|---|---|
|
|
30 seconds |
How long a cached schema is handed out without checking its documents. |
|
|
256 |
The most compiled schemas kept. The least recently used is evicted first. |
|
|
|
The clock the interval is measured by. |
|
|
Meaning |
|---|---|
|
|
Returns the cached schema if it is still current and |
|
|
The blocking form. |
|
|
Drops every compiled schema that has |
|
|
Drops every compiled schema. |
|
|
How many compiled schemas the cache holds. |
|
|
A process-wide instance. The 2.8.0 engines don't use it: |
When several requests for the same schema arrive together, it is compiled once. A failed
compilation is not kept. CheckInterval must not be negative unless it is infinite, and
MaxEntries must be at least 1. Otherwise the constructor throws ArgumentOutOfRangeException.
#SchemaValidator
SchemaValidationResult Validate(CompiledSchema schema, string instance, Uri? sourceUri = null,
SchemaValidationOptions? options = null, CancellationToken cancellationToken = default);
There are also overloads that take a TextReader or a Stream instance. sourceUri is the name
diagnostics use for the document. Any number of validations can run against one compiled schema
at once. The instance is read with no document type declaration allowed and nothing resolved.
xsi:schemaLocation, xsi:noNamespaceSchemaLocation and inline schemas in the instance are
ignored. The schema is always the one you pass in.
|
|
Default |
Meaning |
|---|---|---|
|
|
|
The document element must be declared by the schema. If it isn't, the error has code |
|
|
|
Checks |
|
|
|
Includes the schema processor's warnings with the errors. |
|
|
1,000 |
Validation stops after this many diagnostics. |
|
|
0 (no limit) |
How deeply the instance's elements may nest. Deeper is an error, and validation stops. |
|
|
Meaning |
|---|---|
|
|
True when no error was reported. Always false if validation stopped early. |
|
|
The errors, plus warnings if requested, in document order. |
|
|
Validation stopped before the end of the document: at |
|
|
A pattern match ran past the schema's |
#SchemaDiagnostic
public sealed record SchemaDiagnostic(SchemaSeverity Severity, string Message,
string? SourceUri = null, int Line = 0, int Column = 0)
{
public string? Code { get; init; }
public string? MessageId { get; init; }
}
Compilation and validation report problems in the same shape. Severity is
SchemaSeverity.Warning or SchemaSeverity.Error. Line and Column count from 1, and are 0
when unknown. ToString() gives source(line,column): error: message.
-
Codeis the XSD validation rule the diagnostic reports, such ascvc-complex-type.2.4.aorcvc-pattern-valid. It is set only for a validation error whose message maps to exactly one rule. It isnullin these cases:-
for a message that covers several rules, such as a duplicate key and a duplicate unique value;
-
for every compilation diagnostic;
-
for diagnostics the layer reports itself (except
cvc-elt.1for an undeclared document element).
-
-
MessageIdis System.Xml's own name for the message (for exampleSch_UndeclaredElement). It is the same for every diagnostic of one kind, whatever names and values the text contains. It isnullwhen unknown, and for diagnostics the layer reports itself. The names belong to System.Xml, not to this library, and they may differ between .NET versions.
Both values come from which message System.Xml raised, never from the message wording. Both are
null on a runtime where System.Xml's private state can't be read.
SchemaCompilationException (derives from XmlDbException) is thrown for a schema that
cannot be loaded or doesn't compile. Its Diagnostics property lists every error and warning, in
order.
#XmlCatalog
An OASIS XML Catalogs 1.1 catalog, used through SchemaCompileOptions.Catalog. For example, it
lets a schema that refers to http://www.w3.org/2001/xml.xsd compile from a local copy.
var catalog = await XmlCatalog.LoadAsync([new Uri("file:///srv/schemas/catalog.xml")], gate);
var options = new SchemaCompileOptions { Catalog = catalog };
-
Supported entries: all of them. That is
uri,system,public,rewriteURI,rewriteSystem,uriSuffix,systemSuffix,delegateURI,delegateSystem,delegatePublic,groupandnextCatalog, withxml:baseandpreferwhere they apply. Names are compared after the specification's normalization, and aurn:publicid:name is read as the public identifier it stands for. -
A catalog only renames. Whatever it maps a name to is still read from a source or through the gate.
-
Loading. The catalog files, and every
nextCatalogand delegated catalog, are read at load time through the same gate and sources. If one can't be read, the load fails. A loaded catalog never changes and reads nothing more. A document type declaration in a catalog file is skipped. -
Limits. At most
XmlCatalog.MaxCatalogFiles(64) files.
Members:
-
LoadAsync(catalogs, gate, sources = null, limits = null, cancellationToken = default)and the blockingLoadload a catalog. -
ResolveUri(string),ResolvePublic(string)andResolveExternalIdentifier(string?, string?)look names up. -
IdentityandDocumentsdescribe what was loaded.XmlCatalog.Emptyis a catalog with no entries.
#XSD 1.1
The schema processor is System.Xml's, which implements XSD 1.0. With
SchemaCompileOptions.Xsd11Compatibility (on by default):
-
Elements marked as 1.1-only with
vc:minVersion/vc:maxVersionare removed, as XSD 1.1 §4.2.2 says a 1.0 processor should do. An include that is marked 1.1-only is not fetched. -
References to
xs:dayTimeDuration,xs:yearMonthDurationandxs:dateTimeStampare read as the 1.0 types they restrict. The 1.1 type's own constraint is not applied: anxs:dayTimeDurationofP1Yis accepted. -
A rewritten document is serialized again. Line and column numbers in later diagnostics about it can therefore differ from the original.
Some schemas require XSD 1.1:
-
an
xs:schemawithvc:minVersion="1.1"; -
a schema that uses
xs:assert,xs:assertion,xs:alternative,xs:openContent,xs:defaultOpenContentorxs:overrideoutside avc:guard.
Such a schema fails with a message that names the document and the constructs:
The schema document 'urn:example:v11.xsd' requires XSD 1.1: it uses xs:assert (1) with no
vc:minVersion guard. This processor implements XSD 1.0 and reads only the parts of a schema
that are marked for it.
#Stricter than System.Xml
These defaults are stricter than a bare XmlSchemaSet or validating XmlReader. They apply to
the Core layer and so to both engines. In XQuery and XSLT, a load failure is XQST0059.
-
An unreadable include or import fails the compilation. System.Xml reports it as a warning and compiles what is left. For example, an unused
xs:importwould compile silently without its document. The message isThe schema document 'common.xsd' is not available: no source supplies it and the access gate does not admit it. -
A schema document may not have a document type declaration.
-
A schema document may not nest elements deeper than 512 levels.
-
Schema text with no base URI doesn't resolve relative locations against the current directory. Give a base URI (
SchemaSourcealways has one, and so doesXsdSchemaProvider.AddFromString(namespace, text, baseUri)). -
The document element must be declared (
RequireDeclaredRoot). Without this check, a document validated against a schema for another vocabulary would be reported valid. -
The instance's own schema hints are not followed.
xsi:schemaLocation,xsi:noNamespaceSchemaLocationand inline schemas are ignored. The instance may not have a document type declaration. -
A cached schema goes to a request only after that request's own gate has admitted every document in it. A gate object the cache hasn't seen is always asked first. After that, its answer holds for one
CheckInterval. If one gate object serves many requests and a withdrawn permission must take effect at once, setCheckIntervaltoTimeSpan.Zero, or use a separate gate object for each request.
#Known limits
-
A
CompiledSchemais not opaque. ItsXmlSchemaSetexposes every declaration, andDocumentslists each document's URI. If the code you run must not see those, don't give it the object. -
XSD 1.0 only. XSD 1.1 is limited to what XSD 1.1 describes. A 1.1 type's own constraint is not checked.
-
Pattern limits depend on System.Xml internals.
PatternMatchTimeoutworks by replacing System.Xml's compiled pattern expressions, which are private. On a runtime where they can't be reached, a schema that declares a pattern doesn't compile with a limit set, rather than running unbounded.XsdSchemaProvider.PatternMatchTimeoutthrowsNotSupportedExceptionin the same case. -
Without a pattern limit, matches are unbounded. A pattern that backtracks catastrophically takes as long as the value makes it.
-
XSLT validation in 2.8.0 (XSLT engine issues #353, #354). An element built with
xsl:validation="strict"orxsl:element validation="strict"is validated only when it is written to the principal result. In a variable it is not validated and has no type annotation, soinstance of schema-element(...)andelement(*, T)tests on it are false. A validation failure is reported with the codeXQDY0027, not the XSLT codesXTTE1510toXTTE1555. -
No network gate is included. The layer ships
SuppliedOnlyandLocalFilesonly. -
The
LocalFilesroots must be writable only by the operator. Anyone who can write inside a root can choose what a schema is compiled from.
See also: Resource Policy · XSLT API · Queries API