XML Validierung - wozu? / XML Schema / XML Schema 1.1

XML Schema 1.1

XML Schema 1.1

XML Schema 1.1 bietet mit erweiterten Konzepten für Assertions (assert für komplexe Datentypen, assertion für simple Datentypen), bedingten Typisierungen, schemaweiten Attributen, openContent bzw. defaultOpenContent leistungsfähige Unterstützung bei der Datenvalidierung.

XML Schema 1.1

Versionierung

Für die XML Schema - Versionierung gibt es einen speziellen Namespace, der auf eine Webseite des W3C verweist. Über diese Webseite ist auch ein "XMLSchema-versioning.xsd" erreichbar.


xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" 
vc:minVersion="1.1"
https://www.w3.org/2007/XMLSchema-versioning/XMLSchema-versioning.xsd

"XMLSchema-versioning.xsd" dient lediglich zur Dokumentation, nicht aber dazu, irgend etwas zu validieren. Gleichwohl sind die Attribute "minVersion" bzw. "maxVersion" definiert, die beide vom Typ xs:decimal sein müssen. Dort ist auch der konventionelle Prefix-Name "vc" empfohlen.

Leistungsfähige XML Schema-Editoren passen denn auch das Root-Element des XML Schema für die erweiterte Version entsprechend an:


<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified"
    vc:minVersion="1.1" 
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning">
</xs:schema>

Obwohl die XML Schema-Versionierungen nicht zu Validierungszwecken gedacht sind, prüfen die Prozessoren im Rahmen eines Preprocessing, ob die im XML Schema definierten Elemente den Vorgaben "minVersion" und "maxVersion" entsprechen. Andernfalls werden sie (wie <xs:complexType name="mycomplexType" vc:maxVersion="1.0" vc:minVersion="1.0" >) nicht beachtet.


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified"
    vc:minVersion="1.1" 
    vc:maxVersion="1.2"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning">
    <xs:simpleType 
        name="mysimpletype" 
        vc:minVersion="1.1" 
        vc:maxVersion="1.2">
        <xs:restriction base="xs:string">
            <xs:enumeration value="Hallo"/>
            <xs:enumeration value="Moin"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:complexType 
        name="mycomplexType" 
        vc:maxVersion="1.2" 
        vc:minVersion="1.1">
        <xs:all>
            <xs:element 
                name="mychildnode" 
                type="mysimpletype"/>
        </xs:all>
    </xs:complexType>
    <xs:complexType 
        name="mycomplexType" 
        vc:maxVersion="1.0" 
        vc:minVersion="1.0" >
        <xs:all>
            <xs:element 
                name="mychildnode2" 
                type="mysimpletype"/>
        </xs:all>
    </xs:complexType>
    <xs:element name="myelement" type="mycomplexType"/>
</xs:schema>

Leistungsfähige Editoren geben beim Editieren von XML Schema entsprechende Warn- und (bei Validierung) auch Fehlermeldungen heraus.


<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified"
    vc:minVersion="1.2" 
    vc:maxVersion="1.2"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning">
</xs:schema>

... würde von vornherein nicht beachtet:


Die Schema-Deklaration wird nicht beachtet, 
da der Wert des Attributs "minVersion" größer 
ist als XML Schema 1.1 (minVersion="1.2").

Assertions

Assertions dienen der verfeinerten Werte-Kontrolle. Ein einfaches Beispiel: "mysimpletype" definiert einen Integer, der nicht "0" sein darf.


  <xs:simpleType name="mysimpletype">
    <xs:restriction base="xs:integer">
      <xs:assertion test="$value != 0"/>
    </xs:restriction>
  </xs:simpleType>

Betrachten wir den XML Schema - 1.0 - Fall, daß ein Integerwert > 0 und < 100 definiert werden soll. Hier greift xs:restriction auf xs:maxExclusive und xs:minExclusive zurück.


  <xs:simpleType name="minexclmaxecl">
    <xs:restriction base="xs:integer">
      <xs:maxExclusive value="100"/>
      <xs:minExclusive value="0"/>
    </xs:restriction>
  </xs:simpleType>

