A container element for abstract Schematron rules
David Maus, 19. Feb 2021
I found the following usage patterns in the recently published version of the Lightweight Information Describing Objects schema (LIDO) and Antenna House's focheck utility.
-
The LIDO schema embeds abstract rules in an
xs:appinfo
element at the top of the document. Each abstract rule is wrapped in asch:pattern
with detailed documentation. The abstract rules defined at the top of the document are used in different places. For example, the abstract rulesch_MixedContent
contains an assertion whose expression checks that the context element contains either plain text or a combination of terminology elements. This abstract rule is used to check various elements with the same content model such aslido:genderActor
orlido:measurementType
. - Antenna House's focheck uses a similary pattern. The file abstract.sch defines a pattern containing abstract rules. The file is included in fo-property.sch where the abstract rules are used.
In both cases the abstract rules are referenced in a
rule
attribute of a
sch:extends
element.
This use of abstract rules is at odds with the Schematron specification. Section 5.4.13 specifies an abstract rule
as
a list of assertions that will be invoked by rules belonging to the same pattern
. Meaning, you can
only
sch:extend
an abstract rule if it is defined in the same pattern. To some degree
this makes sense. The
sch:pattern
element provides scope to the contained rules such that
patterns are independent of each other.
Technically you can work around this limitation using
sch:extends
with a
href
attribute. This attribute is mutual exclusive with the
rule
attribute and was introduced with the 2016 release of the Schematron specification. It
ought to reference a Schematron element in an external declaration whose content should be inserted in place of the
sch:extends
element.
But this feels wrong.
First, the semantics of
sch:extends
with
href
is defined
by what the processor should do — insert the contents in place of the extends element — rather than by what we
want to express.
Second, it forces you to externalize parts of your schema for the only reason to make them available.
Third, the way
href
works forces you to create one file per abstract rule, or to rely
on the Schematron implementation to resolve a fragment identifier to a rule in the external file.
E.g. the rule
<rule context="fo:*/@border-bottom-color"> <extends rule="color-transparent"/> </rule>
needs to be rewritten as
<rule context="fo:*/@border-bottom-color"> <extends href="abstract.sch#color-transparent"/> </rule>
and the Schematron processor needs to resolve the fragment
#color-transparent
to the corresponding
rule.
Note that
sch:extends
is only allowed as child of a
sch:rule
. Thus, its sole purpose is to reuse constraints from a different pattern.
I think adding the
href
attribute to
sch:extends
is the
wrong solution for reuse across patterns.
Instead, I propose a new top-level
sch:rules
element that contains only abstract
rules. The abstract rules defined in this element are globally available and can be used in a
rule
attribute of a
sch:extends
element.
This proposed element requires the following changes to the text of the 2020 specification:
-
Change paragraph 5, sentence 2 of section 5.4.13.
An abstract rule is a list of assertions that will be invoked by other rules using the extends element. A rule can extend an abstract rule if it is defined in the same pattern or globally available (5.4.16). Abstract rules provide a mechanism for reducing schema size.
-
Add a section 5.4.16 describing the rules element.
5.4.16 rules element
The rules element provides a section containing globally available abstract rules.
-
Update the RelaxNG schema in Annex A.
diff --git a/schematron.rnc b/schematron.rnc index ff9dbef..ee9f6dd 100644 --- a/schematron.rnc +++ b/schematron.rnc @@ -38,7 +38,7 @@ schema = attribute queryBinding { non-empty-string }?, (foreign & inclusion* - & (title?, ns*, p*, let*, phase*, pattern+, p*, diagnostics?, properties?)) + & (title?, ns*, p*, let*, phase*, rules*, pattern+, p*, diagnostics?, properties?)) } active = element active { @@ -106,6 +106,14 @@ param = attribute value { non-empty-string } } +rules = + element rules { + rich, + title?, + p*, + abstractRule* + } + pattern = element pattern { attribute documents { pathValue }?, @@ -115,7 +123,7 @@ pattern = & ((attribute abstract { "true" }, attribute id { xsd:ID }, title?, - (p*, let*, rule*)) + (p*, let*, (rule | abstractRule)*)) | (attribute abstract { "false" }?, attribute id { xsd:ID }?, title?, @@ -151,6 +159,18 @@ report = linkable, (foreign & (text | name | value-of | emph | dir | span)*) } +abstractRule = + element rule { + attribute flag { flagValue }?, + rich, + linkable, + (foreign + & inclusion* + & (attribute abstract { "true" }, + attribute id { xsd:ID }, + let*, + (assert | report | extends | p)+)) + } rule = element rule { attribute flag { flagValue }?, @@ -158,15 +178,11 @@ rule = linkable, (foreign & inclusion* - & ((attribute abstract { "true" }, - attribute id { xsd:ID }, + & ((attribute context { pathValue }, + attribute id { xsd:ID }?, + attribute abstract { "false" }?, let*, - (assert | report | extends | p)+) - | (attribute context { pathValue }, - attribute id { xsd:ID }?, - attribute abstract { "false" }?, - let*, - (assert | report | extends | p)+))) + (assert | report | extends | p)+))) } span = element span {