Sales, Andrew. “Beyond valid and invalid: implementing complex validation in XProc.” Presented at Balisage: The Markup Conference 2026, Washington, DC, August 3 - 7, 2026. In Proceedings of Balisage: The Markup Conference 2026. Balisage Series on Markup Technologies, vol. 31 (2026). https://doi.org/10.4242/BalisageVol31.Sales01.
Balisage: The Markup Conference 2026 August 3 - 7, 2026
Balisage Paper: Beyond valid and invalid: implementing complex validation in XProc
Andrew Sales
Andrew has led the Content Architecture team at Bloomsbury since 2016. He has worked
with and for publishers in the XML space since 2000. Since 2016 he has been Project
Editor for the Schematron international standard (ISO/IEC 19757-3).
We describe the (re-)implementation at an academic publisher of a piece of enterprise
middleware which acts as quality gatekeeper for our XML-first publishing workflow.
The approach covers re-factoring equivalent legacy software as a single XProc pipeline;
how we have tackled testing this pipeline; and how we use the post-processing of the
validation reports it produces to both refine and re-define our validity criteria,
based on the “size and shape” of the documents under scrutiny and the extent to which
they violate our business rules.
This paper is part technical implementation, part incomplete case study. As it is
now written, it covers the main benefits to us as maintainers of the implementation
and owners of the system, since it is only now being rolled out to its main consumers,
our supplier base. As such, any data or feedback arising in the interim cannot be
included here, but may be presented at a later date.
Background
Bloomsbury Publishing Group plc began as a trade publisher nearly forty years ago, adding an academic division some
time later. We publish around 3,500 titles per year on the academic list.
Our digital products include Bloomsbury Collections (a library of nearly 50,000 titles, in the main academic monographs), Drama Online (home of the Arden Shakespeare and content from partners such as Faber and Faber)
and the Churchill Archive, providing online access to the complete (800,000+) digitized papers of Sir Winston
Churchill.
We work for this part of the business almost exclusively, in the Content Architecture
(CA) team. Our team’s main responsibility is to define and maintain the content conversion
documentation and supporting validation assets used by our supplier base in preparing
the XML we use to publish this content online, and to apply quality assurance prior
to publication: in short, we “spec and check”.
The focus of the work this paper describes is the text-based content we commission,
or
acquire or license from content partners and other third parties. This comprises mostly
books, but also plays and some screenplays. In common with other publishers, our
business grows mainly by acquisition, which can have the effect that our backlist
suddenly grows in some cases by tens of thousands of titles, all eventually requiring
conversion to our standard markup formats, to acceptable levels of quality.
History
Bloomsbury Academic began digital publishing in around 2013, later becoming Bloomsbury
Digital Resources (BDR).
There was no in-house validation expertise at this time, but the need for a robust
set of business rules encapsulated in Schematron schemas was soon recognised.
Likewise, the realisation that an in-house quality assurance team was needed, since
not everything could be reliably or definitively validated by machine with the available
technology and knowhow. The "QA team" sits within Content Architecture.
A “validation portal” was also set up to carry out delivery package integrity checks,
and schema and Schematron validation on receipt from suppliers.
Validation portal 1.0
The first iteration of the validation portal was a bespoke web application
developed, hosted and supported by one of our typesetting vendors.
Figure 1: Simplified validation portal workflow
It provided in-built issue tracking for suppliers to file issues for investigation
by the CA team, and both documentation and schemas could be updated by us in a self-service
manner.
The non-schema validation portions were, however, opaque and not under our control:
we neither owned nor had visibility of the source code.
The gratis provision of this service meant that there was also a unreliable lead
time for any changes. Support was variable in general, with downtime, ad hoc changes
to infrastructure and functional regression all proving disruptive.
Reports resulting from schema validation were purely text-based (i.e., no SVRL[1] produced by Schematron validation) and a log of only the most recent 200 or so submissions
was retained, meaning supplier performance was hard to track over time and analyse
for areas for improvement in their work.
Changing business needs
The growth in our digital publishing throughput over the last ten years has meant
that the need for greater automated validation has only grown with it. It was feasible
to have a team operative quality-assure fifty new titles per month prior to release
on Bloomsbury Collections in 2016; with monthly releases lately exceeding one thousand
titles; however, we have no realistic commercial alternative at this scale but to
automate whatever we can.
This scaling up dovetails neatly with the team’s objective to skill up: we need to
hone our collective technical skills in identifying bad quality and spend less time
on
repetitious manual work (the more basic aspects of which we also have an objective
to
outsource).
On the road
Some team members were already enthusiasts for XProc and its conceptual promise, but
had ultimately been baffled by version 1.0. Available time and headspace dictated
pursuing other avenues with a shallower learning curve for pipeline processing, for
example using XQuery, Python or batch scripts to orchestrate a series of
operations.
In light of the foregoing issues, the maturity of XProc 3.0 [XProc 3.0] (and latterly 3.1 [XProc 3.1]) and two well-supported implementations of that standard led us to reconsider how
and where this validation service would be best managed.
In short, XProc 3.0 was for us a game-changer. In particular, the availability of
both XML Calabash 3.x and MorganaXProc-III meant we could proceed with greater confidence when recommending the adoption of
a new technology and procurement of (commercial, potentially) software to support
it.
At the crossroads
We realised that the functional requirements for a new implementation of the portal
centred on XProc would be relatively quick and easy to write, as this was more of
a migration, in effect. We wanted to emulate what the existing portal did, basically,
but also seize on the chance to retire some redundant hard-coded logic there, most
of it relating to legacy publishing platforms.
An important part of making the successful business case (and an impactful design
decision) was to bring the validation logic entirely under our internal control.
This meant the software vendor we chose would essentially only need to implement a
web application whose principal service was to execute the validation XProc pipeline
on a supplier’s delivery. This would also reduce reliance on a particular vendor,
as we had experienced with the previous portal.
As well as commissioning this “vessel” to house a pipeline we would maintain ourselves,
we decided that the new implementation’s strategy for error handling should be to
fail early. With portal 1.0, even if errors occurred at any point during validation that would render the delivery
unusable, all checks were still applied, from beginning to end.
By contrast, version 2.0 terminates with an error on every such condition, saving
supplier time and sparing system resources.[2]
Design choices
We had learnt from our past experience that transparency and direct control were paramount
concerns for us, along with the ability to track and monitor supplier performance
over time.
Architecturally, we determined that all the reports generated by the validation pipeline
should be XML documents and stored in a native XML database.
We also split out the (rather clunky) issue tracking portion, in favour of our standard
issue tracking system (JIRA), for greater visibility across the business.
New skills in the team led towards using AWS lambdas [AWS Lambdas] for execution of the validation pipeline. A slight downside here is that some significant
changes require the lambda to be re-built and re-deployed, which we currently do need
our vendor’s assistance with, but this is projected to be a relatively rare occurrence.
We chose XProc because we are invested users of, believers in and proponents of the
XML technology stack; the previous portal was implemented in PHP.
This gives us the means to encapsulate the validation logic in a more maintainable,
testable and visible way. Not every member of our team is conversant with XProc, but
they can definitely read and understand XML and are familiar with unit testing frameworks
like XSpec [XSpec].
Why MorganaXProc-III?
We need to do some work with binaries, e.g., inspecting PDFs for corruption; images
for colour space, resolution, etc. For these, we use Python scripts built as executables.
We authored a prototype pipeline and put Morgana and Calabash head-to-head, with a
Morgana evaluation licence (Calabash supports the p:os-exec step, which we’d need for running our executables, but Morgana’s commercially-licensed
“Extended Edition” (MorganaXProc-IIIee) is required for that).
We were impressed by the performance (including its startup time when compared to
that of XML Calabash), prompt support, licensing model and pricing.
Once we had established with Morgana’s developer and co-editor of the XProc standard
Achim Berndzen that we would be operating within the terms of the EULA, the spend
was approved and we were good to go.
One notable difference encountered was that, where our validation logic threw deliberate
errors (using p:error), Calabash required these to be explicitly handled (with p:try/p:catch), whereas Morgana did not.
System architecture
The new portal has a bespoke .NET front end and API, both maintained by our vendors.
An existing application for a different purpose had already been built for us by the
same vendor, so this choice was based on cost and time, rather than an ideal
technological fit, for example, with the other, Java-based applications involved.
It is a very simple application. Beyond the login page, it serves only three main
pages: one enabling the upload of a delivery (or “digital bundle”); one displaying
a list of documentation and validation assets for download; and a dashboard of uploads.
Figure 2: Upload, dashboard and documentation screens
The user model is also simple, consisting of admins (people who can change stuff)
and standard users (who can upload submissions and can access schemas and documentation).
Simplified reports
A BaseX database in the back end stores a digested form of each validation report, fulfilling
our desire to monitor supplier performance.
SVRL is verbose, and can run very large indeed, especially for large documents with
many failing tests. We recognised that we only need a count of the distinct issue
IDs and an example of each, so for efficiency the final output is condensed to e.g.:
<report
uri="file:///C:/Users/asales/git/content-architecture/xproc/validation-portal/test/warning-scores/suppress-pdf-required/fail/9781350002876_txt_xml.zip"
timestamp="2026-02-13T15:39:05.4687649Z" warnings="208" distinctIds="41"
schematronVersion="[schematron-version-info] schematronVersion = 11"
workTitle="[title-value] Book title = "Shakespeare and Fun""
valid="false">
<warning id="bib-title-end-quote" count="27">
<svrl:successful-report test="ends-with(., '’') or ends-with(., '”')"
id="bib-title-end-quote" role="warning"
location="/*:book[namespace-uri()='http://docbook.org/ns/docbook'][1]/*:part[namespace-uri()='http://docbook.org/ns/docbook'][8]/*:chapter[namespace-uri()='http://docbook.org/ns/docbook'][3]/*:bibliolist[namespace-uri()='http://docbook.org/ns/docbook'][1]/*:bibliomixed[namespace-uri()='http://docbook.org/ns/docbook'][32]/*:bibliomset[namespace-uri()='http://docbook.org/ns/docbook'][1]/*:title[namespace-uri()='http://docbook.org/ns/docbook'][1]">
<svrl:text>[bib-title-end-quote] title in biblio item ends with single or double quote;
should it be outside the title? check within "bibliomset" element with id
"b-9781350002876-2101"</svrl:text>
</svrl:successful-report>
</warning>
<warning id="bib-title-start-quote" count="25">
<svrl:successful-report test="starts-with(., '‘') or starts-with(., '“')"
id="bib-title-start-quote" role="warning"
[...]
since we are mainly concerned with the overall breakdown of issues, rather than the
detail of each. In most cases, this reduces the SVRL file size from megabytes to kilobytes.
Note that the portal does also record who made the submission (not shown above).
Documaintenance
That list of documentation assets is just a filtered view of a local clone of our
GitHub repository, so we decided for ease of maintenance to use an XProc pipeline
to generate it (statically; as c:directory, etc.). Again, this means we have complete control over what is included and no software
changes are required to effect a change:
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
xmlns:c="http://www.w3.org/ns/xproc-step" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<p:input port="source" sequence="true"/> <!-- because there is no input -->
<p:output port="result"/>
<p:option name="git-path" select="'C:/Users/asales/git/'"/>
<p:variable name="schematron-base" select="'file:/' || $git-path || 'content-architecture/content-models/DocBook/Schematron/'"/>
<p:variable name="schema-base" select="'file:/' || $git-path || 'content-architecture/content-models/DocBook/schema/'"/>
<p:variable name="docs-base" select="'file:/' || $git-path || 'content-architecture/content-models/DocBook/doc/'"/>
<p:variable name="schematron-tei-base" select="'file:/' || $git-path || 'content-architecture/content-models/TEI/Schematron/'"/>
<p:variable name="schema-tei-base" select="'file:/' || $git-path || 'content-architecture/content-models/TEI/schema/'"/>
<p:variable name="docs-tei-base" select="'file:/' || $git-path || 'content-architecture/content-models/TEI/doc/Data%20model%20XML/'"/>
<p:directory-list path="{$docs-tei-base}"
include-filter='^Bloomsbury%20TEI%20XML%20-%20plays%20and%20screenplays%20v.+\.pdf$' detailed='true'/>
<!-- filter out all but the latest (by modification date): some XPath repetition here
because p:delete/@match can't contain variable references -->
<p:delete match="(//c:file[xs:dateTime(@last-modified) lt max(//c:file/xs:dateTime(@last-modified))])"/>
<p:variable name="docs-tei" select="."/>
<p:directory-list path="{$schematron-tei-base}"
include-filter='^bmyTEI\.sch$' detailed='true'/>
<p:variable name="schematron-tei" select="."/>
<p:directory-list path="{$schema-tei-base}" detailed='true'>
<p:with-option name="include-filter" select="('^bmyTEI\.rnc$', '^bmyTEI_BASE\.rnc$')"/>
</p:directory-list>
<p:variable name="schema-tei" select="."/>
<p:directory-list path="{$docs-base}"
include-filter='^Bloomsbury%20DocBook%20XML%20Data%20Specification%20v.+\.pdf$' detailed='true'/>
<!-- filter out all but the latest (by modification date): some XPath repetition here
because p:delete can't contain variable references -->
<p:delete match="(//c:file[xs:dateTime(@last-modified) lt max(//c:file/xs:dateTime(@last-modified))])"/>
<p:variable name="docs" select="."/>
<p:directory-list path="{$schematron-base}"
include-filter='^docbook-mods\.sch$' detailed='true'/>
<p:variable name="schematron" select="."/>
<p:directory-list path="{$schema-base}" detailed='true'>
<p:with-option name="include-filter" select="('^bloomsbury-mods\.rnc$', '^publishers-51cr\.rnc$')"/>
</p:directory-list>
<p:wrap-sequence wrapper="repo">
<p:with-input select="., $schematron, $docs, $schema-tei, $schematron-tei, $docs-tei"/>
</p:wrap-sequence>
</p:declare-step>
On upload to the portal, a lambda simply executes the validation pipeline on the zip
containing XML and supporting assets (images, PDFs, etc.). This encapsulates the core
validation logic, so where we’ll spend a bit of time.
Turning the corner
Much of the pipeline is concerned with unexciting checks on the integrity of the delivery:
after extracting the contents of the archive and resolving any XInclude instructions,
it checks that file and directory naming conventions have been followed and identifies
any superfluous or missing assets. These are all quite straightforward to do with
XProc, but inspecting the file system and archive structure made especially convenient.
For example, once we have established the primary XML document in the zip (because
there may be XIncludes) and resolved any inclusions and retained a trace of the fact
with xml:base:
<!-- infer the primary XML document from its filename; alternatively we could search for an appropriate root element -->
<p:variable name="primary-xml"
select="/c:directory[@name eq $zip-isbn]/c:file[matches(@name, '^' || $this-filename-pattern || '.xml$')]/@name"/>
<p:variable name='non-primary-xml' select="/c:directory/c:file[ends-with(@name, '.xml') and @name ne $primary-xml]"/>
<!-- resolve XIncludes: we explicitly fix up xml:base, so that we can check for any extraneous XML -->
<p:xinclude fixup-xml-base="true">
<p:with-input>
<p:document href="{resolve-uri($primary-xml, /c:directory/@xml:base)}"/>
</p:with-input>
</p:xinclude>
<!-- all xml:base attributes present after fixup -->
<p:variable name="xml-base-fixup" select="//@xml:base"/>
the rest of the work of identifying superfluous XML can then be expressed by some
fairly lucid and succinct XPath:
<!-- superfluous XML: extracted files not appearing as @xml:base values after XInclude resolution -->
<p:variable name="superfluous-xml" select="$non-primary-xml[not(base-uri(.) = $xml-base-fixup)]"/>
<!-- check for superfluous XML in the zip -->
<p:if test="exists($superfluous-xml)">
<p:error code="superfluous-xml">
<p:with-input>
<message isbn='{$zip-isbn}'>XML documents supplied which are not part of this delivery: {for $x in $superfluous-xml return string-join($x/@name, '
')}</message>
</p:with-input>
</p:error>
</p:if>
Schemalets (or epischemas)
Although we have our main schema documentation, some content sets require special
treatment which may be a one-off, so we typically avoid bloating the schema or documentation
with those instructions and constraints.
Instead, we issue additional “conversion instructions” in such cases and historically
have applied any programmatic checks in-house, using small, standalone Schematron
schemas particular to the case in hand. (We have christened these “schemalets”, as
they tend to be small-scale offshoots of the main schema they supplement.[3])
We would prefer to have these applied at point of receipt, which was not realistically
feasible using the legacy portal. So the method we are pursuing is to specify an appropriate
<?xml-model?> and have the pipeline parse that to determine which schemalet(s) to use.
This means we can now apply arbitrary schemalets as needed – provided of course that
the XML supplier includes the correct <?xml-model?> in their delivery.
Scoring the warnings
Our Schematron schemas distinguish between two basic levels of severity deserving
attention: errors and warnings.[4]
The acceptance criteria are currently that the presence of any errors means the XML
is invalid, therefore rejected; but any number of warnings can be present.
However – and this is probably the case more particularly with human-authored, discursive
content - some warnings are more important than others. Divining this is a significant
part of what the QA team is engaged in.
With the new freedoms afforded to us by the portal 2.0, we resolved to apply a “scoring”
algorithm to these warnings, to see which we could instead reject out of hand at submission-time.[5]
This meant collating information from the team about what would constitute a request
for re-work from the supplier, in effect the formal expression of these implicit tolerances.
The approach is to post-process the Schematron validation report. For a given constraint
ID, we can specify two parameters: a threshold, or absolute number we can accept of
a certain warning; or a proportion, i.e., for what percentage of the occurrences of
a particular structure the warning was reported.
The pipeline logic is that a Schematron SVRL document containing warnings, but no
errors, is filtered by an XSLT stylesheet, whose main processing template is:
while proportion-exceeded() calculates whether the percentage has been exceeded:
<xsl:function name="bmy:proportion-exceeded" as="attribute(invalid)?">
<xsl:param name="id" as="attribute(id)"/>
<xsl:param name="warningCount" as="attribute(count)"/>
<xsl:param name="nodeCount" as="xs:integer?"/>
<xsl:if test="($warningCount div $nodeCount) ge ($warning-percentages?($id) div 100)">
<xsl:attribute name="invalid" select="'proportionExceeded'"/>
</xsl:if>
</xsl:function>
If any warning is marked as invalid in this way, <report valid='false'>...</report> is output, with the upshot that the portal rejects the delivery.
It will be apparent from the snippets above that the configuration of these value
limits is currently done using maps. For extensibility and ease of maintenance, we
plan to move this into an XML configuration document instead, e.g.:
where each id corresponds to a Schematron warning ID.
We fully appreciate that greater complexity of algorithms is possible and do plan
to exploit this as needed. For now, we need this approach to prove its worth, then
we can expand on it.
The other major consideration for us is that having a portal whose validation criteria
are stricter than necessary appeals in principle, but when faced with the reality
of a fast-paced production environment, the risk to the business of potentially slowing
down or blocking supply unnecessarily is often too great.
ONIX metadata cross-checks
While we process a reasonable amount of licensed and acquired content, the majority
on balance is indigenous – commissioned by our in-house editors.
For this, our bibliographic metadata system (Biblio) produces ONIX [ONIX], which is syndicated to various recipients, including content aggregators and discovery
providers.
An ongoing issue for us has been discrepancies between the metadata held in that system
(i.e., as expressed in ONIX) and its equivalents contained in the XML representing
the content.
This can arise because of late-breaking changes while the title is in production
are not subsequently reflected in what is recorded in Biblio. For example, a work’s
subtitle might change after it has been typeset, perhaps because of space
constraints on the page.
The intention (not implemented as of this writing) is to take a regular feed of ONIX
XML for everything in production. These are very large (sometimes multi-gigabyte)
documents, but fortunately record-based XML, so the feed can easily be decomposed
into small, individual ONIX Products, then stored and queried efficiently.[6]
This means for a given ISBN, we can have the validation pipeline query a database
of ONIX Products and compare key items of metadata against the XML as captured, flagging or rejecting
any mismatch.
This approach also leads the way to potential automated reconciliation: results of
validation can be fed back into Biblio, most probably via a human-managed gateway
interface.
Testing the pipeline
Testing XProc 3.0 pipelines appears to be a nascent area of the technology, so we
were pleased to learn while preparing this paper that support in XSpec for this is
being added [XSpec 3.4 highlights].
In the interim, we use a unit-testing approach supported by BaseX's unit module.
Each test is represented by a function with the BaseX-specific XQuery annotation %unit:test. The tests are organised to enable the entire suite to be run at once. There is one
passing and one failing unit test for each of the conditions the pipeline can present.
For example, this pair of tests relate to the same condition, where media assets not
referenced by the XML are present in the delivered package:
declare %unit:test function _:extraneous-media-fail()
{
unit:assert(
_:run-pipeline('extraneous-media/fail/9781350996526_txt_xml.zip')/c:errors/c:error[@code eq 'extraneous-media']
)
};
(:~ clean up extracted files :)
declare %unit:after('extraneous-media-fail') function _:extraneous-media-fail-cleanup()
{
file:delete(resolve-uri('extraneous-media/fail/9781350996526/', static-base-uri()), true())
};
declare %unit:test function _:extraneous-media-pass()
{
unit:assert-equals(
_:run-pipeline('extraneous-media/pass/9781350996526_txt_xml.zip')/c:errors/c:error[@code eq 'extraneous-media'],
()
)
};
(:~ clean up extracted files :)
declare %unit:after('extraneous-media-pass') function _:extraneous-media-pass-cleanup()
{
file:delete(resolve-uri('extraneous-media/pass/9781350996526/', static-base-uri()), true())
};
Note
The %unit:after annotation in BaseX specifies that the body of the function is executed after the
named %unit:test function has been executed: here, we clean up any extracted files.
There is a performance overhead with this approach, in that we start a new instance
of Morgana for every test, invoked via the BaseX function proc:execute, but an XML document representing the result of that invocation is at least returned
for inspection.[7]
We do have some test cases that address individual steps in isolation (enabled, for
example, by the appropriate Morgana command line options), but this is less relevant for us as we mostly use built-in steps in the pipeline
as currently written. This is where our fail-early philosophy presents a slight disadvantage,
because if we want to test in a way that resembles real-world scenarios closely, we
have the overhead of maintaining test cases that are valid up to a certain point in
the pipeline.
For most tests that is fine, as the criteria are quite stable; but our
Schematron schemas do change relatively often, which can mean a pipeline test
suddenly fails as a consequence. It would be onerous for us to maintain complete,
shared test case documents, so the tactic we have employed here amounts to
"mocking", whereby a step option accepts suitable input to produce the desired
behaviour. In this unit test, setting the $use-mocking option to
true() has the effect of making the step output the input document,
just as would be the case if validation succeeded:
declare %unit:test function _:schema-validation-pass()
{
let $result := _:run-library-step(
'schema-validation/pass/9780567706652_txt_xml.xml',
$_:STEPS_LIBRARY_PATH,
'Q{http://www.bloomsbury.com/xproc}bmy:validate-rnc-sch',
map{},
map{
'relax-ng-uri':'../../content-models/DocBook/schema/bloomsbury-mods.rnc',
'schematron-uri':'../../content-models/DocBook/schematron/docbook-mods.sch',
'use-mocking':true()
}
)
let $output := $result/output=>parse-xml()
return
unit:assert-equals($output/c:errors, ())
};
At first this can feel wrong, since we effectively bypass the test, or at least produce
an artificial result; but we have reasoned that this is on balance tolerable since
a) the main purpose of this test suite is to test the pipeline's behaviour, rather than that of the schemas it references; and b) we already have
robust test suites in place elsewhere to test that the schemas themselves are fit
for purpose.
Extensibility
Not long after development of the new portal was completed, a new use case for
a content type arose. We had handled this content before but on such a small scale
that it didn’t warrant building into the portal. Now, however, the scale of the
incoming content is ambitious, so we feel vindicated in that we can now easily
incorporate a new content type by some relatively self-contained changes to the
pipeline.
The changes will amount to adding some minor decision logic to respond to the new
markup to select the correct schema, and re-generating the filtered view of our cloned
GitHub repository, to make the added validation assets available to users on the portal
front end.
Conclusion
The changes we have made to our validation infrastructure have benefited our digital
production workflows, as follows:
We have traded reliance on a vendor offering a "black box" service for free,
for a low-maintenance, low-cost validation service.
We have brought the validation logic the service applies in house, in turn making
it more adaptable and responsive to the delivery quality issues we face.
We can collate and interpret data on our suppliers' performance
programmatically for the first time.
In early 2017, our team was understandably challenged by senior leadership, questioning
why QA work carried out by human beings was still needed if we had schemas and a validation
portal to apply them automatically on receipt from the supplier. Our response contained
the phrase, "we will always need to do some checking [...] because schemas alone are
not enough in some cases - they have not yet reached a level of artificial intelligence."
Perhaps this will be the next significant development in XML quality assurance of
this nature.
[1] Schematron Validation Report Language [Schematron 2025], Annex D (informative).
[2] Suppliers are selected on that basis that they have the tools and expertise to
provide good-quality capture, which we support with our schemas, documentation
and active dialogue of feedback on quality. Our ideal way of working and what we
are seeking to encourage under version 2.0 is that they prepare good-quality XML
using these offline resources before upload to the portal,
which should act for them as a final check before delivery.
[3] Multiple schemas applied in this way have been described as epischemas [Imsieke, 2017], although our own preference is to use additional Schematron schemas for their flexibility
in expressing complex business rules.
[4] We note that the latest edition of the Schematron standard [Schematron 2025] gives more potential flexibility with the dedicated attribute severity (glossed at What's New In Schematron 4).
[6] Our experimentation has shown that performance-wise these are better ingested, indexed
and queried as small, individual documents in BaseX.
[7] Inverting this process, so that a single Morgana pipeline invokes the
tests instead, would likely improve performance, but Morgana does not
currently support invoking XQuery using BaseX.