In XML Schema 1.1 überprüft "ungeraderTyp_0_100" zudem mittels xs:assertion, ob der Integer-Wert eine ungerade Zahl zwischen 0 und 100 ist.


  <xs:simpleType name="ungeraderTyp_0_100">
    <xs:restriction base="xs:integer">      
      <xs:assertion test="$value &gt; 0 
                and $value &lt; 100 
                and $value mod 2 = 1" />
    </xs:restriction>
  </xs:simpleType>

"ungeraderTyp_0_100" läßt sich alternativ auch so definieren:


  <xs:simpleType name="ungeraderTyp_0_100">
    <xs:restriction base="xs:integer">      
      <xs:assertion test="$value &gt; 0"/>
      <xs:assertion test="$value &lt; 100"/>
      <xs:assertion test="$value mod 2 = 1" />
    </xs:restriction>
  </xs:simpleType>

Suboptimal, aber möglich wäre, den Wert als xs:string zu casten und dessen Stringlänge zu ermitteln. Ich führe das hier an, um auf die flexible Verwendbarkeit der XPath-Funktionen hinzuweisen.


  <xs:simpleType name="minexclmaxecl">
    <xs:restriction base="xs:integer">
      <xs:assertion 
          test="string-length(xs:string($value)) &lt; 3"/>
      <xs:assertion 
          test="string-length(xs:string($value)) &gt; 1"/>
    </xs:restriction>
  </xs:simpleType>

Auch zeitliche Beschränkungen sind möglich; beispielsweise soll geprüft werden, ob ein Ereignis später als 01.12.2017 12 Uhr stattfindet.


  <xs:simpleType name="ST_Fruehestens" >
    <xs:restriction base="xs:dateTime">
      <xs:assertion 
          test="$value &gt; xs:dateTime('2017-12-01T12:00:00.0')"/>
    </xs:restriction>
  </xs:simpleType>

Anstelle einer xs:enumeration ist auch die Arbeit mit einer entsprechenden Assertion möglich.


  <xs:simpleType name="Arbeitstag">
    <xs:restriction base="xs:string">
      <xs:assertion test="$value = ('MO', 'DI', 'MI', 'DO', 'FR')"/>
    </xs:restriction>
  </xs:simpleType>

Statt aufwändiger Deklaration von xs:pattern können wir das auch der "matches"-Funktion überlassen, die in einer Assertion aufgerufen wird. Die Definition regulärer Ausdrücke bleibt uns indes nicht erspart.


  <xs:element name="myelement">    
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:assertion 
            test="matches($value, '[A-Z]{1}[a-z]{1,19}')"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>

Das gilt analog auch dür die Anwendung der "tokenize"-Funktion in Verbindung mit "matches". Der folgende Aufruf läßt eine beliebige Anzahl von Werten zu, die jeweils durch einen Blank getrennt, dem vorher definierten regulären Ausdruck entsprechen: nur Buchstaben, das erste Zeichen groß, die restlichen klein.


  <xs:element name="myelement">    
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:assertion 
            test="every $m in 
                  tokenize(xs:string($value), ' ') 
                  satisfies (
                     matches(xs:string($m), 
                             '[A-Z]{1}[a-z]{1,}'))"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>

Also auch auf diese Weise können wir die Wochentage überprüfen:


<myelement>Montag Dienstag Mittwoch Donnerstag Freitag</myelement>

Interessant ist hier auch die Arbeit mit der translate-Funktion. Wenn wir sicherstellen wollen, daß in einer Zeichenkette auch Zahlen auftreten können, so können wir sämtliche Nicht-Zahlen löschen und prüfen, ob der Rest als Integer gecastet werden kann.


  <xs:element name="myelement">    
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:assertion 
            test="translate($value, 
                    translate($value, '0123456789', '')
                  , '') 
                  castable as xs:integer"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>

Das folgende Beispiel definiert ein Integer-Array mit folgenden Einschränkungen:


  <xs:simpleType name="Integerarray">
    <xs:restriction>
      <xs:simpleType>
        <xs:list itemType="xs:integer"/>        
      </xs:simpleType>
      <xs:assertion test="count($value) &lt; 5"/>
      <xs:assertion test="$value[1] = max($value)"/>
      <xs:assertion test="sum($value) = 9"/>
      <xs:assertion test="every $m in $value satisfies $m &gt; 0"/>
    </xs:restriction>
  </xs:simpleType>

