XML testen / Detailtests mit Schematron

Detailtests mit Schematron

Detailtests mit Schematron

➪ Während XML-Schema im Kern die Datenstruktur von Elementen und Attributen sowie deren Datentypen und Namespaces festlegt, beruht Schematron auf einer Detailprüfung.

Auf dieser Seite:

So kann Schematron über einen klar definierten XPath-Pfad eine Einzeladresse (z.B. ein Attribut) ansprechen und überprüfen, ob die darin stehende Information dem Vorgabewert entspricht. Ist das nicht der Fall, erhalten Sie eine Fehlermeldung.

Angenommen, Sie möchten ein XML-Dokument daraufhin prüfen, ob die Inhalte bestimmten Vorgaben entsprechen. Etwa, dass jedes Mensch-Element einen Childnode vorname hat, dass jeder Childnode name am Textende den Inhalt los aufweist, dass jedes Gehalt-Element als xs:decimal gecastet werden kann und sämtliche Gehalt-Nodes einen Wert größer als die Summe anderer Nodes haben.

Eine solche Prüfung ist nicht nur in XSLT hervorragend möglich, sondern ebenso mit Schematron. Wie XML-Schema, ist auch Schematron ein XML-Dokument mit einem speziellen Namespace. Je nach verwendetem Prozessor ist der Namespace http://www.w3.org/2001/XMLSchema für XML-Schema bereits integriert, und die Verwendung der XPath-Funktionen bietet weitere Unterstützung.


<sch:schema 
  xmlns:sch="http://purl.oclc.org/dsdl/schematron" 
  queryBinding="xslt2">
  <sch:pattern>    
    <sch:rule context="/Orte/Ort/Mensch">
      <sch:assert test="exists(vorname)">
           ERROR: vorname ist nicht vorhanden
      </sch:assert>
      <sch:assert 
           test="matches(name/text(), 'los$')">
           name entspricht nicht dem Pattern
           real content: <sch:value-of select="name/text()"/>
      </sch:assert>
      <sch:assert 
           test="Gehalt castable as xs:decimal">
           Gehalt ist kein decimal-Typ 
           real content: <sch:value-of select="Gehalt/text()"/>
      </sch:assert>
      <sch:assert 
           test="Gehalt &gt; sum(Kauf/Gesamt)">
           Gehalt ist kleiner als die Summe aller Einkäufe bei: 
           <sch:value-of select="concat(vorname, ' ', 
                                        name, ': ', 
                                        Gehalt, ' &lt; ', 
                                        sum(Kauf/Gesamt))"/>
      </sch:assert>
    </sch:rule>
  </sch:pattern>
</sch:schema>

Validieren Sie ein XML-Dokument mit dieser Schematron-Datei, so erhalten Sie möglicherweise diese Fehlermeldungen:


Programmname: ISO Schematron
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Hugo Holzflos: 234.56 < 3563.1400000000003
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Stefan Sagblos: 321.45 < 2753.9399999999996
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Siggi Sorglos: 987.58 < 2610.96
Fehlerlevel: error
Beschreibung: 
name entspricht nicht dem Pattern real content: Rhodos
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Rudi Rhodos: 333.33 < 740.6999999999999
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Susi Schlaflos: 321 < 808.13
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Lotte Rielos: 456 < 1555.98
Fehlerlevel: error
Beschreibung: 
Gehalt ist kleiner als die Summe aller Einkäufe bei: 
Liane Leinenlos: 135 < 964.0699999999999

Der Wert solcher automatisierter Testverfahren zeigt sich sofort, wenn die Tests wiederholt durchgeführt werden sollen. Stellen Sie sich vor, Sie müßten eine Datei mit nur 100 Detailinformationen durch optische Kontrolle testen. Irgendwo finden Sie einen Fehler; Sie ändern die Implementierungslogik und überprüfen sämtliche Ergebnisse erneut, von Anfang bis Ende. Bei jedem gefundenen Fehler dasselbe Spiel: Korrektur der Programmierung, neue Prüfung. Ich vermute, Sie werden bald die Geduld verlieren und nach einem automatisierten Testverfahren rufen, das Ihnen die lästige und stupide Detailprüfung abnimmt, eines, das "das" von selbst macht.

Die vorher dargestellte Schematron-Prüfung bezog sich auf sämtliche Mensch-Nodes. Sie können die XPath-Pfade aber auch sehr präzise auf einzelne Elemente setzen und deren Textinhalte prüfen.


<sch:schema queryBinding="xslt2" 
   xmlns:sch="http://purl.oclc.org/dsdl/schematron">
 <sch:pattern>
  <sch:rule context="/">
   <sch:assert test="exists(Root)">
     ERROR: Root[1] DOES NOT EXIST</sch:assert>
   <sch:assert test="exists(Root/A[1])">
     ERROR: A[1] DOES NOT EXIST</sch:assert>
   <!-- stell sicher, dass es kein Root/A[2] gibt -->
   <sch:assert test="not(exists(Root/A[2]))">
     ERROR: A[2] EXISTS: more than one occurance of /Root/A
   </sch:assert>
  </sch:rule>
  <!-- stellt sicher, dass der Wert in 
       /Root/A[1]/Person[5]/NN[1] 'Sinnlos' ist -->
  <sch:rule context="/Root/A[1]/Person[5]/NN[1]">
    <sch:assert test="./text()='Sinnlos'">
     WRONG VALUE IN /Root/A[1]/Person[5]/NN[1]; 
     expected value: Sinnlos | 
     real content: <sch:value-of select="./text()"/>
    </sch:assert>
    <sch:assert test="./text()!='SINNLOS'">
    WRONG VALUE IN /Root/A[1]/Person[5]/NN[1]; 
    value must not be: SINNLOS | 
    real content: <sch:value-of select="./text()"/>
    </sch:assert>
  </sch:rule>
 </sch:pattern>
