Ignore Schematron Checks

Schematron allows you to define your own custom validation rules for XML documents. You can even set different severities for the reported problems, such as fatal, error, warn, info. But in some situations, the users want to ignore some of this rules. For example, if the problem severity is warn, maybe it is not something important, or if the problem severity is info, maybe is more like a guideline for the users.

There is no built-in implementation to ignore reported Schematron problems. But this mechanism can be implemented in different ways using the current Schematron support and quick fixes. To do this, you need to uniquely determine the rule that you want to ignore and you can do this by assigning an ID to the rule check. Then you need to store this ID in the file or in a separate file or option to avoid triggering the Schematron check in the future. You can assign the ID to a rule element. This means that you will be able to ignore all the asserts from that rule. Alternatively, you can assign an ID to each assert/report element, and this means that you will be able to ignore each assert/report from the rule separately.

In my implementation, I decided to assign an ID for each assert/report check. The ID is specified in the value of the ruleCheckId variable, and it will be used by the isRuleIgnored() function to check if the current rule is marked as ignored. A better way to implement this is to use the id attribute value specified on the assert/report element, but this means that it will not work with the current Schematron implementation that does not process this ID value. In the following example, the report is triggered only if the boldCheck rule is not marked as ignored.

Example 8. Schematron rule that is triggered only if is not ignored

<sch:let name="ruleCheckId" value="'boldCheck'"/>
<sch:report test="not(func:isRuleIgnored(., $ruleCheckId)) and exists(b)"
  sqf:fix="resolveBold ignoreRule ignoreRuleGlobal" role="warn"> Bold 
  element is not allowed in title. </sch:report>

To store the ID of the rule check that is ignored in the XML document, I used a processing instruction with the name SuppressRule. The processing instruction is added before the current node, in case you want to ignore only the current rule check, or at the end of the document in case you want to ignore the current rule in the entire document. Another solution would be to store this ID in a separate document to avoid adding processing instructions in the edited XML document.

The following XSLT function checks if there is a processing instruction with the name SuppressRule before the current element or at the end of the document, and if the ID of the current rule is specified in the processing instruction. It returns true if the rule is specified as ignored.

Example 9. XSLT function that verifies if an assert/report is marked as ignored

<xsl:function name="func:isRuleIgnored" as="xs:boolean">
  <xsl:param name="node"/>
  <xsl:param name="ruleId"/>
  <xsl:value-of
    select="($ruleId =
        $node/preceding-sibling::processing-instruction()[name() = 
            'SuppressRule']/tokenize(., ' ')
      or $ruleId = 
        /processing-instruction()[name() = 
            'SuppressRule']/tokenize(., ' '))"/>
</xsl:function>

To mark a rule as ignored, you can use quick fix actions. You can define a quick fix that will marked as ignore the current rule check by adding a processing instruction before the current element, with the name SuppressRule and the value of the rule ID. In case the processing instruction is already added for other ignored rules, it will concatenate the current rule ID to the existing ones.

Example 10. Quick fix that adds the current check to the ignore list

<sqf:fix id="ignoreRule" role="delete">
  <sqf:description>
    <sqf:title>Ignore current rule</sqf:title>
  </sqf:description>
  <sch:let 
    name="ignoredRulePI" 
    value="preceding-sibling::processing-instruction()[name() = 'SuppressRule']"/>
  <sqf:delete match="$ignoredRulePI" use-when="$ignoredRulePI"/>
  <sqf:add position="before">
    <xsl:processing-instruction
     name="SuppressRule"
     select="concat($ignoredRulePI, ' ', $ruleCheckId)"/>
  </sqf:add>
</sqf:fix>

You can also define a similar quick fix action that will ignore the current rule check for the entire document by adding the processing instruction at the end of the document. You can also define a quick fix action that will add the rule ID in a separate file and specify if the rule is ignored for the current file or for the entire project.