Assertions übernehmen sehr flexibel die Existenzkontrolle für bestimmte Elemente bzw. Attribute. Nehmen wir an, wir wollten ein Element "myelement" entweder mit dem Attribut "a" oder "b" ausstatten, aber nicht mit beiden.

<xs:assert test="exists(@a | @b)"/> würde zulassen, daß beide Attribute vorhanden sind. Daher wird die Prüfung notwendig, ob


  <xs:element name="myelement">
    <xs:complexType>
      <xs:attribute name="a" use="optional"/>
      <xs:attribute name="b" use="optional"/>
      <xs:assert 
          test="if (
                      (exists(@a) and not(exists(@b))) 
                    or 
                      (exists(@b) and not(exists(@a)))) 
                then (true()) 
                else (false())"/>
    </xs:complexType>
  </xs:element>

Das folgende Beispiel "GeometrFigur" stelle eine geometrische Figur dar, die die optionalen Grundelemente "Umfang" und "Flaeche" vorgibt. Zur späteren Verwendung werden noch die beiden Attribute "myTYPE" (required) und "maxwert" (optional) mitgegeben.


  <xs:simpleType name="ST_Massangabe">
    <xs:restriction base="xs:decimal">
      <xs:assertion test="$value &gt; 0"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="GeometrFigur">
    <xs:sequence>
      <xs:element name="Flaeche" type="ST_Massangabe" minOccurs="0"/>
      <xs:element name="Umfang" type="ST_Massangabe" minOccurs="0"/>
    </xs:sequence>
    <xs:attribute name="maxwert" type="ST_Massangabe" use="optional"/>
    <xs:attribute name="myTYPE" use="required">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:enumeration value="kreis"/>
          <xs:enumeration value="rechteck"/>
          <xs:enumeration value="abstrakt"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>

Der "RechteckType" erweitert "GeometrFigur" um die Childnodes "Laenge" und "Breite". Zusätzlich wird kontrolliert,


  <xs:complexType name="RechteckType">
    <xs:complexContent>
      <xs:extension base="GeometrFigur">
        <xs:sequence>
          <xs:element name="Laenge" type="ST_Massangabe"/>
          <xs:element name="Breite" type="ST_Massangabe"/>
        </xs:sequence>        
        <xs:assert test="Laenge &gt; Breite"/>
        <xs:assert 
            test="if(exists(@maxwert)) 
                  then (Laenge &lt;= @maxwert) 
                  else (true())"/>
        <xs:assert 
            test="if(exists(Flaeche)) 
                  then (Flaeche = Laenge * Breite) 
                  else (true())"/>    
        <xs:assert 
            test="if(exists(Umfang)) 
                  then (Umfang = (Laenge + Breite) * 2) 
                  else (true())"/> 
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="myelement" type="RechteckType"/>

Somit würde dieses XML Schema beispielsweise folgende Werte definieren:


<myelement maxwert="20" myTYPE="rechteck">
 <Flaeche>6</Flaeche>
 <Umfang>10</Umfang>
 <Laenge>3</Laenge>
 <Breite>2</Breite>
</myelement>

Ein ähnlicher Ansatz wie vorher existiert auch beim "KreisTyp", der den Radius vorgibt und die abhängigen (optionalen) Größen "Flaeche", "Umfang" und "Durchmesser" kontrolliert. Auch die Abhängigkeit des "Radius" vom optionalen Attribut "maxwert" ist gegeben.


  <xs:complexType name="KreisTyp">
    <xs:complexContent>
      <xs:extension base="GeometrFigur">
        <xs:sequence>
          <xs:element 
              name="Radius" 
              type="ST_Massangabe"/>
          <xs:element 
              name="Durchmesser" 
              type="ST_Massangabe" 
              minOccurs="0"/>
        </xs:sequence>
        <xs:assert 
            test="if(exists(@maxwert)) 
                  then (Radius &lt;= @maxwert) 
                  else (true())"/>
        <xs:assert
            test="if (exists(Umfang)) 
                  then (Umfang = Radius * 2 * 3.14159)
                  else (true())"/>
        <xs:assert
            test="if (exists(Flaeche)) 
                  then (Flaeche = Radius * Radius * 3.14159)
                  else (true())"/>
        <xs:assert
          test="if (exists(Durchmesser)) 
                then (Durchmesser = Radius * 2)
                else (true())"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
 <xs:element name="myelement" type="KreisTyp"/>