</sch:schema>

Automatisiertes Erzeugen von Schematron-Dateien

Nun ist die Generierung von Schematron-Dateien für komplexe Ergebnisdokumente durchaus mit einigem Aufwand verbunden. Eine beträchtliche Hilfe kann darin bestehen, das erwartete XML-Dokument (das nach Durchlaufen des gesamten XSLT-Prozesses generiert werden soll) (ggf. nach Korrektur) zu fixieren und auf dieser Basis die Schematron-Datei weitgehend automatisch zu generieren. Dabei kann folgendes XSL-Stylesheet hilfreich sein (Die Logik generiert Schematron-Dateien für Elemente mit Textinhalt; für die Überprüfung von Attributen wären zusätzliche Schritte erforderlich):


<xsl:stylesheet 
     version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/"> 
    <sch:schema queryBinding="xslt2"
      xmlns:sqf="http://www.schematron-quickfix.com/validator/process"
      xmlns:sch="http://purl.oclc.org/dsdl/schematron"> 
      <sch:pattern> 
        <xsl:for-each select="/*/descendant-or-self::*
                                 [count(child::*) =0]/parent::*">          
          <xsl:variable name="vxpath">
            <xsl:text>/</xsl:text>
            <xsl:for-each select="ancestor-or-self::*">
              <xsl:value-of select="name()"/>
              <xsl:if test="../*[2]">
                <xsl:text>[</xsl:text>
                <xsl:value-of select="1+count(
                     preceding-sibling::*[name(.)=name(current())])"/>
                <xsl:text>]</xsl:text>
              </xsl:if>
              <xsl:choose>
                <xsl:when test="position() != last()">
                  <xsl:text>/</xsl:text>
                </xsl:when>
              </xsl:choose>
            </xsl:for-each>
          </xsl:variable>  
          <sch:rule>  
            <xsl:attribute name="context">
              <xsl:value-of select="$vxpath"/>
            </xsl:attribute> 
            <xsl:for-each select="child::*[count(child::*) =0]">
              <xsl:variable name="vexpectedvalue">
                <xsl:value-of select="./text()"/>
              </xsl:variable>
              <sch:assert>
                <xsl:attribute name="test">                  
                  <xsl:text>exists(</xsl:text>
                  <xsl:value-of select="name()"/>
                  <xsl:text>)</xsl:text>
                </xsl:attribute>
                <xsl:value-of select="$vxpath"/>
                <xsl:text>/</xsl:text>
                <xsl:value-of select="name()"/>
                <xsl:text> DOES NOT EXIST</xsl:text>
              </sch:assert>
              <sch:assert>
              <xsl:attribute name="test">
                <xsl:value-of select="name()"/>
                <xsl:text>/text()='</xsl:text>
                <xsl:value-of select="./text()"/>
                <xsl:text>'</xsl:text>
              </xsl:attribute>
              <xsl:text> WRONG VALUE IN </xsl:text>
              <xsl:value-of select="$vxpath"/>
              <xsl:text>/</xsl:text>
              <xsl:value-of select="name()"/>
              <xsl:text> Expected value: </xsl:text>
              <xsl:value-of select="$vexpectedvalue"/>                
              <xsl:text> | real content: </xsl:text>  
              <sch:value-of select="./text()"/>
            </sch:assert>  
            </xsl:for-each>            
          </sch:rule> 
        </xsl:for-each> 
      </sch:pattern>
    </sch:schema>
  </xsl:template>
</xsl:stylesheet>

Neben zahlreichen anderen klar adressierbaren XPath-Statements und deren erwartetem Inhalt wird damit folgender Output generiert (stark gekürzte Darstellung), der anschließend in einem geeigneten Schematron-Validierungstool (basierend auf dem realen XSL-Ergebnis) aufgerufen wird; damit werden Abweichungen zwischen SOLL und IST schnell reportet.


<sch:schema 
   xmlns:sch="http://purl.oclc.org/dsdl/schematron"
   queryBinding="xslt2">
  <sch:pattern>
   <sch:rule 
      context="/Orte/Ort[1]/Mensch[1]/Kauf[1]">
     <sch:assert 
       test="exists(idMensch)">
       /Orte/Ort[1]/Mensch[1]/Kauf[1]/idMensch DOES NOT EXIST
     </sch:assert>
     <sch:assert 
       test="idMensch/text()='1'"> 
       WRONG VALUE IN /Orte/Ort[1]/Mensch[1]/Kauf[1]/idMensch 
       Expected value: 1 | real content: 
       <sch:value-of select="./text()"/>
     </sch:assert>
   </sch:rule>
  </sch:pattern>
</sch:schema>

wg / 5. April 2018



Fragen? Anmerkungen? Tipps?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/Schematron.html