Tester les nœuds de contexte

Nous voulons désormais tester le nœud de contexte sélectionné précédemment, afin de pouvoir générer un message d'erreur. À l'aide de cet exemple, nous pouvons d'ores et déjà contaster que l'élément <report> définit un test. La structure principale d'un test est facile à comprendre:

<report test="expression XPath">message d'erreur</report>

Lorsqu'une erreur est constatée, le contenu de l'élément <report> est présenté comme étant un message d'erreur.
L'expression XPath dans l'attribut test fournit une valeur booléenne, comme valeur de retour. Si XPath ne retourne aucune valeur booléenne, le parseur Schematron essaie de transformer la valeur de retour en valeur booléenne. Par exemple, une expression qui fournit comme résultat un ou bien plusieurs nœuds sera transformée en valeur booléenne true (vraie) ou s'il s'agit d'un node-set vide en valeur false (fausse).

<schema xmlns="http://purl.oclc.org/dsdl/schematron"> 
  <ns uri="http://www.schematron.info/arche" prefix="arc"/> 
  <pattern> 
    <rule context="arc:animal[@carnivore='oui']"> 
      <report test="parent::*/arc:animal[@carnivore='non']"> 
        Il y a des carnivores et des herbivores dans un même compartiment. 
        Les animaux ne sont pas des ressources alimentaires! 
      </report> 
    </rule> 
  </pattern> 
</schema>

Le test ci-dessus cherche un élément <animal> ayant comme attribut carnivore='non'. Si un ou plusieurs de ces éléments existent, la valeur de retour de l'expression XPath sera transformée en valeur true, sinon en valeur false.
L'élément <report> permet de vérifier la présence d'une erreur. Si l'expression XPath dans l'attribut test renvoie la valeur true, cela signifie qu'une erreur est survenue et que l'implémentation envoie un message d'erreur. La valeur true sera utilisée dans ce cas comme indicateur d'erreur. Comme alternative, Schematron permet l'utilisation de l'élément <assert>. Sa structure est identique à celle du test <report>:

<assert test="expression XPath">message d'erreur</assert>

La différence entre les tests <report> et <assert> réside dans le traitement des valeurs booléennes. Un élément <assert> définit une hypothèse pour le nœud de contexte, qui doit se révéler juste. La valeur false indique que cette hypothèse n'est pas admise et déclenche une erreur. Dans ce cas, l'indicateur d'erreur est la valeur false.
Celui qui se plonge avec intérêt dans la matière XPath remarquera, qu'opérer une différence entre les tests <report> et <assert > n'est pas vraiment nécessaire, puisque chaque test <assert> peut être très facilement transformé en un test <report>, comme le montrent les deux règles suivantes qui ont un mode de fonctionnement identique:

<rule context="arc:animal[@carnivore='oui']"> 
  <report test="parent::*/arc:animal[@carnivore='non']"> 
    Il y a des carnivores et des herbivores dans un même compartiment. 
    Les animaux ne sont pas une ressource alimentaire!
  </report> 
</rule> 
<rule context="arc:animal[@carnivore='oui']"> 
  <assert test="not(parent::*/arc:animal[@carnivore='non'])"> 
    Il y a des carnivores et des herbivores dans un même compartiment. 
    Les animaux ne sont pas une ressource alimentaire!
  </assert> 
</rule>

Grâce à la fonction XPath not(), chaque test <report> peut être transformé en test <assert> et vice versa, ayant un même mode de fonctionnement. Pourquoi opérer une différence entre asserts et reports? Pour des raisons sémantiques d'une part et d'autre part pour des raisons pragmatiques.

Du point de vue sémantique, Schematron fait une distinction entre un test pour détecter les erreurs et un autre pour les hypothèses. Cela peut conduire, selon l'implémentation utilisée, à différentes représentations des messages d'erreur. Nous ferons donc une différence entre les deux affirmations suivantes:

  • Erreur (<report>): Le nœud de contexte interdit de remplir la condition.
  • Hypothèse (<assert>): Le nœud de contexte doit remplir la condition.

D'un point de vue pragmatique, l'utilisation de la fonction not(), d'habitude si nécessaire, peut être abandonnée dans ce contexte. Toutefois, les développeurs peuvent choisir respectivement entre un test <assert> ou alors le test <report> ou bien alors un mélange des deux. À l'aide des connaissances acquises, nous pouvons désormais comprendre la règle Schematron dans son ensemble:

<schema xmlns="http://purl.oclc.org/dsdl/schematron"> 
  <ns uri="http://www.schematron.info/arche" prefix="arc"/> 
  <pattern> 
    <rule context="arc:animal[@carnivore='oui']"> 
      <report test="parent::*/arc:animal[@carnivore='non']"> 
        Il y a des carnivores et des herbivores dans un même compartiment. 
        Les animaux ne sont pas une ressource alimentaire! 
      </report> 
    </rule> 
  </pattern> 
