如何使用包含来自另一个命名空间的keyref的XSD创建XML实例

时间:2014-04-22 16:49:38

标签: xml namespaces xsd keyref

我正在尝试验证依赖于另一个实例(来自不同的命名空间)的XML实例,并且它具有该命名空间中的键的keyref。当我尝试验证实例时,会产生一个错误,指出密钥超出了范围。

这些是我的XSD:

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>

我的实例:

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>

服务器主机属性是主机ID的关键引用。

在架构验证期间, test2.xml 文件引发了以下错误:

  

错误:[Xerces] Identity Constraint错误:keyref标识   约束&#34; Host-FK&#34;是指超出范围的密钥或唯一。

我该如何解决?

如何从 test2.xml 引用 test1.xml 实例?

1 个答案:

答案 0 :(得分:1)

我假设您可以更改两个XSD,并且您正在使用XSD 1.0。

在第一个XSD中,您需要限定XPath元素,因为无前缀元素属于 no namespace 。因为你的钥匙将无法正常工作。您可以验证这是否为test1添加了重复的ID:

<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>

它仍然有效,何时不应该。

要解决此问题,请添加第二个test1名称空间声明,其前缀为:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
    targetNamespace="test1" xmlns="test1" xmlns:t1="test1">

现在您可以限定XPath表达式:

<xs:key name="Host-PK">
    <xs:selector xpath="t1:host"/>
    <xs:field xpath="@id"/>
</xs:key>

如预期的那样,重复ID的验证将失败。

现在,您的第二个XSD无法找到任何Host-PK。它的root元素是完全不同的元素。它只与root的{​​{1}}共享相同的名称。它不可能在范围内。如果您想在两个模式中共享相同的密钥,您可以做的一件事就是将test1中的root声明为test2root的扩展名。但这需要在test1中进行一些更改,以便test1.xsd能够引用test2中的元素和类型。

要允许其他模式扩展test1.xsd元素的类型,请将其设置为顶级。另外,将root元素置于顶级,因为我们需要引用它才能定义hosts。您还可以使用它来验证以keyref为根元素的文件(这将非常有用,因为我们将会提前看到)。

我们无法扩展hosts,但在您的情况下,您可以安全地将其替换为xs:all。这是重构后的最终 test1.xsd

xs:sequence

我还将<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> 添加到minOccurs="0",因此可以定义仅包含服务器的hosts(但这是暂时的 - 我们会在完成之前再次要求它)

现在我们可以在root中引用hosts元素和Root类型。我们可以从扩展test2.xsd元素的基本类型开始,以允许root元素:

server

它也是一个序列。

<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> 元素应声明为:

root

现在<xs:element name="root" type="NewRoot"> ... </xs:element> 中的root元素是test2root元素的扩展名,test1元素将位于上下文中。

由于我们必须使用XPath来选择服务器元素,因此必须为host的命名空间声明前缀,以便我们可以在XPath表达式中使用它:

test2

现在,您可以定义引用<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:t1="test1" targetNamespace="test2" xmlns="test2" xmlns:t2="test2" > 的本地密钥,并将其用于hosts/host中的host属性:

server

这是重构后的最终 test2.xsd

<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 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> 并且......它失败了,但不再是&#34;超出范围&#34;错误。它失败了,因为它没有找到test2.xml值的任何键。这意味着它可以正确验证密钥,但无法访问ABC元素。您需要在XML实例中使用它们。

如果您只是剪切并粘贴host中的hosts元素并为它们设置默认命名空间,它将起作用:

test1.xml

你可以尝试一下。除非<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> hostABC,否则无法验证。

您可能还希望将DEF子树保留在单独的文件中,并将其导入到两个XML实例中。本机方法是声明DTD实体。首先将hosts放在文件中( test3.xml ):

hosts

现在使用<hosts xmlns="test1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="test1 test1.xsd"> <host id="ABC"/> <host id="DEF"/> </hosts> 将其加入test1.xmltest2.xml

<强> test1.xml

<!ENTITY>

<强> test2.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>

现在,您可以将<!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> 放回minOccurs="0"hosts的声明中,以保证它始终存在。