What is new in the Second Draft?

It is planed for 2017 to publish the Second Draft of the Schematron QuickFix specification. The first Draft was published in April 2015. The specification very incomplete, but the distributed Schema was almost ready. In the following, we will focus on the schema changes from the first to the second draft.

In Summary, there are three key changes:

The following sections should describe this changes in detail.

Generic QuickFixes

In the first draft, it was not possible to have a dynamic amount of QuickFixes for each Schematron error. The QuickFix developer needs to define, how many QuickFixes a Schematron error should have at the end. The only chance to make the amount of available QuickFixes depending on the source document, was the use-when attribute. But this is only to hide QuickFixes, not to create an unbounded count of QuickFixes for one error.

The following use case should demonstrate, what is possible with generic QuickFixes:

Let assume you have multiple persons in your person.xml (example from the User Guide):

1 <?xml-model href="people.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
2 <people>
3 <person>
4 <name>Nico Kutscherauer</name>
5 <dateOfBirth>1986-04-05</dateOfBirth>
6 <age>23</age>
7 </person>
8 <person>
9 <name>Lisa Simpson</name>
10 <dateOfBirth>2006-01-01</dateOfBirth>
11 <age>9</age>
12 </person>
13 <person>
14 <name>Homer Simpson</name>
15 <dateOfBirth>1960-01-01</dateOfBirth>
16 <age>10</age>
17 </person>
18 </people>

Just to remember: The business rule was, that all persons should be of age in this list.

One possible fix would be to take over the <dateOfBirth> of another <person> which is of age. So it should be a number of QuickFixes available, as much as other persons are in this list. But this is depending on the source document, so the schematron developer does not know it when he design his QuickFixes.

With the generic QuickFixes, this is now possible:

13 <sqf:fix id="takeFrom" use-for-each="../person except .">
14 <let name="person" value="$sqf:current"/>
15 <sqf:description>
16 <sqf:title>Take the birthday and age of <value-of select="$person/name"/></sqf:title>
17 </sqf:description>
18 <sqf:replace match="age" target="age" node-type="element" select="$person/age/text()"/>
19 <sqf:replace match="dateOfBirth" target="dateOfBirth" node-type="element" select="$person/dateOfBirth/text()"/>
20 </sqf:fix>

To create a generic QuickFix, the <sqf:fix> element gets an use-for-each attribute. The XPath will be evaluated by the context of the Schematron error. The result value will be treated as sequence. For each item of this sequence one QuickFix will be generated.

The context inside of the QuickFix will be still the current Schematron error, but in the $sqf:current variable the current item of the sequence will be stored.

QuickFix content model

In the first draft, the content model of the <sqf:fix> element was very strict. The simplified description (excluding all non-SQF elements) would be:

(sqf:param*, sqf:description, sqf:user-entry*, (sqf:call-fix | (sqf:add | sqf:delete | sqf:replace | sqf:stringReplace)+))

There are two weak points in this structure. On the one hand it is only allowed to have exactly one <sqf:description> for each QuickFix. This will be discussed in the section Localisation concept bellow. On the other hand the <sqf:call-fix> is not combinable with other <sqf:call-fix> or activity elements. Additionally both, the called and the calling QuickFix are forced to have a <sqf:description> element, so the description of the called QuickFix is always useless.

Let this demonstrate on an example:

3 <sqf:fixes>
4 <sqf:fix id="movePerson">
5 <sqf:description>
6 <sqf:title>Move the person at the end</sqf:title>
7 </sqf:description>
8 <sqf:call-fix ref="moveQuickFix">
9 <sqf:with-param name="moveContext" select="."/>
10 <sqf:with-param name="moveTo" select="/*/person[last()]"/>
11 </sqf:call-fix>
12 </sqf:fix>
13 <sqf:fix id="moveQuickFix">
14 <sqf:param name="moveContext" required="yes"/>
15 <sqf:param name="moveTo" required="yes"/>
16 <sqf:description>
17 <sqf:title>Move the element <name path="$moveContext"/> after the element <name path="$moveTo"/>.</sqf:title>
18 </sqf:description>
19 <sqf:add match="$moveTo" position="after" select="$moveContext"/>
20 <sqf:delete match="$moveContext"/>
21 </sqf:fix>
22 </sqf:fixes>

As the QuickFix moveQuickFix has required parameters, it is not possible to refer to the QuickFix directly from a <sch:assert> or <sch:report> element. So the only way to use the QuickFix is, to call it by a <sqf:call-fix> element. But regarding to the first draft structure it is required, that the called QuickFix have an own <sqf:description> element (like the QuickFix move Person in the example). This description will be used for the calling QuickFix. So the description of the called QuickFix moveQuickFix will be never used.

The second draft has in contrast a quite lax content model for the <sqf:fix> element. The simplified version looks like:

sqf:param*, (sqf:description+, ( sqf:call-fix | sqf:add | sqf:delete | sqf:replace | sqf:stringReplace )+ ) | (sqf:call-fix)