Das folgende XML Dokument wäre damit vollauf valide:


<myelement maxwert="3" myTYPE="kreis">
 <Radius>3</Radius>
 <Durchmesser>6</Durchmesser>
 <Flaeche>28.27431</Flaeche>
 <Umfang>18.84954</Umfang>
</myelement>

Alternativen

Nun hängt es von dem Attribut "myTYPE" ab, ob "myelement" vom "KreisTyp", "RechteckTyp" oder "GeometrFigur" ist.


 <xs:element name="myelement">
    <xs:alternative test="@myTYPE eq 'kreis'" type="KreisTyp"/>
    <xs:alternative test="@myTYPE eq 'rechteck'" type="RechteckTyp"/>  
    <xs:alternative type="GeometrFigur"/>
  </xs:element>

xs:override

Wenn die TargetNamespaces identisch sind, kann in einem zweiten XML Schema Dokument ein xs:complexType, der in einem anderen XML Schema definiert wurde, verändert werden. So können die Felder der xs:complexType "GeometrFigur" auch umbenannt / überschrieben werden:


  <xs:override schemaLocation="XSDTest_1_1.xsd">
    <xs:complexType name="GeometrFigur">
      <xs:sequence>
        <xs:element name="__flaeche" 
            type="ST_Massangabe" minOccurs="0" />
        <xs:element name="__umfang" 
            type="ST_Massangabe" minOccurs="0"/>
      </xs:sequence>
    </xs:complexType>
  </xs:override>

... so daß mit den neuen Elementnamen gearbeitet werden kann.


 <__flaeche>7</__flaeche>
 <__umfang>5</__umfang>

Das "redefine" von XML Schema 1.0 ist veraltet.


<xs:redefine schemaLocation="XSDTest_1_1.xsd">

Beispiel mit Namespaces

Nehmen wir noch ein etwas komplexeres Beispiel mit mehreren XML Schema Dokumenten, die jeweils über einen eigenen Namespace verfügen und sich gegenseitig importieren. Im ersten XML Schema "SomeSimpleTypes_With_NS.xsd" wird lediglich ein simpleType mit einem regulären Ausdruck definiert.


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified"
    targetNamespace="wilfried-grupe.de/someSimpleTypes"
    xmlns:myST="wilfried-grupe.de/someSimpleTypes"
    version="1.1"
    vc:minVersion="1.1" 
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"> 
    <xs:simpleType name="stringmax20">        
        <xs:restriction base="xs:string">
            <xs:pattern value="[A-Z]{1}[a-z]{1,19}"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>

Im zweiten XML Schema "Mensch_someelements_withNS.xsd", das "SomeSimpleTypes_With_NS.xsd" importiert, seien nur einige Attribute und Elemente sowie deren Typen und Namespaces aufgelistet.


<xs:schema 
    targetNamespace="wilfried-grupe.de/someelements"
    xmlns:ns2="wilfried-grupe.de/someelements"
    xmlns:myST="wilfried-grupe.de/someSimpleTypes"
    version="1.1"
    elementFormDefault="qualified"
    attributeFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xpathDefaultNamespace="wilfried-grupe.de/someelements"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" 
    vc:minVersion="1.1">
  <xs:import namespace="wilfried-grupe.de/someSimpleTypes" 
             schemaLocation="SomeSimpleTypes_With_NS.xsd"/>
  <xs:attribute name="HOBBY" type="myST:stringmax20"/>
  <xs:attribute name="ALTER1" type="xs:nonNegativeInteger"/>
  <xs:attribute name="ALTER2" type="xs:nonNegativeInteger"/>
  <xs:element name="vorname" type="myST:stringmax20"/>
  <xs:element name="nachname" type="myST:stringmax20"/>
  <xs:element name="NN" type="myST:stringmax20"/>
  <xs:element name="VN" type="myST:stringmax20"/>
  <xs:element name="age" type="xs:nonNegativeInteger"/>
