How can I create a XML instance with a XSD that includes keyref from another namespace

假装没事ソ 提交于 2019-12-11 11:42:54

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/23225613/how-can-i-create-a-xml-instance-with-a-xsd-that-includes-keyref-from-another-nam

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!