Martin Puppe


XML Schema, Keys und Namespaces

Bei der Erstellung von Schemas mit einem Ziel-Namensraum kann man leicht in eine kleine, facepalm-induzierende Falle tappen, die mich heute einiges an Nerven gekostet hat. Um das Problem zu erläutern sehen wir uns am besten ein Beispiel an.

Ohne Namensraum

Beispielinstanz ohne Namensraum (instance.xml) Rohdatei
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="schema.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Das Dokument besteht aus einem Wurzelement foo und Kindelementen bar. Die bar-Elemente haben ein Attribut id. Nun wollen wir erzwingen, dass id ein Schlüssel ist. Die Instanz wäre dann nicht valide. Das entsprechende XML-Schema muss dazu etwa so aussehen:

Schema ohne Ziel-Namensraum (schema.xsd) Rohdatei
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Man beachte hier vor allem die Zeilen 15 bis 17, in denen festgelegt wird, dass innerhalb des foo-Elements die bar-Elemente eindeutig durch das Schlüssel-Attribut id identifizierbar sein müssen.

Mit Namensraum

Hier folgt eine Beispiel-Instanz, für die http://foo.bar als Namensraum festgelegt wurde.

Beispielinstanz mit Namensraum (instance_namespace.xml) Rohdatei
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns="http://foo.bar"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://foo.bar schema_falsch.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Man könnte nun meinen, dass es ausreicht, den Namensraum entsprechend auch im Schema anzugeben. Das wurde im folgenden Beispiel in Zeile 3 und 4 getan.

Falsches Schema mit Zielnamensraum (schema_falsch.xsd) Rohdatei
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    targetNamespace="http://foo.bar"
    elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Das Problem ist aber, dass die Beispiel-Instanz nicht als invalide erkannt wird, sondern klaglos akzeptiert wird. Warum ist das so? Das Problem ist der XPath-Ausdruck in Zeile 17. Damit werden alle bar-Elemente selektiert und sichergestellt, dass diese eine eindeutige id haben. Unser Ziel-Namensraum ist allerdings http://foo.bar. Das heißt wir wollen nicht bar-Elemente selektieren, sondern http://foo.bar:bar-Elemente. Das richtige Schema sieht deshalb so aus:

Richtiges Schema mit Zielnamensraum (schema_richtig.xsd) Rohdatei
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    xmlns:foobar="http://foo.bar"
    targetNamespace="http://foo.bar"
    elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="foobar:bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Zu beachten sind die Zeilen 4 und 19. In Zeile 4 wird definiert, dass das Kürzel foobar für den Namensraum http://foo.bar steht und in Zeile 18 wird dieses Kürzel verwendet, um den korrekten XPath-Ausdruck zu konstruieren.