Migrations - phax/ph-schematron GitHub Wiki
This page lists known traps and pit falls when migrating from one version to another. It is highly recommended to only perform a step of one major version, and not skip a major version.
v10.1 is a security hardening release. There are no renames and no moved classes - everything that breaks does so because a resource that used to be fetched is now refused. If all your Schematron files, their includes and the documents they reference live on the class path or in the file system, nothing changes for you.
| Area | Change | Breaking? | What to do |
|---|---|---|---|
Remote document() / doc() / unparsed-text() |
Denied in all engines (the pure engines had no restriction at all before) | Yes, if used | Allow the scheme explicitly, see below |
xsl:import / xsl:include from a remote URL |
Denied (previously fetched by Saxon despite the log warning) | Yes, if used | Copy the module locally, or allow the scheme |
xsl:result-document |
Rejected at compile time (XTSE0010) in a custom Schematron / XSLT |
Yes, if used | See below |
system-property('x') (no namespace) |
Returns "" instead of the JVM system property |
Behavioural | Pass the value as a Schematron parameter instead |
environment-variable() |
Returns the empty sequence | Behavioural | Pass the value as a Schematron parameter instead |
Custom URIResolver |
No longer consulted for a remote href | Yes, if used | Allow the scheme on the resolver you pass in |
XPathConfigBuilder.DEFAULT_PROCESSOR |
Now a secured Processor, shared with the pure XSLT engine |
Behavioural | Pass your own Processor to opt out |
SchematronPureXsltCache |
Cache key no longer contains the Processor; a non-default Processor bypasses the cache |
Behavioural | Nothing - the cache now actually hits. Set forceCacheResult (true) to cache with your own Processor
|
| Extension functions | Registered ones still work (Saxon trusts them) | No | - |
| ph-commons |
12.4.0 → 12.5.0
|
Maven only | - |
For the XSLT based engines (SchematronResourceXSLT, SchematronResourceSCH, SchematronResourceSchXslt_XSLT2, SchematronResourceSchXslt2) the block sits in the ph-commons URIResolver, so hand in one that permits the scheme:
final SchematronResourceSCH aSCH = SchematronResourceSCH.builder (aRes)
.uriResolver (new DefaultTransformURIResolver ().setAllowedRemoteSchemes ("http",
"https"))
.build ();For the pure engines (SchematronResourcePureXPath, SchematronResourcePureXslt) the block sits in the Saxon Processor, so set the allowed schemes before the first validation:
SchematronProcessorFactory.setAllowedRemoteSchemes ("http", "https");Both are global-ish switches on purpose: the Processor objects and the compiled artefacts derived from them are cached, so a later change has no effect on what was already created.
Disabling external functions is what secure processing means for Saxon; it also disables xsl:result-document, the no-namespace system-property() and environment-variable(). To get the old behaviour back:
// XSLT based engines
SchematronTransformerFactory.setTransformerFactoryCustomizer (aTF -> aTF.setAttribute (FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS,
Boolean.TRUE));
// pure engines
SchematronProcessorFactory.setAllowExternalFunctions (true);Prefer passing data in as a Schematron parameter over re-enabling this.
v10 is a major restructuring release. Most changes are backward compatible (old names kept as deprecated aliases, old configuration strings still accepted), but there are a handful of genuinely breaking changes — concentrated in the SVRL JAXB model, the PS data model, and the ESchematronEngine enum constants. Read the "At a glance" table first, then the section relevant to how you use the library.
Nothing about the validation results changes for a typical consumer that only calls getSchematronValidity(...) / applySchematronValidationToSVRL(...) on a pre-built SchematronResource*. The breaking items only bite if you (a) parse the SVRL JAXB model yourself, (b) walk/edit the PSSchema model, (c) reference ESchematronMode / ESchematronEngine.PURE in Java, or (d) depend on the moved Maven artifacts / renamed packages.
| Area | Change | Breaking? | What to do |
|---|---|---|---|
| Modules | New ph-schematron-isosch, ph-schematron-model, ph-schematron-pure-xslt
|
Maven only | Add the new dependency if you used the moved classes |
| Packages | Model packages drop the .pure. segment |
Source | Update imports (sed snippet below) |
| Engine enum |
ESchematronMode deprecated; ESchematronEngine.PURE renamed to PURE_XPATH
|
Source (Java) | Use ESchematronEngine; rename PURE→PURE_XPATH
|
| Entry point |
SchematronResourcePure → SchematronResourcePureXPath
|
Optional | Old name works (deprecated); adopt when convenient |
| New engine |
SchematronResourcePureXslt (pure-Java XSLT 3.0) |
Additive | Opt-in |
| Builders | New fluent builder() / buildCached() / buildUncached() API |
Additive | Old from*(...) factories + some setters deprecated |
| Customizers / caches |
TransformerCustomizerSCH & siblings, EStep* enums and the static SchematronResource*Cache classes deleted
|
Yes | See Removed customizer and static cache classes below |
| SVRL model | JAXB iterator renamed; getFlag() → List; typed content getters; elements removed |
Yes | See SVRL below |
| PS model |
getAllPatterns() no longer returns <group>s; PSPattern/PSGroup are siblings |
Yes | See PS model below |
ESchematronVersion.LATEST |
Now resolves to SCHEMATRON_2025 (was 2020) |
Behavioural | Pin an explicit version if you relied on 2020 |
| Telemetry | Optional OpenTelemetry spans/metrics | Additive | Opt-in |
| Maven plugin / Ant |
validate goal now supports SchXslt & pure-xslt
|
Additive | Config strings unchanged |
-
ph-commons
12.2.6→12.3.2. -
Saxon-HE stays at
12.9. - Minimum Java stays at 17 (unchanged since v9.0.0).
-
New transitive dependency
ph-telemetry(com.helger.telemetry:ph-telemetry, currently1.0.1) is pulled in by the pure engines. It is a no-op unless you register a telemetry SPI — see Telemetry.
ph-schematron-testfiles
ph-schematron-api
ph-schematron-xslt
ph-schematron-isosch (new)
ph-schematron-schxslt
ph-schematron-schxslt2
ph-schematron-model (new)
ph-schematron-pure-xpath (folder renamed; artifactId stays ph-schematron-pure)
ph-schematron-pure-xslt (new)
ph-schematron-validator
ph-schematron-maven-plugin
ph-schematron-ant-task
ph-schematron-benchmarks (new, build-internal JMH benchmarks — not a consumer dependency)
ph-schematron-it (new, build-internal integration tests — not a consumer dependency)
The "SCH → XSLT preprocessing" half of ph-schematron-xslt (the ISO Schematron stylesheet chain iso_dsdl_include.xsl → iso_abstract_expand.xsl → iso_svrl_for_xslt2.xsl) was extracted into a new module ph-schematron-isosch.
ph-schematron-xslt keeps only the "apply a pre-built XSLT to an XML instance" side.
| What | v9.2 location | v10 location |
|---|---|---|
com.helger.schematron.sch.SchematronResourceSCH |
ph-schematron-xslt |
ph-schematron-isosch |
com.helger.schematron.sch.SchematronProviderXSLTFromSCH |
ph-schematron-xslt |
ph-schematron-isosch |
com.helger.schematron.sch.SchematronResourceSCHCache |
ph-schematron-xslt |
deleted — use com.helger.schematron.sch.SchematronSCHCache (ph-schematron-isosch) |
com.helger.schematron.sch.TransformerCustomizerSCH |
ph-schematron-xslt |
deleted — use com.helger.schematron.sch.SchematronSCHConfig (ph-schematron-isosch) |
com.helger.schematron.sch.EStepSCH |
ph-schematron-xslt |
deleted — engine-internal, no public replacement |
external/schematron/20100710-xslt2/*.xsl |
ph-schematron-xslt |
ph-schematron-isosch |
com.helger.schematron.xslt.SchematronResourceXSLT |
ph-schematron-xslt |
ph-schematron-xslt (unchanged) |
com.helger.schematron.xslt.SchematronProviderXSLTPrebuild |
ph-schematron-xslt |
ph-schematron-xslt (unchanged) |
com.helger.schematron.xslt.SchematronResourceXSLTCache |
ph-schematron-xslt |
deleted — use com.helger.schematron.xslt.SchematronXSLTCache (ph-schematron-xslt) |
For everything that survived, the package names are not changed — only the Maven artifact moves.
The four deleted rows are the exception; see Removed customizer and static cache classes for the replacement code.
If your code uses SchematronResourceSCH, add the new dependency:
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-isosch</artifactId>
<version>10.0.0</version>
</dependency>If you only use SchematronResourceXSLT (apply a pre-built XSLT), you continue depending on ph-schematron-xslt and need nothing new.
The engine-agnostic data model, SCH XML reader/writer, error handler types and the preprocessor were extracted from ph-schematron-pure into a new module ph-schematron-model. The bound-schema, binding, XPath and validation layers stay in ph-schematron-pure. The packages were also renamed to drop the .pure. segment (it no longer reflects the artifact they live in).
| v9.2 package | v10 package | New artifact |
|---|---|---|
com.helger.schematron.pure.model |
com.helger.schematron.model |
ph-schematron-model |
com.helger.schematron.pure.exchange |
com.helger.schematron.exchange |
ph-schematron-model |
com.helger.schematron.pure.errorhandler |
com.helger.schematron.errorhandler |
ph-schematron-model |
com.helger.schematron.pure.preprocess |
com.helger.schematron.preprocess |
ph-schematron-model |
To enable the preprocessor to live in ph-schematron-model without dragging the binding/bound layers along, the string-level transformation methods of IPSQueryBinding (getNegatedTestExpression, getStringReplacementMap, getWithParamTextsReplaced) were promoted to a new engine-agnostic super-interface com.helger.schematron.model.IPSQueryBindingTransform. IPSQueryBinding in ph-schematron-pure now extends IPSQueryBindingTransform. PSPreprocessor's constructor parameter, field type and getQueryBinding() return type are now IPSQueryBindingTransform. Existing callers passing an IPSQueryBinding instance keep working unchanged (Liskov substitution); only callers that explicitly typed a variable as the result of getQueryBinding() may need to widen back via cast or update the variable type.
If your code references any of the renamed packages, update the imports. A single sed pass works for typical code bases:
find . -name '*.java' -type f -print0 | xargs -0 sed -i.bak \
-e 's|com\.helger\.schematron\.pure\.model\.|com.helger.schematron.model.|g' \
-e 's|com\.helger\.schematron\.pure\.exchange\.|com.helger.schematron.exchange.|g' \
-e 's|com\.helger\.schematron\.pure\.errorhandler\.|com.helger.schematron.errorhandler.|g' \
-e 's|com\.helger\.schematron\.pure\.preprocess\.|com.helger.schematron.preprocess.|g'Consumers that depend on ph-schematron-pure get the new module transitively and do not need a new explicit Maven dependency. Consumers that only need to read, write, preprocess or hold the Schematron data model without validating may depend on ph-schematron-model directly — it is much smaller than ph-schematron-pure (no Saxon, no binding, no XPath evaluation).
The two pure-Java engines are now consistently named pure-xpath (the XPath-only engine, previously just "pure") and pure-xslt (the new in-v10 Saxon-native engine that emits XSLT 3.0 in Java and runs it through Saxon s9api).
| v9.2 / pre-rename | v10.0 canonical |
|---|---|
Class com.helger.schematron.pure.SchematronResourcePure
|
Kept, now @Deprecated(since="10.0.0", forRemoval=false). New canonical name com.helger.schematron.pure.SchematronResourcePureXPath — a thin source-compatible subclass with covariant factory return types. Existing code keeps compiling; new code should use the new name. |
Module folder ph-schematron-pure
|
Renamed on disk to ph-schematron-pure-xpath/. The artifactId stays ph-schematron-pure for source compatibility — your <dependency> is unchanged. |
Class com.helger.schematron.puresaxon.SchematronResourceSaxon (interim v10 name) |
Renamed to com.helger.schematron.purexslt.SchematronResourcePureXslt (note the casing: PureXslt, not PureXSLT). Module folder ph-schematron-pure-xslt/, artifactId ph-schematron-pure-xslt. No alias kept — the module was new in v10, no v9 consumers exist. |
SchematronResourcePure → SchematronResourcePureXPath is optional: the old name continues to work, just emits a deprecation warning at the type declaration site. Adopt the new name when convenient.
A brand-new pure-Java engine that generates an XSLT stylesheet from the parsed PSSchema (no external ISO Schematron stylesheet chain) and runs it through Saxon s9api. Entry point com.helger.schematron.purexslt.SchematronResourcePureXslt accepts a URIResolver, ErrorListener and a configurable XSLT version (EPureXsltVersion — XSLT 1.0 / 2.0 / 3.0, default 3.0; the generated stylesheet's SVRL @location computation adapts to stay conformant to the selected version), and supports <xsl:*> pass-through (xsl:function, xsl:key, xsl:include, …) from foreign children of <sch:schema> as well as <xsl:choose>-bodied <sch:let>.
It also ships com.helger.schematron.purexslt.xslt.SchematronToXsltConverter — a stand-alone SCH → XSLT tool that emits to a W3C DOM Document / String / OutputStream / Writer / File. To avoid clashing with the ISO-XSLT engine's similarly named types, this engine's classes use the PureXslt* prefix (PureXsltStylesheetGenerator, PureXsltQueryBindingTransform, PureXsltTelemetry, EPureXsltVersion).
<dependency>
<groupId>com.helger.schematron</groupId>
<artifactId>ph-schematron-pure-xslt</artifactId>
<version>10.0.0</version>
</dependency>ESchematronMode (in ph-schematron-api, package com.helger.schematron) is now @Deprecated(since="10.0.0", forRemoval=true). It has been merged into ESchematronEngine, which is the single authoritative engine selector. ESchematronMode still exists and still works; every value carries a bridge method toEngine(), and every string id it recognised is still accepted by ESchematronEngine.getFromIDOrNull(String).
Two changes are source-breaking for Java code that names enum constants:
-
ESchematronEngine.PUREwas renamed toESchematronEngine.PURE_XPATH. A deprecated compatibility fieldpublic static final ESchematronEngine PURE = PURE_XPATH;is kept, soESchematronEngine.PUREstill compiles — but you should migrate toPURE_XPATH. -
New constant
ESchematronEngine.XSLT_PREBUILT(the formerESchematronMode.XSLT, id"xslt"), plus the newPURE_XSLTengine.
ESchematronEngine constants and accepted string ids (all pre-v10 ids still resolve):
| Constant | Canonical id | Accepted aliases |
|---|---|---|
PURE_XPATH |
pure-xpath |
pure |
PURE_XSLT |
pure-xslt |
pure-saxon |
ISO_SCHEMATRON |
iso-schematron |
iso, isoschematron, schematron, sch
|
SCHXSLT1 |
schxslt |
schxslt1, schxslt-xslt2
|
SCHXSLT2 |
schxslt2 |
— |
XSLT_PREBUILT |
xslt |
— |
ESchematronMode → ESchematronEngine value mapping:
ESchematronMode (v9) |
ESchematronEngine (v10) |
|---|---|
PURE / PURE_XPATH
|
PURE_XPATH |
PURE_XSLT |
PURE_XSLT |
SCHEMATRON |
ISO_SCHEMATRON |
SCHXSLT_XSLT2 |
SCHXSLT1 |
XSLT |
XSLT_PREBUILT |
Developer action: replace ESchematronMode with ESchematronEngine (or call .toEngine()); rename the constants PURE→PURE_XPATH and mode-XSLT→XSLT_PREBUILT. String-based configuration (e.g. schematronProcessingEngine="schematron") needs no change.
Every SchematronResource* class gained a nested Builder plus static factory methods. This is additive — existing single-argument constructors (e.g. new SchematronResourceSCH(IReadableResource)) are not deprecated.
New per-class API (illustrated with SchematronResourceSCH; the same shape exists on SchematronResourcePureXPath, SchematronResourcePureXslt, SchematronResourceSchXslt_XSLT2, SchematronResourceSchXslt2, SchematronResourceXSLT):
-
Static factories returning a
Builder:builder(IReadableResource),builderFromClassPath(...),builderFromFile(...),builderFromURL(...),builderFromInputStream(...),builderFromByteArray(...),builderFromString(...). -
Fluent setters on
Builder:useCache,lenient,entityResolver,phase,languageCode,errorListener,uriResolver,parameter/parameters,forceCacheResult,transformerFactoryCustomizer,telemetry(...),perAssertionResultTelemetry,perRuleExecutionTelemetry,outputValidityDeterminator,validateSVRL,cache. -
Terminal methods:
-
build()— lazy compile (existing semantics). -
buildCached()/buildCached(cache)— eagerly compile through the cache. -
buildUncached()— eagerly compile once, bypassing the cache (fail-fast).
-
-
One-shot convenience returning the resource:
compileCached(...),compileUncached(...).
final SchematronResourceSCH aSCH = SchematronResourceSCH.builderFromFile ("rules.sch")
.phase ("full")
.languageCode ("en")
// compiles immediately, throws on error
.buildUncached ();The following are now @Deprecated(since="10.0.0", forRemoval=false) in favour of the builder (they keep working):
- The old static factories
fromClassPath(...),fromFile(...),fromURL(...),fromInputStream(...),fromByteArray(...),fromString(...)→ usebuilderFrom*(...). - The mutating setters
setPhase(String),setLanguageCode(String),setForceCacheResult(boolean)→ configure via theBuilder.
A generic compilation-cache abstraction was published in ph-schematron-api, package com.helger.schematron.api.cache: ISchematronCompilationCacheKey (marker), ISchematronCompilation<ARTIFACT> (a single compilation step: getResource(), getCacheKey(), canCacheResult(), compile()), and AbstractSchematronCache<CFG, ARTIFACT>.
Each engine now exposes a value/compilation class via resource.toConfig() (SchematronSCHConfig, SchematronPureXPathConfig, SchematronPureXsltConfig, SchematronSchXslt_XSLT2Config, SchematronSchXslt2Config, SchematronXSLTConfig).
Ordinary callers are unaffected; this only matters if you implement a custom engine cache or drive compilation manually.
There is no new public "validator" interface to implement (an ISchematronValidator family was prototyped mid-cycle but removed before release).
The v9 classes that this SPI replaces were deleted, not deprecated — see the next section.
The TransformerCustomizer* mutable value holders, the EStep* enums and the static SchematronResource*Cache utility classes no longer exist in v10.0.0. They are not deprecated aliases — the classes are gone, so code referencing them fails to compile.
Their responsibilities were split into the two new per-engine types:
-
Schematron*Config— the immutable "what to compile and how" value object (phase, language code, error listener, URI resolver, XSLT parameters, cache-forcing, telemetry, transformer-factory customizer). This replacesTransformerCustomizer*. -
Schematron*Cache— the instance-based artifact cache. This replaces the staticSchematronResource*Cache.
| v9.2 class (gone in v10) | v10 replacement |
|---|---|
com.helger.schematron.sch.TransformerCustomizerSCH |
com.helger.schematron.sch.SchematronSCHConfig |
com.helger.schematron.sch.SchematronResourceSCHCache |
com.helger.schematron.sch.SchematronSCHCache |
com.helger.schematron.sch.EStepSCH |
— engine-internal now, no public replacement |
com.helger.schematron.schxslt.xslt2.TransformerCustomizerSchXslt_XSLT2 |
com.helger.schematron.schxslt.xslt2.SchematronSchXslt_XSLT2Config |
com.helger.schematron.schxslt.xslt2.SchematronResourceSchXslt_XSLT2Cache |
com.helger.schematron.schxslt.xslt2.SchematronSchXslt_XSLT2Cache |
com.helger.schematron.schxslt.xslt2.EStepSchXslt_XSLT2 |
— engine-internal now, no public replacement |
com.helger.schematron.schxslt2.xslt.TransformerCustomizerSchXslt2 |
com.helger.schematron.schxslt2.xslt.SchematronSchXslt2Config |
com.helger.schematron.schxslt2.xslt.SchematronResourceSchXslt2Cache |
com.helger.schematron.schxslt2.xslt.SchematronSchXslt2Cache |
com.helger.schematron.xslt.SchematronResourceXSLTCache |
com.helger.schematron.xslt.SchematronXSLTCache (+ SchematronXSLTConfig) |
Every setter has a 1:1 counterpart on SchematronSCHConfig.Builder — and the identical method names also exist on SchematronResourceSCH.Builder, so most code never has to name the config class at all.
TransformerCustomizerSCH (v9.2) |
SchematronSCHConfig.Builder / SchematronResourceSCH.Builder (v10) |
|---|---|
new TransformerCustomizerSCH () |
SchematronSCHConfig.builder (aRes) — or SchematronResourceSCH.builder (aRes)
|
setErrorListener (ErrorListener) |
errorListener (ErrorListener) |
setURIResolver (URIResolver) |
uriResolver (URIResolver) |
setParameters (Map <String, ?>) |
parameters (Map <String, ?>) — or parameter (String, Object) one at a time |
setPhase (String) |
phase (String) |
setLanguageCode (String) |
languageCode (String) |
setForceCacheResult (boolean) |
forceCacheResult (boolean) |
overriding customize (TransformerFactory)
|
transformerFactoryCustomizer (Consumer <TransformerFactory>) |
overriding customize (EStepSCH, Transformer)
|
— no replacement; the engine applies the config itself |
The read side (getPhase(), getLanguageCode(), getErrorListener(), getURIResolver(), getParameters(), hasParameters(), isForceCacheResult(), canCacheResult()) exists under the same names on the immutable SchematronSCHConfig.
Case 1 — you only configured the resource. Nothing to migrate beyond the method names:
// v9.2
final SchematronResourceSCH aSCH = SchematronResourceSCH.fromFile ("rules.sch");
aSCH.setPhase ("full");
aSCH.setLanguageCode ("en");
aSCH.setForceCacheResult (true);
// v10
final SchematronResourceSCH aSCH = SchematronResourceSCH.builderFromFile ("rules.sch")
.phase ("full")
.languageCode ("en")
.forceCacheResult (true)
.build ();Case 2 — you subclassed to register Saxon extension functions. In v9 this meant overriding the @OverrideOnDemand hook SchematronResourceSCH.createTransformerCustomizer(), returning a TransformerCustomizerSCH subclass that overrode customize (TransformerFactory) — and then manually re-applying all default values. Both the hook and the helper applyDefaultValuesOnTransformerCustomizer(...) are gone. This is now a first-class, subclass-free builder option:
// v9.2 — see the old ph-schematron issue #129 example
final SchematronResourceSCH aSCH = new SchematronResourceSCH (new FileSystemResource (aSchematron))
{
@Override
@NonNull
@OverrideOnDemand
protected TransformerCustomizerSCH createTransformerCustomizer ()
{
final TransformerCustomizerSCH aCustomizer = new TransformerCustomizerSCH ()
{
@Override
public void customize (@NonNull final TransformerFactory aTransformerFactory)
{
super.customize (aTransformerFactory);
if (aTransformerFactory instanceof TransformerFactoryImpl)
{
final TransformerFactoryImpl aSaxonTF = (TransformerFactoryImpl) aTransformerFactory;
final Configuration aSaxonConfig = aSaxonTF.getConfiguration ();
final Processor aProcessor = (Processor) aSaxonConfig.getProcessor ();
aProcessor.registerExtensionFunction (new EF_Test ());
}
}
};
return aCustomizer.setErrorListener (getErrorListener ())
.setURIResolver (getURIResolver ())
.setParameters (parameters ())
.setPhase (getPhase ())
.setLanguageCode (getLanguageCode ())
.setForceCacheResult (isForceCacheResult ());
}
};// v10 — no subclass, no re-applying of defaults
final SchematronResourceSCH aSCH = SchematronResourceSCH.builder (new FileSystemResource (aSchematron))
.transformerFactoryCustomizer (aTransformerFactory -> {
if (aTransformerFactory instanceof final TransformerFactoryImpl aSaxonTF)
{
final Configuration aSaxonConfig = aSaxonTF.getConfiguration ();
final Processor aProcessor = (Processor) aSaxonConfig.getProcessor ();
aProcessor.registerExtensionFunction (new EF_Test ());
}
})
.build ();Notes on transformerFactoryCustomizer (...):
- It receives the
TransformerFactoryof the final compile step (the one that compiles the validation stylesheet), after the error listener and URI resolver were applied and immediately before compilation — the same point at whichcustomize (TransformerFactory)used to be called. - Because the cache key cannot capture the identity of an arbitrary lambda, setting a customizer makes
canCacheResult()returnfalse, i.e. it disables caching — unless you also setforceCacheResult (true). - The same option exists on
SchematronResourceXSLT,SchematronResourceSchXslt_XSLT2,SchematronResourceSchXslt2and on all fourSchematron*Config.Builderclasses.
Case 3 — you called the static cache directly.
| v9.2 | v10 |
|---|---|
SchematronResourceSCHCache.getSchematronXSLTProvider (aRes, aTC) |
SchematronSCHCache.shared ().getOrCompile (aConfig) |
SchematronResourceSCHCache.createSchematronXSLTProvider (aRes, aTC) |
aConfig.compile () |
SchematronResourceSCHCache.clearCache () |
SchematronSCHCache.shared ().clear () |
// v10
final SchematronSCHConfig aConfig = SchematronSCHConfig.fromFile ("rules.sch")
.phase ("full")
.build ();
// Cached; automatically falls back to a plain compile when aConfig.canCacheResult () is false
final ISchematronXSLTBasedProvider aProvider = SchematronSCHCache.shared ().getOrCompile (aConfig);Besides shared (), SchematronSCHCache can be instantiated per use case — optionally with a name and a maximum size (new SchematronSCHCache ("my-cache", 100)) — and handed to a resource via SchematronResourceSCH.builder (...).cache (aMyCache). It also offers isCached(cfg), invalidate(cfg), size() and clear(). The static v9 cache offered none of this.
The SchematronProviderXSLTFromSCH constructor is no longer public either — it now takes a SchematronSCHConfig and is protected. Go through SchematronSCHConfig.compile () (uncached) or SchematronSCHCache.getOrCompile (...) (cached) instead.
The same mechanical rename applies to SchXslt 1.x, SchXslt 2.x and the pre-built XSLT engine.
The builder method names (errorListener, uriResolver, parameter / parameters, phase, languageCode, forceCacheResult, telemetry, transformerFactoryCustomizer) are identical across SchematronSCHConfig, SchematronSchXslt_XSLT2Config and SchematronSchXslt2Config.
SchematronXSLTConfig (pre-built XSLT) has no phase / languageCode / forceCacheResult, because there is no SCH preprocessing step to parameterize.
This is the area most likely to break a consumer that inspects validation output. Two independent things changed: the emitted SVRL XML was made conformant with the official SVRL / SVRL 2025, and the generated JAXB model changed shape accordingly.
The SVRL namespace is unchanged (http://purl.oclc.org/dsdl/svrl). Output differences:
-
<svrl:text>is now emitted last insidefailed-assert/successful-report(afterdiagnostic-reference/property-reference), instead of first. Do not rely ontextbeing the first child. -
Rich attributes (
see,icon,fpi,xml:lang,xml:space) moved off the message element and into the<svrl:text>element forfailed-assert,successful-reportanddiagnostic-reference. -
fired-ruleno longer carries rich attributes — onlycontext,id,roleand properties remain. -
diagnostic-referencecontent is now wrapped in<svrl:text>rather than being applied directly. -
active-pattern/@documentis gone; only@documentsis emitted (they were emitted in parallel since v8.0.4; the singular form is now dropped). -
An empty
flagattribute is no longer written —flagappears only when non-empty. -
schematron-output/@titleand@schemaVersionare still emitted by the XSLT engine.
If you have code (or XSLT/XPath, or golden-file tests) that reads SVRL by attribute position or expects rich attributes on the assert/report element, update it.
-
flag:xs:NMTOKEN→ list ofxs:token(drives the JAXBgetFlag()type change below). - New
severityattribute (xs:string) on assert/report. -
role:xs:NMTOKEN→xs:string;phase:xs:NMTOKEN→xs:string(loosened; Java type staysString). - New
active-groupelement (alongsideactive-pattern). -
failed-assert/successful-reportcontent model changed from a mixed<xs:choice>to a typed<xs:sequence>(diagnostic-reference*,property-reference*,text). -
Removed elements:
message-code,message-category,suppressed-rule(these SchXslt extension elements added in v8.0.6 are gone from the model; unknown foreign-namespace content is still accepted viaxs:anyonschematron-output).
The package and target namespace are unchanged, but generated method names changed. If you walk or build the SVRL model directly:
| v9 | v10 |
|---|---|
SchematronOutputType.getActivePatternAndFiredRuleAndFailedAssert() |
getActivePatternOrActiveGroupAndFiredRule() (and matching addActivePatternOrActiveGroupAndFiredRule(...)) |
FailedAssert.getFlag() / SuccessfulReport.getFlag() → String
|
List<String> (new helpers hasFlagEntries(), getFlagAtIndex(int), addFlag(...)) |
| — | new getSeverity() on FailedAssert / SuccessfulReport
|
getDiagnosticReferenceOrPropertyReferenceOrText() → List<Object>
|
split into typed getDiagnosticReference() (List<DiagnosticReference>), getPropertyReference() (List<PropertyReference>), getText() (single Text) |
ActivePattern only |
new concrete classes ActivePattern and ActiveGroup
|
- Public method signatures on
SVRLHelper,SVRLFailedAssert,SVRLSuccessfulReportare unchanged (only their bodies were updated to use the new typed getters). - Six
SVRLHelpermethods are now@Deprecated(since="10.0.0", forRemoval=true)because the JAXB model now exposes typed getters directly:getAllDiagnosticReferences(FailedAssert),getAllPropertyReferences(FailedAssert),getText(FailedAssert)and the threeSuccessfulReportequivalents. Migrate to the model getters (aFA.getDiagnosticReference(),aFA.getPropertyReference(),aFA.getText()). -
ISVRLErrorLevelDeterminatorprecedence changed. The default resolution order for the error level of a failed-assert / successful-report is nowseverity→ firstflagentry →role(wasflag→role). If you supply a customISVRLErrorLevelDeterminator, mirror the new precedence and readflagfrom the list (e.g.hasFlagEntries() ? getFlagAtIndex(0) : null).
Beyond the package move (above), the PS data model gained ISO/IEC 19757-3 edition awareness (2006 / 2016 / 2020 / 2025) and grew new element types.
Most of this is additive, but three items are breaking or behavioural for code that walks or edits PSSchema.
2025 introduces the <group> container.
A new abstract base com.helger.schematron.model.AbstractPSPatternLike was extracted; PSPattern and PSGroup are now siblings, both extending it.
Consequences:
-
PSSchema.getAllPatterns()returns patterns only. Groups live in a separate collection reached via the newgetAllGroups()(withgetGroupCount(),getGroupOfID(...),addGroup(...),hasAnyGroup()). Code that iteratedgetAllPatterns()to enumerate all rule-bearing containers will now silently skip every<group>. -
instanceof PSPatternno longer matches a group (by design). If you mean "pattern or group", testinstanceof AbstractPSPatternLike. - A schema can now be valid with zero
<pattern>s if it has<group>s, sogetPatternCount() == 0no longer implies an empty/invalid schema.PSSchema.isValid()fails only when both patterns and groups are empty. - Familiar methods (
getAllRules(),addRule(),isAbstract(),getAllLets(), …) now live onAbstractPSPatternLikerather than being declared onPSPattern. Source compiles unchanged, but pre-compiled bytecode that referencedPSPattern.<method>must be recompiled (binary-incompatible).
ESchematronVersion (in ph-schematron-api, package com.helger.schematron) gained SCHEMATRON_2025, and **LATEST now resolves to SCHEMATRON_2025 instead of SCHEMATRON_2020.
** Any code reading ESchematronVersion.LATEST now gets 2025 — pin an explicit value if you depended on 2020.
New accessors: getEditionYear() ("2025", …), static getFromEditionYearOrNull(String), and isOlderThan(ESchematronVersion).
A new PSVersionChecker runs automatically at the end of every PSReader.readSchemaFromXML(...) and at the start of every PSWriter write (writeToFile/writeToStream/writeToWriter/getAsString).
It emits warnings only (never errors, never exceptions; return values unchanged) through the registered error handler when a schema uses a feature newer than its declared schematronEdition — or when no edition is declared at all.
With the default LoggingPSErrorHandler these appear as logged WARN messages.
- Impact: code that asserts "no error-handler callbacks / no warnings" during read or write may now trip.
Supply your own
IPSErrorHandler(PSReader) /PSWriter.setErrorHandler(...)to redirect or suppress. - Typical pre-2025 schemas that don't use new features stay silent.
Note
queryBindingis the one check that only warns when an explicit older edition is declared, so a commonqueryBinding="xslt2"schema with noschematronEditionproduces no warning.
The PSPattern/PSGroup validity checks were realigned to the 2025 (v4) RNC three-branch shape.
Net effect: two combinations that were hard errors in v9 are now allowed — an abstract pattern with is-a, and an abstract pattern with <param> (the latter flagged as a PSVersionChecker warning on pre-2025 schemas, not a hard error).
Nothing that previously validated now fails, and the same rules now apply to <group>.
- New classes in
com.helger.schematron.model:PSGroup,PSLibrary(a<library>root document; read viaPSReader.readLibrary()/readLibraryFromXML(...)),PSRules,PSProperties,PSProperty, and the baseAbstractPSPatternLike. -
PSSchema.getSchematronEdition()/setSchematronEdition(ESchematronVersion)carry the 2025schematronEditionattribute. - New attribute accessors (all additive):
PSLet.getAs()/setAs(...);PSPhase.getFrom()/getWhen();PSRule.getVisitEach(),getSeverity();PSAssertReport.getSeverity(),getAllProperties();role/documentson both pattern and group. -
flagis now a whitespace-separated token list.IPSHasFlagexposesgetAllFlags()/addFlag(...), andsetFlag(String)splits on whitespace. The oldgetFlag()still returns a single value (the first token), so code that must see all tokens should switch togetAllFlags().
Both SchematronResourcePureXPath and SchematronResourcePureXslt expose setTelemetry(boolean) and setPerAssertionTelemetry(boolean) (also configurable via the builder's telemetry(...) / perAssertionResultTelemetry / perRuleExecutionTelemetry).
When enabled, the engines emit OpenTelemetry-shaped spans (schematron.validate, schematron.parse, schematron.preprocess, schematron.generate, schematron.compile, schematron.execute, optional schematron.assertion), counters and a schematron.validate.duration histogram. This is zero runtime cost when no ITelemetryTracerSPI / ITelemetryMeterSPI is registered — ph-telemetry degrades to a silent no-op.
Nothing to do unless you want the instrumentation.
Public XML parameter names are unchanged (schematronProcessingEngine, forceCacheResult, phaseName, languageCode, …), and all pre-v10 engine string values still resolve (via ESchematronEngine aliases). The engine coverage changed:
-
validategoal (SchematronValidationMojo) now handles all six engines —pure-xpath,pure-xslt,iso-schematron,schxslt,schxslt2,xslt. In v9 the validation goal only handledpure,schematronandxslt; SchXslt (v1 and v2) andpure-xsltare newly usable here.phaseName,languageCodeandparametersapply. -
convertgoal (Schematron2XSLTMojo) additionally acceptspure-xslt(becausePURE_XSLTis XSLT-based). Supported convert engines:iso-schematron,schxslt,schxslt2,pure-xslt(pure-xpathis still rejected, as it produces no XSLT). -
Ant task
Schematronnow handles bothpure-xsltandschxslt2too. All six engine values (pure-xpath,pure-xslt,iso-schematron,schxslt,schxslt2,xslt) are supported, matching thevalidategoal.
v9.2 only affects ph-schematron-pure. All other modules (ph-schematron-api, ph-schematron-xslt, ph-schematron-schxslt, ph-schematron-schxslt2, ph-schematron-maven-plugin, ph-schematron-ant-task, ph-schematron-validator) are source-compatible.
ph-schematron-pure was migrated from the JAXP XPath API (javax.xml.xpath.*, internally limited to XPath 1.0) to the Saxon s9api (net.sf.saxon.s9api.*).
Schematron expressions are now compiled and evaluated as XPath 3.1 by default.
The user-facing entry points keep their DOM signatures:
-
SchematronResourcePure.applySchematronValidationToSVRL(org.w3c.dom.Node, String)— unchanged. -
SchematronResourcePure.getSchematronValidity(org.w3c.dom.Node, String)— unchanged. -
IPSValidationHandler— still receivesorg.w3c.dom.Node/NodeList. Existing implementations of this interface require no changes. -
IPSErrorHandler— unchanged.
If you only call into these entry points, you do not need any source change. You may want to review your Schematrons for XPath 1.0 quirks though — see "Behavioural change" below.
All JAXP-typed methods on XPathConfigBuilder were removed:
| Old (9.1) | New (9.2) |
|---|---|
setXPathFactory(javax.xml.xpath.XPathFactory) |
setProcessor(net.sf.saxon.s9api.Processor) |
setXPathFactoryClass(...) |
setProcessor(new Processor(false)) etc. |
setGlobalXPathFactory(String) |
(removed — no longer relevant) |
setXPathVariableResolver(javax.xml.xpath.XPathVariableResolver) |
addExternalVariable(QName, XdmValue) / addAllExternalVariables(Map)
|
setXPathFunctionResolver(javax.xml.xpath.XPathFunctionResolver) |
addExtensionFunction(net.sf.saxon.s9api.ExtensionFunction) / addAllExtensionFunctions(Iterable)
|
The XPath language version is now an enum, EXPathVersion, with values XPATH_1_0, XPATH_2_0, XPATH_3_0, XPATH_3_1 (default), XPATH_4_0. Set it via XPathConfigBuilder.setXPathVersion(EXPathVersion); it ends up on XPathCompiler.setLanguageVersion(String).
JAXP XPathFunction / MapBasedXPathFunctionResolver (from ph-commons) are no longer wired into the engine. Implement net.sf.saxon.s9api.ExtensionFunction instead. Arguments arrive as XdmValue[] (instead of List<Object> of DOM Nodes), and the result is an XdmValue.
Example (old, 9.1):
final MapBasedXPathFunctionResolver fns = new MapBasedXPathFunctionResolver ();
fns.addUniqueFunction ("http://example/ns", "my-count", 1, args -> {
final List<?> arg = (List<?>) args.get (0);
return Integer.valueOf (arg.size ());
});
final IXPathConfig cfg = new XPathConfigBuilder ().setXPathFunctionResolver (fns).build ();Example (new, 9.2):
final IXPathConfig cfg = new XPathConfigBuilder ()
.addExtensionFunction (new ExtensionFunction () {
public QName getName () { return new QName ("http://example/ns", "my-count"); }
public SequenceType [] getArgumentTypes () { return new SequenceType [] { SequenceType.ANY }; }
public SequenceType getResultType () { return ItemType.INTEGER.one (); }
public XdmValue call (final XdmValue [] a) { return new XdmAtomicValue (a[0].size ()); }
})
.build ();JAXP XPathVariableResolver is gone. Bind external variables on the builder:
final IXPathConfig cfg = new XPathConfigBuilder ()
.addExternalVariable (new QName ("my-var"), new XdmAtomicValue ("hello"))
.build ();loadXQuery(InputStream) now returns ICommonsList<ExtensionFunction> (was MapBasedXPathFunctionResolver). Feed the list straight into the builder:
final IXPathConfig cfg = new XPathConfigBuilder ()
.addAllExtensionFunctions (new XQueryAsXPathFunctionConverter ().loadXQuery (xqStream))
.build ();The wrapper around UserFunction now exposes the actual declared argument and result SequenceTypes, so Saxon performs automatic coercion (atomization, cardinality checks, type promotion) for free — this resolves the long-standing DOMNodeWrapper cannot be cast to AtomicValue problem when calling e.g. functx:are-distinct-values(...).
If you subclass or directly consume the bound-schema internals, note:
-
PSXPathBoundSchema,PSXPathBoundRule,PSXPathBoundAssertReport,PSXPathBoundElement,PSXPathVariables,IPSXPathVariablesnow holdnet.sf.saxon.s9api.XPathExecutableinstead ofjavax.xml.xpath.XPathExpression. -
XPathLetVariableResolverno longer implementsjavax.xml.xpath.XPathVariableResolver. It is a per-threadQName→XdmValuestore used by the bound schema. -
XPathEvaluationHelperis reshaped aroundXPathExecutable/XdmItem/XdmValue. The DOM-returning variants (evaluateAsNodeList, etc.) are gone; useevaluateAsXdmNodes(...)orevaluate(...). - A new
XPathEvaluationContext(thread-local) is published. The bound schema installs one pervalidate(...)call; the SVRL handler uses it.
Schematrons that target XPath 2.0 / 3.0 / 3.1 in their queryBinding attribute (the common case) work as written. The engine no longer silently accepts XPath-1.0 quirks though:
-
eq/ne/lt/le/gt/geare now value comparisons (previously syntax errors that were tolerated by some configurations). - Functions that don't exist in the selected XPath version fail at bind time with a clear compile error.
- String coercion of node sequences follows XPath 2.0+ rules.
If your Schematron uses a 1.0-only construct, set XPathConfigBuilder.setXPathVersion(EXPathVersion.XPATH_1_0) to keep the legacy semantics.
SchematronResourcePure.getAsNode(...) now parses the input XML straight into a Saxon TinyTree (via Processor.newDocumentBuilder()) and presents it as a DOM facade (net.sf.saxon.dom.NodeOverNodeInfo / DocumentOverNodeInfo). All overloads of applySchematronValidationToSVRL, getSchematronValidity and applySchematronValidation that take a Source or IHasInputStream benefit automatically. Caveats:
- If you have a custom XML
EntityResolverconfigured on the resource, the TinyTree path is bypassed and the previous DOM parsing route is used so the resolver still works. - The
org.w3c.dom.Nodereturned bygetAsNode(...)in the fast path is Saxon's read-only DOM facade. Code that mutates the returned DOM will hitNOT_SUPPORTED_ERR. Read-only consumers (XPath, traversal, serialisation) are unaffected. - Overloads that take a pre-built
org.w3c.dom.Nodekeep their existing behaviour (the engine wraps the DOM via Saxon'sDocumentWrapper).
<let> elements without a value attribute now have their body text picked up as a plain XPath expression — fixes #189. Plain XPath in the body works (e.g. <let name="cnt">count(item)</let>). XSLT-instruction bodies (<xsl:for-each>, <xsl:value-of>, …) still cannot be executed by the pure engine; they now produce a clear warning naming the offending child element instead of the cryptic <let> has no 'value' failure. If you need full XSLT-body support, use SchematronResourceSCH / SchematronResourceXSLT for now.
- The minimum Java version changed from Java 11 to Java 17
- Using Java 11 as the baseline
- Updated to ph-commons 11
- Using JAXB 4.0 as the baseline
- The Maven group ID changed from
com.helgertocom.helger.schematron - In this version the submodule structure was modified
-
ph-schematronwas split inph-schematron-api,ph-schematron-pureandph-schematron-xslt- you need to choose a different artifact - You need to pick either
ph-schematron-pureorph-schematron-xsltdepending on the engine you want to use - Alternatively you can use the new
ph-schematron-schxsltmodule - Note: the Maven Plugin and the ANT task support all engines
-
- Some classes moved to different packages (e.g.
SchematronResourceSCHwas moved fromcom.helger.schematron.xslttocom.helger.schematron.sch)