Reputation: 11
I am trying to validate a XML instance that depends on another instance (from a different namespace), and it has a keyref to a key in that namespace. When I try to validate the instance, it produces an error that says the key is out of scope.
These are my XSDs:
test1.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="test1" xmlns="test1">
<xs:complexType name="Host">
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
<xs:element name="root">
<xs:complexType>
<xs:all>
<xs:element name="hosts">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="host" type="Host"
/>
</xs:sequence>
</xs:complexType>
<xs:key name="Host-PK">
<xs:selector xpath="host"/>
<xs:field xpath="@id"/>
</xs:key>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>
</xs:schema>
test2.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="test2" xmlns="test2" xmlns:t1="test1">
<xs:import namespace="test1" schemaLocation="test1.xsd"/>
<xs:element name="root">
<xs:complexType>
<xs:all>
<xs:element name="server">
<xs:complexType>
<xs:attribute name="host" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:all>
</xs:complexType>
<xs:keyref name="Host-FK" refer="t1:Host-PK">
<xs:selector xpath="server"/>
<xs:field xpath="@host"/>
</xs:keyref>
</xs:element>
</xs:schema>
And my instances:
test1.xml
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="test1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test1 test1.xsd">
<hosts>
<host id="ABC"/>
<host id="DEF"/>
</hosts>
</root>
test2.xml
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:t1="test1" xmlns="test2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test2 test2.xsd">
<server host="ABC"/>
</root>
The server host attribute is a key reference to the host ids.
During schema validation the test2.xml file raised the error below:
Error: [Xerces] Identity Constraint error: the keyref identity constraint "Host-FK" refers to a key or unique that is out of scope.
How can I fix that?
And how can I reference test1.xml instance from test2.xml?
Upvotes: 1
Views: 809
Reputation: 23637
I'm assuming you can change both XSDs, and that you are using XSD 1.0.
In the first XSD, you will need to qualify your XPath elements, since unprefixed elements belong to no namespace. As is your key will not work. You can verify this adding a duplicate ID to test1
:
<root xmlns="test1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test1 test1.xsd">
<hosts>
<host id="ABC"/>
<host id="DEF"/>
<host id="DEF"/>
</hosts>
</root>
It still validates, when it shouldn't.
To fix that, add a second test1
namespace declaration with a prefix:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="test1" xmlns="test1" xmlns:t1="test1">
Now you can qualify your XPath expression:
<xs:key name="Host-PK">
<xs:selector xpath="t1:host"/>
<xs:field xpath="@id"/>
</xs:key>
And the validation for the duplicate ID will fail, as expected.
Now your second XSD won't be able to find any Host-PK
. Its root
element is a completely different one. It just shares the same name with the root
of test1
. There's no way it could be in scope. If you want to share the same key in both schemas one thing you could do is to declare the root
in test2
as an extension of the root
in test1
. But that will require some changes in test1.xsd
in order to allow test2
to refer to elements and types in test1.xsd
.
To allow other schemas to extend the type of the root
element, make it top-level. Also, make the hosts
element top-level, since we will need to refer to it in order to define the keyref
. You can also use it to validate a file which has hosts
as a root element (this will be useful, as we'll see ahead).
We won't be able to extend xs:all
, but in your case you can safely replace it with a xs:sequence
. This is the final test1.xsd after refactoring:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="test1"
xmlns="test1"
xmlns:t1="test1">
<xs:complexType name="Host">
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
<xs:complexType name="Root">
<xs:sequence>
<xs:element ref="hosts" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:element name="root" type="Root" />
<xs:element name="hosts">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="host" type="Host"/>
</xs:sequence>
</xs:complexType>
<xs:key name="Host-PK">
<xs:selector xpath="t1:host"/>
<xs:field xpath="@id"/>
</xs:key>
</xs:element>
</xs:schema>
I also added minOccurs="0"
to hosts
so it will be possible to define a root
containing only a server (but this is temporary - we will make it required again before we finish)
Now we can refer to the hosts
element and to the Root
type in test2.xsd
. We can start by extending the root
element's base type, to allow for the server
element:
<xs:complexType name="NewRoot">
<xs:complexContent>
<xs:extension base="t1:Root">
<xs:sequence>
<xs:element name="server">
<xs:complexType>
<xs:attribute name="host" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
It's also a sequence.
The root
element should be declared as:
<xs:element name="root" type="NewRoot"> ... </xs:element>
Now the root
element in test2
is an extension of the root
element in test1
, and the host
element will be in context.
Since we have to use XPath to select the server element, it's necessary to declare a prefix for the namespace of test2
so we can use it in the XPath expression:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
xmlns:t1="test1" targetNamespace="test2"
xmlns="test2" xmlns:t2="test2" >
Now you can define a local key that refers to hosts/host
and use it for the host
attribute in server
:
<xs:element name="root" type="t2:NewRoot">
<xs:key name="Host-PK">
<xs:selector xpath="t1:hosts/t1:host"/>
<xs:field xpath="@id"/>
</xs:key>
<xs:keyref name="Host-FK" refer="Host-PK">
<xs:selector xpath="t2:server"/>
<xs:field xpath="@host"/>
</xs:keyref>
</xs:element>
And this is your final test2.xsd after refactoring:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="test2"
xmlns="test2"
xmlns:t2="test2"
xmlns:t1="test1">
<xs:import namespace="test1" schemaLocation="test1.xsd"/>
<xs:complexType name="NewRoot">
<xs:complexContent>
<xs:extension base="t1:Root">
<xs:sequence>
<xs:element name="server">
<xs:complexType>
<xs:attribute name="host" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="root" type="t2:NewRoot">
<xs:key name="Host-PK">
<xs:selector xpath="t1:hosts/t1:host"/>
<xs:field xpath="@id"/>
</xs:key>
<xs:keyref name="Host-FK" refer="Host-PK">
<xs:selector xpath="t2:server"/>
<xs:field xpath="@host"/>
</xs:keyref>
</xs:element>
</xs:schema>
So now you try to validate test2.xml
and ... it fails, but no longer with the "out of scope" error. It fails because it didn't find any key with the ABC
value. That means it's validating the key all right, but it can't access the host
elements. You need to have them in your XML instance.
It will work if you simply cut and paste the hosts
element from the test1.xml
and set a default namespace for them:
<root xmlns:t1="test1" xmlns="test2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test2 test2.xsd">
<hosts xmlns="test1">
<host id="ABC"/>
<host id="DEF"/>
</hosts>
<server host="ABC"/>
</root>
You can try it out. It won't validate unless host
is ABC
or DEF
.
You might also want to keep the hosts
subtree in a separate file, and import it into both your XML instances. The native way to do that is declaring a DTD entity. First place your hosts
in a file (test3.xml):
<hosts xmlns="test1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test1 test1.xsd">
<host id="ABC"/>
<host id="DEF"/>
</hosts>
Now include it into test1.xml
and test2.xml
using an <!ENTITY>
:
test1.xml
<!DOCTYPE root [
<!ENTITY test3 SYSTEM "test3.xml">
]>
<root xmlns="test1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test1 test1.xsd">
&test3;
</root>
test2.xml
<!DOCTYPE root [
<!ENTITY test3 SYSTEM "test3.xml">
]>
<root xmlns:t1="test1" xmlns="test2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="test2 test2.xsd">
&test3;
<server host="ABC"/>
</root>
Now you can place the minOccurs="0"
back in the declaration for hosts
in test1.xsd
, to guarantee that it will always be present.
Upvotes: 1