Testing of context nodes
Now, we want to test the context node selected on the previous site in order to finally generate an error message. The example shows that the <report> element defines a test. The general structure of a test can be easily understood:
<report test="XPath-expression">error message</report>
If an error is discovered, the content of the <report> element will be outputted as an error message.
The XPath expression in the test attribute provides a Boolean value as return value. If no Boolean value is returned by XPath, the Schematron parser will try to transform the return value into such a Boolean value (cast). For instance, an expression which provides one or several nodes as result is transformed into the Boolean value true; or into the value false, in the case of an empty node set.
<schema xmlns="http://purl.oclc.org/dsdl/schematron">
<ns uri="http://www.schematron.info/ark" prefix="ark"/>
<pattern>
<rule context="ark:animal[@carnivore='yes']">
<report test="parent::*/ark:animal[@carnivore='no']">
There are carnivores and herbivores in one accommodation.
The animals are not a food source!
</report>
</rule>
</pattern>
</schema>
The text above is looking for an <animal> element with the attribute carnivore='no'. If there are one or several of such elements, the return value of the XPath expression will be transformed into the value true. Otherwise the value false will be returned.
The <report> element enables a test for an error: If the XPath expression in the test attribute returns the value true, an error is occured and the implementation outputs the error message. In this example, the value true is used as error indicator. Alternatively, Schematron allows the <assert> element. Its structure is identical to the one of the <report> test:
<assert test="XPath-expression">error message</assert>
The difference between <report> and <assert> tests lies in the processing of the Boolean value. An <assert> element defines for the context node an assumption which has to apply. So, the value false indicates that this assumption is not met and therefore causes an error. In this case, the error indicator is the value false.
Someone who is intensively dealing with XPath will rightly remark that the distinction between <report> and <assert > tests is actually not necessary since each <assert> test can be easily transformed into a <report> test. This is demonstrated by the two following rules having identical functionalities:
<rule context="ark:animal[@carnivore='yes']">
<report test="parent::*/ark:animal[@carnivore='no']">
There are carnivores and herbivores in one accommodation.
The animals are not a food source!
</report>
</rule>
<rule context="ark:animal[@carnivore='yes']">
<assert test="not(parent::*/ark:animal[@carnivore='no'])">
There are carnivores and herbivores in one accommodation.
The animals are not a food source!
</assert>
</rule>
With the help of the XPath function not(), each <report> test can be transformed into an identical <assert > test and vice versa. So, what is the reason for this distinction between assert and report tests? On the one hand there are semantical reasons, on the other hand there is a pragmatical reason.
Semantically, Schematron distinguishes between a test for an error and a test for an assumption. That may lead to different ways of displaying the error message depending on the used implementation. A distinction is made between the following two assertions:
- error (<report>): The context node must not meet the condition.
- assumption (<assert>): The context node must meet the condition.
For pragmatic reasons, the otherwise required not() function is not used in this connection. However, developers may decide whether only <assert> or only <report> tests shall be used or a mixed form. With our current level of knowledge, we are now able to completely reconstruct the Schematron rule:
<schema xmlns="http://purl.oclc.org/dsdl/schematron">
<ns uri="http://www.schematron.info/ark" prefix="ark"/>
<pattern>
<rule context="ark:animal[@carnivore='yes']">
<report test="parent::*/ark:animal[@carnivore='no']">
There are carnivores and herbivores in one accommodation.
The animals are not a food source!
</report>
</rule>
</pattern>
</schema>
The rule is applied to all <animal> elements having a carnivore attribute with the value yes. For this rule a test has been defined which says: The parent element of the appropriate <animal> element (which means <room>) must not contain an <animal> element having the carnivore attribute with the value no.
It is known from the XML Schema that animals are accommodated in rooms. Therefore, the parent element of an <animal> element is always a <room> element. So if the parent element of the context element contains a further <animal> element with the attribute carnivore='no', this <animal> would be a sibling element of the context element and thus both would be accommodated in the same room. Since the context element is always a carnivore and the element searched for is always a herbivore in this rule, the composition of the room contradicts the rules on the Ark in case a herbivore is found during the <report> test.
Once a node has been selected, instead as before, it can be checked by means of several tests. For our example the rule is expanded, so that not only carnivores must be separated from herbivores but also from weaker carnivores. Because it is very likely that a carnivore also attacks other carnivores being weaker than itself! The strength of an animal is determined on the basis of its body weight. It shall apply that a carnivore may be twice as heavy as the lightest roommate:
<pattern>
<rule context="ark:animal[@carnivore='yes']">
<report test="parent::*/ark:animal[@carnivore='no']">
There are carnivores and herbivores in one accommodation.
The animals are not a food source!
</report>
<report test="parent::*/ark:animal/ark:weight < (ark:weight div 2)">
Noah, this carnivore is too strong (heavy) for its roommate.
The carnivore could use it as a food source.
</report>
</rule>
</pattern>
The XPath expression checks whether a sibling element (parent::*/ark:animal) has a <weight> child element (/ark:weight) whose value is smaller (<) than half the value of the <weight> element of the context node (ark:weight div 2).
If the context node shall be tested several times, the test elements will be arranged in consecutive order. In this example <assert> and <report> elements can be mixed freely. If a matching context node is found in the instance, it will be checked by all tests. The order does not matter – except for the depiction in the error message.
Note for XSLT experts:
During the Skeleton transformation into XSLT, tests are converted to "if" or "choose-when-otherwise" constructs. The report test is as follows:
<report test="parent::*/ark:animal[@carnivore='no']">There are carnivores and herbivores in one accommodation. The animals are not a food source!</report>
converted to:
<xsl:if test="ark:animal[@carnivore='yes']">
[…]
<!-- Implementation of the error message in SVRL -->
[…]
</xsl:if>
Whereas an assert test uses the <xsl:choose> element:
<assert test="not(parent::*/ark:animal[@carnivore='no'])">There are carnivores and herbivores in one accommodation. The animals are not a food source!</assert>
converted to:
<xsl:choose>
<xsl:when test="not(parent::*/ark:animal[@carnivore='no'])"/>
<xsl:otherwise>
[…]
<!-- Implementation of the error message in SVRL -->
[…]
</xsl:otherwise>
</xsl:choose>
Copyright © dpunkt.verlag GmbH 2011
Printing of the online version is permitted exclusively for private use. Otherwise this chapter from the book "Schematron - Effiziente Business Rules für XML-Dokumente" is subject to the same provisions as those applicable for the hardcover edition: The work including all its components is protected by copyright. All rights reserved, including reproduction, translation, microfilming as well as storage and processing in electronic systems.
dpunkt.verlag GmbH, Ringstraße 19B, 69115 Heidelberg, fon 06221-14830, fax 06221-148399, hallo(at)dpunkt.de