</xs:schema>

Das dritte XML Schema "Mensch_withNS.xsd" importiert das zweite (und damit auch das erste) und definiert mit dessen Namespace und den dort definierten Elementen / Attributen die Zielstruktur "MENSCH". Zudem überprüft das XML Schema Dokument durch mehrere xs:assert das Vorkommen einiger Elemente bzw. Attribute.

pic/XSD_with_imported_Namespaces.jpg


<xs:schema 
  xmlns:ns2="wilfried-grupe.de/someelements"
  targetNamespace="wilfried-grupe.de/Beispiel" 
  xmlns:ns3="wilfried-grupe.de/Beispiel"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  vc:minVersion="1.1"
  xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" 
  version="1.1"
  elementFormDefault="qualified" 
  attributeFormDefault="qualified"
  xpathDefaultNamespace="wilfried-grupe.de/Beispiel">
  <xs:import namespace="wilfried-grupe.de/someelements" 
      schemaLocation="Mensch_someelements_withNS.xsd"/>
  <xs:complexType name="MENSCH_TYP">
    <xs:all>
      <xs:element ref="ns2:nachname" minOccurs="0" maxOccurs="1"/>
      <xs:element ref="ns2:NN" minOccurs="0" maxOccurs="1"/>
      <xs:element ref="ns2:vorname" minOccurs="0" maxOccurs="1"/>
      <xs:element ref="ns2:VN" minOccurs="0" maxOccurs="1"/>
    </xs:all>
    <xs:attribute ref="ns2:HOBBY" use="required"/>
    <xs:attribute ref="ns2:ALTER1" use="optional"/>
    <xs:attribute ref="ns2:ALTER2" use="optional"/>
    <xs:assert test="
        if (
        (exists(@ns2:ALTER1) and not(exists(@ns2:ALTER2)))
        or
        (exists(@ns2:ALTER2) and not(exists(@ns2:ALTER1))))
        then (true())
        else (false())" />
    <xs:assert test="
      if (
      (exists(ns2:nachname) and not(exists(ns2:NN)))
      or
      (exists(ns2:NN) and not(exists(ns2:nachname))))
      then (true())
      else (false())" />
    <xs:assert test="
      if (
      (exists(ns2:vorname) and not(exists(ns2:VN)))
      or
      (exists(ns2:VN) and not(exists(ns2:vorname))))
      then (true())
      else (false())" />
  </xs:complexType>
  <xs:element name="MENSCH" type="ns3:MENSCH_TYP"/>
</xs:schema>

pic/XSD_with_Namespaces.jpg

Diese drei XML Schemata zusammen definieren ein XML Dokument, das beispielsweise so ...


<ns3:MENSCH xmlns:ns2="wilfried-grupe.de/someelements"
 xmlns:ns3="wilfried-grupe.de/Beispiel"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="wilfried-grupe.de/Beispiel file:/C:/wg/Mensch_withNS.xsd" 
 ns2:HOBBY="XML" 
 ns2:ALTER1="22">
 <ns2:NN>Holzflos</ns2:NN>
 <ns2:vorname>Hugo</ns2:vorname>
</ns3:MENSCH>

... oder auch so aussehen könnte (ohne xsi:schemaLocation):


<ns3:MENSCH xmlns:ns2="wilfried-grupe.de/someelements"
 xmlns:ns3="wilfried-grupe.de/Beispiel"
 ns2:HOBBY="Java" 
 ns2:ALTER1="33">
 <ns2:nachname>Saftlos</ns2:nachname>
 <ns2:VN>Sabine</ns2:VN>
</ns3:MENSCH>

wg / 16. Dezember 2017



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf:

Vorname
Nachname
Mailadresse







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: info2018@wilfried-grupe.de

www.wilfried-grupe.de/XML_Schema_1_1.html