</schema>

La règle sera appliquée à tous les éléments <animal> qui ont un attribut carnivore avec la valeur "oui". Un test a été défini pour cette règle qui énonce que l'élément parent de l'élément <animal> correspondant (c'est-à-dire <compartiment>) ne doit comporter aucun élément <animal> ayant un attribut carnivore avec la valeur non.
Nous savons grâce au schéma XML que des animaux ont été placés dans des compartiments. Dès lors, l'élément parent d'un élément <animal> est toujours un élément <compartiment>. Si l'élément parent de l'élément de contexte comporte un autre élément <animal> avec l´attribut carnivore='non', alors cet élément <animal> est un élément frère de l'élément de contexte, par conséquent les deux seraient placés dans le même compartiment. Étant donné que dans cette règle, l'élément de contexte est toujours un carnivore et que l'élément recherché est un herbivore, la composition du compartiment est en contradiction avec les règles de l'arche, dès lors qu'un herbivore est détecté par le test <report>.
Si un nœud est sélectionné, il peut, contrairement à ce qui était possible jusqu'à présent, être vérifié à l'aide de plusieurs tests. Nous élargissons la règle dans notre exemple. Désormais les carnivores doivent être tenus éloignés des herbivores, mais également d'autres carnivores plus faibles. Les carnivores pourraient également s'attaquer aux plus faibles! La force d'un animal est déterminée grâce à son poids. La règle est qu'un carnivore ne peut être maximum que deux fois plus lourd que le plus léger des animaux partageant son compartiment:

<pattern> 
  <rule context="arc:animal[@carnivore='oui']"> 
    <report test="parent::*/arc:animal[@carnivore='non']"> 
      Il y a des carnivores et des herbivores dans un même compartiment. 
      Les animaux ne sont pas une ressource alimentaire! 
    </report> 
    <report test="parent::*/arc:animal/arc:poids &lt; (arc:poids div 2)"> 
      Noé, ce carnivore est trop fort (lourd) pour celui qui partage son
      compartiment. Il pourrait l'utiliser comme ressource alimentaire.
    </report> 
  </rule> 
</pattern>  

L'expression XPath vérifie si un élément frère (parent::*/arche:animal) a un élément fils <poids> (/arche:poids), dont la valeur est plus petite (&lt;) que la moitié de la valeur de l'élément <poids> du nœud de contexte (arche:poids div 2).
Si le nœud de contexte doit être contrôlé plusieurs fois, les éléments testés seront alignés l'un après l'autre. À cette occasion les éléments <assert> et <report> seront mélangés librement. Lorsque que le nœud de contexte approprié sera trouvé dans l'instance, il sera examiné à l'aide de tous les tests. L'ordre n'a pas d'importance, sauf dans la présentation du rapport d'erreur.

Remarque à l'attention des experts XSLT:

Lors de la transformation Skeleton en XSLT, les tests sont convertis en structure "if" ou en "choose-when-otherwise". Le test report:

<report test="parent::*/arc:animal[@carnivore='non']">Il y a des carnivores et des herbivores dans un même compartiment. Les animaux ne sont pas des ressources alimentaires!</report>

se transforme en:

<xsl:if test="arc:animal[@carnivore='oui']">
   […]
   <!-- Convertion du message d'erreur dans SVRL -->
   […]
</xsl:if>

Au contraire, un test assert a recours à l'élément <xsl:choose>:

<assert test="not(parent::*/arc:animal[@carnivore='non'])">Il y a des carnivores et des herbivores dans un même compartiment. Les animaux ne sont pas des ressources alimentaires!</assert>

se transforme en:

<xsl:choose> 
   <xsl:when test="not(parent::*/arc:animal[@carnivore='non'])"/> 
   <xsl:otherwise> 
      […] 
      <!-- Convertion du message d'erreur dans SVRL --> 
      […] 
   </xsl:otherwise> 
</xsl:choose>

Copyright © dpunkt.verlag GmbH 2011
Vous pouvez imprimer cette version en ligne pour un usage privé. Par ailleurs, ce chapitre du livre "Schematron - Effiziente Business Rules für XML-Dokumente" est soumis aux mêmes clauses prévues pour la version papier : L'intégralité de l'oeuvre est protégée par les droits d'auteurs. Tous droits réservés y compris la copie, la traduction, la reproduction sur microfilm, tout comme l'enregistrement et le traitement dans des systèmes électroniques.

dpunkt.verlag GmbH, Ringstraße 19B, 69115 Heidelberg, téléphone + 49 (0)6221-14830, fax + 49 (0)6221-148399, hallo(at)dpunkt.de