XML-Validierung: Wozu? / 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.

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, irgendetwas zu validieren. Gleichwohl sind die Attribute minVersion bzw. maxVersion definiert, die beide vom Typ xs:decimal sein müssen. Dort ist auch der konventionelle Präfix-Name vc empfohlen.

Leistungsfähige XML-Schema-Editoren passen denn auch das Root-Element des XML-Schemas 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 Preprocessings, 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 xs: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 Sie den Fall, dass ein Integerwert > 0 und < 100 definiert werden soll. In XML-Schema 1.0 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ässt 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 aufwendiger Deklaration von xs:pattern können Sie das auch der -Funktion überlassen, die in einer Assertion aufgerufen wird. Die Definition regulärer Ausdrücke bleibt Ihnen 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 für die Anwendung der -Funktion in Verbindung mit matches. Der folgende Aufruf lässt eine beliebige Anzahl von Werten zu, die jeweils durch ein 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 Sie die Wochentage überprüfen:


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

Interessant ist hier auch die Arbeit mit der -Funktion. Wenn Sie sicherstellen wollen, dass in einer Zeichenkette auch Zahlen auftreten können, so können Sie sämtliche Nicht-Zahlen löschen und prüfen, ob der Rest als xs: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. Angenommen, Sie 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, dass 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 Target-Namespaces 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>

... sodass 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 Sie 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 / 12. April 2018



Fragen? Anmerkungen? Tips?

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/XML_Schema_1_1.html