Alternatively to a <sqf:desctription> element, the sqf:fix element could contain exactly one <sqf:call-fix> element. Additionally, if a sqf:description element is used, the sqf:call-fix element can combined with any other activity element. In fact, it can be used synonymical. This content model works, if some additional rules are respected:

  1. There should not be two descriptions for the same language.

  2. A sqf:call-fix inherit the description of a called QuickFix and the following conditions:

    1. The calling QuickFix inherits the descriptions of the called QuickFix, only if the calling QuickFix contains exactly one <sqf:call-fix> element without any activity element.

    2. The calling QuickFix inherits only descriptions, which are in a different language as those, who was defined for the calling QuickFix.

This lax content model enables significant use cases:

Localisation concept

There is no localisation concept in the first draft or just a rudimentary one: there is a xml:lang attribute available for the following elements:

But the logic, how to implement QuickFixes with descriptions for different languages is missing. There is only one <sqf:description> element permitted inside of <sqf:fix> or <sqf:user-entry> and it can contain exactly one <sqf:title> element only.

The second draft introduce now a first version of a localisation concept. As described in the section QuickFix content model above, it is now permitted to have multiple description for the same QuickFix as long as they have different xml:lang values. The following example shows, how to localize a QuickFix:

1 <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2" xmlns:sqf="http://www.schematron-quickfix.com/validator/process" xml:lang="en">
2 <title>People &#x2013; test</title>
3 <pattern>
4 <rule context="person">
5 <let name="currYear" value="year-from-date(current-date())"/>
6 <let name="maxBirth" value="replace(string(current-date()),
7 string($currYear),
8 string($currYear - age - 1))"/>
9 <let name="minBirth" value="replace(string(current-date()),string($currYear), string($currYear - age))"/>
10 <let name="birth" value="xs:date(dateOfBirth)"/>
11 <assert test="$birth gt xs:date($maxBirth) and $birth le xs:date($minBirth)"><value-of select="name"/> is not <value-of select="age"/> years old or is not born on <value-of select="dateOfBirth"/>.</assert>
12 <assert test="not($birth gt xs:date($maxBirth) and $birth le xs:date($minBirth)) and number(age) ge 18" sqf:fix="delete"><value-of select="name"/> is too young.</assert>
13 <sqf:fix id="delete">
14 <sqf:description>
15 <sqf:title>Delete the person from the list.</sqf:title>
16 <sqf:p>The fix deletes this person from the list, because it is out of age.</sqf:p>
17 </sqf:description>
18 <sqf:description xml:lang="de">
19 <sqf:title>Lösche die Person von der Liste.</sqf:title>
20 <sqf:p>Dieser Fix löscht die Person von der Liste, da sie nicht volljährig ist.</sqf:p>
21 </sqf:description>
22 <sqf:delete/>
23 </sqf:fix>
24 </rule>
25 </pattern>
26 </schema>

If the implementation does not support the localisation concept, it should display only the first description of the QuickFix.

Add localized descriptions to existing QuickFixes

Additionally to the multiple descriptions, the new <sqf:call-fix> element logic can be used to localize existing QuickFix without any changes of the them. This is possible, because if a QuickFix contain only one sqf:call-fix element and no other activity element it inherits beside the complete logic also all descriptions of the called QuickFix, if they have different languages as those descriptions of it self.

For the following example, let assume, that we have an existing QuickFix library localized-lib.sqf:

1 <?xml version="1.0" encoding="UTF-8"?>
2 <sqf:fixes xmlns:sqf="http://www.schematron-quickfix.com/validator/process"
3 xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:lang="en">
4 <sqf:fix id="delete">
5 <sqf:description>
6 <sqf:title>Delete the person from the list.</sqf:title>
7 <sqf:p>The fix deletes this person from the list, because it is out of age.</sqf:p>
8 </sqf:description>
9 <sqf:delete/>
10 </sqf:fix>
11 </sqf:fixes>

But this library just contains QuickFixes in English.

A Schematron schema uses this library by including it:

1 <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2" xmlns:sqf="http://www.schematron-quickfix.com/validator/process" xml:lang="en">
2 <title>People &#x2013; test</title>
3 <pattern>
15 </pattern>
16 <include href="localized-lib.sqf"/>
26 </schema>

To add localized description(s) the QuickFix delete, it just needs to create a new QuickFix delete_loc which contains descriptions of other languages and a call of the QuickFix delete:

1 <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2" xmlns:sqf="http://www.schematron-quickfix.com/validator/process" xml:lang="en">
2 <title>People &#x2013; test</title>
3 <pattern>
15 </pattern>
16 <include href="localized-lib.sqf"/>
17 <sqf:fixes>
18 <sqf:fix id="delete_loc">
19 <sqf:description xml:lang="de">
20 <sqf:title>Lösche die Person von der Liste.</sqf:title>
21 <sqf:p>Dieser Fix löscht die Person von der Liste, da sie nicht volljährig ist.</sqf:p>
22 </sqf:description>
23 <sqf:call-fix ref="delete"/>
24 </sqf:fix>
25 </sqf:fixes>
26 </schema>

Further discussions

Please note, that the issue about the language concept is not closed yet. The reason is, that this concept does not include the possibility to store all descriptions into an external file and just use them by references. Depending on the decisions of this discussion there could be changes on the existing structure. This is one reason, because the second draft is not published yet.

© Copyright 2014-2017 Nico Kutscherauer