内容


XML Schema 1.1,第 3 部分

XML Schema 1.1 简介

利用强大的通配符支持改进模式

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: XML Schema 1.1,第 3 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:XML Schema 1.1,第 3 部分

敬请期待该系列的后续内容。

前言

在 W3C Workshop on XML Schema 1.0 User Experiences 期间(参见 参考资料),模式版本控制是模式用户的主要关注点之一。当 XML 数据改变时,相应的模式也需要改变。如何确保一定级别的兼容性,以减少对应用程序的破坏呢?

人们经常谈论两种兼容性。在模式版本控制上下文中,向后兼容性要求模式版本 n 中的有效实例在模式版本 n+1 下保持有效。这是人们通常谈论到的兼容性,也是比较容易支持的一种兼容性,因为模式版本 n+1 的作者具有版本 n 的模式和实例的访问权。

另一种是向前兼容性,即模式版本 n+1 中的有效实例在模式版本 n 下也有效。这通常较难做到,因为作者并不知道下一版本中可能引入什么样的更改。我们所能做的就是在模式中留下扩展点,以允许未来进行扩展。

由于获得向前兼容性很重要,也比较困难,所以 XML Schema 1.1 中的主要目标之一就是让编写向前兼容的模式更为容易。通配符在定义模式中的扩展点方面扮演着重要角色,是本文关注的焦点。本系列的下一篇文章将讨论与模式版本控制相关的其他特性。

W3C XML Schema 工作组发布了一个针对 XML Schema 1.1 的版本控制指南(参见 参考资料)。那些寻求模式版本控制方面帮助的人可能也会觉得它的内容有趣。

弱通配符

有些模式作者创建复杂类型定义,在其中混合一系列元素和通配符(通配符允许同一个或多个名称空间充当其他元素),可能会发现他们所编写的模式是无效的。该错误最可能的原因是,违背了 XML Schema 1.0 中定义的 Unique Particle Attribution (UPA) 规则,该规则指出,对于实例文档中的每个元素,匹配粒子(例如复杂类型定义中的 <xs:element><xs:any>)都可明确确定。这种确定性简化了验证器的实现,并且对于需要实例文档中的元素与模式中的粒子匹配的应用程序来说也很有用。但是也向模式作者自然地表达他们希望允许的内容提出了挑战。

清单 1 中的模式片段演示了,模式作者试图使用通配符创建扩展点时通常会面临的问题。我们来考虑一个复杂类型,用于建模一个运动团队的输赢记录。在有些运动中,比如美国足球,是允许打平的。而在另一些运动中,比如篮球,会一直比下去,直到分出输赢才结束。模式作者可能会选择让 ties 作为可选元素(利用 minOccurs="0")。除了赢、输以及平之外,团队记录中可能还会包含其他统计信息,所以您可能想要用一个通配符来允许额外的内容,在未来版本的模式中可以定义该通配符。

清单 1. 模式片段 - 一个输赢记录类型定义
<xs:complexType name="record">
  <xs:sequence>
    <xs:element name="wins" type="xs:nonNegativeInteger"/>
    <xs:element name="losses" type="xs:nonNegativeInteger"/>
    <xs:element name="ties" type="xs:nonNegativeInteger" minOccurs="0"/>
    <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="lax"/>
  </xs:sequence>
</xs:complexType>

上面这个复杂类型定义的问题可以用 清单 2 中的实例文档来演示。该实例中的 winslosses 元素分别与它们在模式中的元素声明匹配(参见 清单 1)。当您试图将 ties 元素向后与复杂类型匹配时,会发现该粒子有两个匹配的选项。它可以是 ties 元素声明(可选),也可以是通配符(也允许 ties 出现在实例中)。该模式由于具有不止一个潜在的匹配,所以违背了 XML Schema 1.0 中的 Unique Particle Attribution (UPA) 规则,因而是无效的。

清单 2. XML 片段 - 一个无效的输赢记录元素
<record>
  <wins>20</wins>
  <losses>15</losses>
  <ties>8</ties>
  <points>48</points>
</record>

作为一种解决方案,模式作者可能会在可选的元素声明和通配符之间放置一个必需的元素,如 清单 3 所示。由于 separator 元素必须出现在实例中,所以在匹配 separator 元素声明的内容和它后面的通配符之间不存在含糊性。

清单 3. 模式片段 - 在可选元素和可选通配符之间定义一个必需的元素
<xs:complexType name="record">
  <xs:sequence>
    <xs:element name="wins" type="xs:nonNegativeInteger"/>
    <xs:element name="losses" type="xs:nonNegativeInteger"/>
    <xs:element name="ties" type="xs:nonNegativeInteger" minOccurs="0"/>
    <xs:element name="separator"/>
    <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="lax"/>
  </xs:sequence>
</xs:complexType>

尽管通常可以添加一个必需的元素来避免 UPA 错误,但是引入到实例中的内容往往是无意义的,或者让数据失去自然的排序。下面来看一下 清单 4。引入的 separator 元素不能提供任何文档信息,但是为了让文档有效,它必须出现在这里。理想情况下,您并不想要文档中有这么一个元素。

清单 4. XML 片段 - 一个有效的输赢记录元素
<record>
  <wins>20</wins>
  <losses>15</losses>
  <ties>8</ties>
  <separator/>
  <points>48</points>
</record>

为了让模式作者更容易创建比较自然的内容模型,XML Schema 1.1 引入了弱通配符(weakened wildcard)的概念。弱通配符是 UPA 规则的放宽,通过规定元素声明总是优先于通配符,解决了元素声明和通配符之间的含糊性。因此,清单 1 中的复杂类型定义在 XML Schema 1.1 中变得有效,因为元素声明和通配符之间的不确定性已不复存在。通配符添加于第一处的原因是为了允许模式改进。设想一下,在未来某个时候,我们更新了 record 类型的定义,使之包含一个 points 元素,如 清单 5 所示。现在,清单 2 中实例中的 points 元素定义好了,根据弱通配符规则,它明确地与它的元素声明匹配。

清单 5. 模式片段 - 一个扩展的输赢记录类型定义
<xs:complexType name="record">
  <xs:sequence>
    <xs:element name="wins" type="xs:nonNegativeInteger"/>
    <xs:element name="losses" type="xs:nonNegativeInteger"/>
    <xs:element name="ties" type="xs:nonNegativeInteger" minOccurs="0"/>
    <xs:element name="points" type="xs:nonNegativeInteger" minOccurs="0"/>
    <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="lax"/>
  </xs:sequence>
</xs:complexType>

否定通配符

有时候,会希望通配符不匹配某些名称。例如,在 schema 1.0 中,可以指定 ##other 作为一个通配符(<any><anyAttribute>)的 namespace 属性的值,表示这个通配符匹配除当前模式文档的目标名称空间之外的名称空间中的名称空间。该特性已被证实对于在模式中留下扩展点非常有用。

但是有些情况不符合 ##other。XML Schema 1.1 引入了一些机制来为通配符指定例外情况。这些机制可统称为否定通配符(negative wildcards)

名称空间排除

##other 只可用于排除单个名称空间:目标名称空间。若要排除多个名称空间呢?例如,假设一个模式的版本 1 使用目标名称空间 ".../V1",版本 2 使用 ".../V2"。模式作者可能希望留下扩展点,以允许除版本 1 或版本 2 之外的任何名称空间中的名称。清单 6 展示了现在可以如何在 XML Schema 1.1 中表达这一点。

清单 6. 模式片段 - XML Schema 1.1 中的名称空间排除
<xs:complexType>
  <xs:sequence>
    ...
    <xs:any notNamespace="http://example.com/V1 http://example.com/V2"
            processContents="lax"/>
  </xs:sequence>
</xs:complexType>

利用这个新的 notNamespace 属性,可以指定通配符不应该匹配的名称空间,该属性的含义与 namespace 属性的含义相反。显然,一个通配符上只需要这两个属性中的一个。

notNamespace 属性期望一个由空格分隔的任何 URI 值的列表。类似于 namespace 属性,notNamespace 也允许列表中出现特殊符号 ##targetNamespace##local,分别表示目标名称空间和空名称空间。

QName 排除

通配符通常用于匹配除显式指定的名称之外的名称。清单 7 展示了这样的一个例子。

清单 7. 模式片段 - 通配符匹配显式指定的名称之外的名称
<xs:complexType name="referenceType">
  <xs:sequence>
    <xs:element ref="tns:uri"/>
    <xs:element ref="tns:description" minOccurs="0"/>
    <xs:any processContents="lax"
            minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

每个 reference 类型都需要一个 uri 子元素和一个可选的 description 子元素,后跟任意数量用于扩展的子元素。这似乎没有问题;但是不幸的是,它也允许以下实例(清单 8):

清单 8. XML 片段 - 一个 reference 元素带有多个 uri 子元素
<reference>
  <uri>...</uri>
  <uri>...</uri>
</reference>

现在,应用程序处理 reference 元素时会遇到困难,无法决定使用哪个 uri 子元素。这是由通配符匹配多个名称导致的。为了解决这个问题,可以使用 XML Schema 1.1 中新引入的不允许的名称(disallowed names)概念,如 清单 9 所示。

清单 9. 模式片段 - 使用不允许的名称
<xs:complexType name="referenceType">
  <xs:sequence>
    <xs:element ref="tns:uri"/>
    <xs:element ref="tns:description minOccurs="0"/>
    <xs:any processContents="lax" notQName="tns:uri"
            minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

利用 notQName 属性,模式作者可以提供通配符不应该匹配的一系列 QNames。这个更新的类型定义防止了上面那个具有两个 uri 子元素的实例。

已知兄弟元素的排除

有时,模式作者可能希望排除一个很长的名称列表,这难以使用 notQName 属性来指定所有要排除的名称。XML Schema 1.1 指出了两种会经常出现的情况,并提供了简化它们的机制。

如果您定义一个用来描述人的复杂类型,那么类型中会有很多元素,比如针对姓名、出生日期、住址、职业等的元素。如果您也想使用通配符(或开放内容)来允许添加额外的信息,那么您想要对通配符进行限制,使之不匹配类型中已经声明的元素。

为此,使用 notQName 属性并列出所有已知的元素名称。排除列表不仅很长,也难以维护。一个新元素添加到类型时,您必须记住将它的名称添加到 notQName 中。在 XML Schema 1.1 中,使用 ##definedSibling 很容易描述此类排除(清单 10):

清单 10. 模式片段 - 使用 ##definedSibling 的 QName 排除
<xs:complexType name="personType">
  <xs:sequence>
    <xs:element ref="tns:name"/>
    <xs:element ref="tns:dateOfBirth"/>
    <xs:element ref="tns:address"/>
    <xs:element ref="tns:occupation"/>
    <xs:any processContents="lax" notQName="##definedSibling"
            minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

可以使用关键字 ##definedSibling 作为 notQName 属性中的一个值,来表示通配符不匹配任何已经在该复杂类型中显式声明的元素名称。这包括那些从基类型(通过扩展)继承而来的元素。

注意,##definedSibling 不适用于属性通配符(<anyAttribute>),因为 XML 不允许同一个元素上出现相同名称的属性。

已知全局元素的排除

如果期望模式的未来版本在当前目标名称空间中引入新的概念(从而引入新的元素或属性),那么复杂类型中一定要具有允许新名称的通配符或开放内容。与此同时,通配符应该不允许当前版本的模式所已知的概念。否则,它们就已经包含在复杂类型定义中了。

我们来看一下上面 清单 10 中的 personType。如果有一个针对 person 的全局元素声明,那么由于通配符的作用,下面的 xml 片段(清单 11)对于 personType 是有效的:

清单 11. XML 片段 - 一个 person 元素
<person>
  <name>...</name>
  <dateOfBirth>...</dateOfBirth>
  <address>...</address>
  <occupation>...</occupation>
  <person>...</person>
</person>

为了避免这个问题,XML Schema 1.1 提供了另一个特殊的关键字,用在 notQName 属性中,##defined 表示该通配符不匹配任何有全局声明的名称。您可以像下面这样更新 personType 复杂类型中的通配符(清单 12):

清单 12. 模式片段 - personType 定义
<xs:complexType name="personType">
  <xs:sequence>
    ...
    <xs:any processContents="lax" notQName="##definedSibling ##defined"
            minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

现在,它既不匹配 personType 中显式声明的元素,也不匹配任何全局声明的元素。因此,一个 person 元素出现在另一个 person 元素中的实例是不被允许的。

假设没有为 telephone 声明全局元素,那么更新后的 personType 允许一个 person 元素,如 清单 13 中所示。

清单 13. 模式片段 - 一个使用已知全局排除的 person 元素定义
<person>
  <name>...</name>
  <dateOfBirth>...</dateOfBirth>
  <address>...</address>
  <occupation>...</occupation>
  <telephone>...</telephone>
</person>

在下一版本的模式中,如果添加了一个 telephone 元素,那么该实例就变成无效。这是故意这么做的,是为了突出新模式中的 personType 真的应该被更新为包含 telephone,如果希望它出现在 person 中的话。

开放内容

在 XML Schema 1.0 中,一个复杂类型所允许的子元素序列完全由它的内容模型 — 组织在 <sequence><choice><all> 模型组中的元素声明和通配符确定。XML Schema 1.1 通过提供一种机制来接受除内容模型中显式定义的子元素之外的子元素,进一步扩展了这一特性。这种机制就是通常所说的开放内容。为了理解开放内容,我们来看一下 清单 14 中的 XML 片段,演示了一张 CD 专辑中的一首示例 CD 歌曲。

清单 14. XML 片段 - CD 专辑中的 CD 歌曲
<cd id="0001">
  <artist>Foo Faa</artist>
  <album>Blah Blah</album>
  <genre>Alternative</genre>
  <price>11.99</price>
  <currency>USD</currency>
  <release_date>01-01-2009</release_date>
  <song>
    <track>XML XML</track>
    <duration>1.45</duration>
  </song>
</cd>

现在来看模式片段(清单 15),它以灵活的方式描述了 cd 元素,并允许模式作者无需改变模式就可扩大 cd 元素的内容。

清单 15. 模式片段 - CD 歌曲定义
<xs:complexType name="CatalogEntry">
  <xs:sequence>
    <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
    <xs:element name="artist" type="xs:string"/>
    <xs:element name="album" type="xs:string"/>
    <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
    <xs:element name="price" type="xs:decimal"/>
    <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
    <xs:element name="release_date" type="xs:dateTime"/>
    <xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:string"/>
</xs:complexType>

<xs:element name="cd" type="tns:CatalogEntry"/>

清单 15 中的模式可以看到,出现在 xml 片段(清单 14)中的可选元素(即 genrecurrencysong),通过很多元素通配符定义 <xs:any> 在模式中指定,其中 <xs:any> 分散于整个复杂类型定义 CatalogEntry 中。这会使得模式难以理解并导致额外的工作,有时是重复性工作,比如说要求模式作者在模式中插入很多通配符声明。

开放内容通过提供默认通配符解决了这个问题,这扩展了内容模型,以接受任何地方或者仅仅是内容模型末尾的元素。开放模型可以在模式或复杂类型级别进行指定。注意,开放内容通配符甚至比显式指定的通配符还要弱。也就是说,在子元素序列中的一个元素可以匹配显式通配符也可以匹配开放内容通配符时,显式通配符优先。

复杂类型定义中的开放内容

要在复杂类型上指定开放内容,可在复杂类型定义中或者它的 <xs:restriction><xs:extension> 子元素中包含一个 <xs:openContent> 子元素。<xs:openContent> 元素可以包含可选的 idmode 属性。

mode 属性的值确定内容模型如何被扩展。值 interleave 表示匹配开放内容通配符的元素可以出现在子元素序列的任何地方,而值 suffix 表示元素只可出现在序列的末尾。mode 属性的值也可以是 none,这我们将在 下一小节 中详细讨论。

<xs:openContent> 元素的子元素是一个元素通配符。

清单 16 中,我们演示了如何使用 XML Schema 1.1 中新的开放内容特性来定义 cd 元素。清单展示了如何用一个开放内容取代 清单 15 中模式片段中的元素通配符。

清单 16. 模式片段 - 使用开放内容的 CD 专辑
<xs:complexType name="CatalogEntry">
  <xs:openContent mode="interleave">
    <xs:any namespace="##any" processContents="skip"/>
  </xs:openContent>

  <xs:sequence>
    <xs:element name="artist" type="xs:string"/>
    <xs:element name="album" type="xs:string"/>
    <xs:element name="price" type="xs:decimal"/>
    <xs:element name="release_date" type="xs:dateTime"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:string"/>
</xs:complexType>

<xs:element name="cd" type="tns:CatalogEntry"/>

清单 16 中,复杂类型定义包含一个由 4 个显式定义的子元素组成的序列。此外,<xs:openContent> 元素允许来自任何名称空间的元素出现在这些子元素中的任何地方。

模式文档级别的开放内容

模式作者通常需要将同一种通配符添加到大量复杂类型以允许进一步的扩展。这就需要能够指定一个适用于所有复杂类型的默认开放内容。这减少了编写和维护模式的工作量,还确保了不会有某个复杂类型被偶然遗漏而没有扩展性。

要指定默认开放内容,可在 <xs:schema> 元素下包含一个 <xs:defaultOpenContent> 子元素。就像 <xs:openContent> 元素一样,<xs:defaultOpenContent> 元素包含一个元素通配符和类似的可选 idmode 属性,其中 mode 的值为 interleavesuffix

此外,默认开放内容元素还可以包含一个可选的 appliesToEmpty 属性。当 appliesToEmpty 属性的值为 true 时,默认开放内容适用于当前模式文档中的所有复杂类型。值 false 表示,如果某个复杂类型具有空内容模型,默认开放内容就不适用于它。

另一种覆盖默认行为的方式是,在复杂类型的 <xs:openContent> 元素上指定 none 作为 mode 的值。none 值表示这个复杂类型不使用默认开放内容。

清单 17 中,我们修改了 清单 16 中的模式片段,以使用默认开放内容,而不使用复杂类型级别的开放内容。

清单 17. 模式片段 - 使用默认开放内容的 CD 歌曲
<xs:schema ...>
...
<xs:defaultOpenContent mode="interleave">
  <xs:any namespace="##any" processContents="skip"/>
</xs:openContent>
...
<xs:complexType name="CatalogEntry">
  <xs:sequence>
    <xs:element name="artist" type="xs:string"/>
    <xs:element name="album" type="xs:string"/>
    <xs:element name="price" type="xs:decimal"/>
    <xs:element name="release_date" type="xs:dateTime"/>
  </xs:sequence>
  <xs:attribute name="id" type="xs:string"/>
</xs:complexType>
<xs:element name="cd" type="tns:CatalogEntry"/>
...
</xs:schema>

该复杂类型定义的内容模型 CatalogEntry 包含一个由 4 个显式定义的子元素组成的序列,以及一个在模式级别定义的 <xs:defaultOpenContent> 元素的开放内容。

默认的模式文档范围的属性

在 XML Schema 1.0 中,模式作者通过使用 <xs:attributeGroup>,能够为一个给定的复杂类型定义一组常见属性。清单 18 展示了一个示例属性组,其中定义了两个常用属性:widthheight

清单 18. 模式片段 - 使用属性组定义的常见属性
<xs:attributeGroup name="dimensionGroup">
  <xs:attribute name="width" type="xs:int"/>
  <xs:attribute name="height" type="xs:int"/>
</xs:attributeGroup>

<xs:complexType name="dimensionType">
  ...
  <xs:attributeGroup ref="tns:dimensionGroup"/>
</xs:complexType>

如果那组属性碰巧是很多复杂类型定义所共有的,那么除了在所有复杂类型定义中包含属性组引用之外,XML Schema 1.0 中没有什么容易的方式来指出这个事实。清单 19 演示了,在 XML Schema 1.0 中通过引用同一属性组,很多复杂类型定义如何定义同一组属性。

清单 19. 模式片段 - 定义在多个复杂类型定义中的常见属性
<xs:attributeGroup name="dimensionGroup">
  <xs:attribute name="width" type="xs:int"/>
  <xs:attribute name="height" type="xs:int"/>
</xs:attributeGroup>

<xs:complexType name="dimensionType">
  ...
  <xs:attributeGroup ref="tns:dimensionGroup"/>
</xs:complexType>

<xs:complexType name="sofa">
  ...
  <xs:attributeGroup ref="tns:dimensionGroup"/>
</xs:complexType>

XML Schema 1.1 引入了默认属性组的表示法。在 <xs:schema> 元素上,可以指定一个属性组定义作为默认属性(使用 defaultAttributes 属性)。该属性组定义将被自动包含在模式文档中定义的每个复杂类型中。在下面的 清单 20 中,dimensionTypesofa 都将包含属性组 dimensionGroup 中定义的属性。无需在每个复杂类型定义中显式地引用属性组。

清单 20. 模式片段 - 使用默认属性定义的常见属性
<xs:schema ....
           defaultAttributes="tns:dimensionGroup"/>

<xs:attributeGroup name="dimensionGroup">
  <xs:attribute name="width" type="xs:int"/>
  <xs:attribute name="height" type="xs:int"/>
</xs:attributeGroup>

<xs:complexType name="dimensionType">
  ...
</xs:complexType>

<xs:complexType name="sofa">
  ...
</xs:complexType>

...

</xs:schema>

如果一个复杂类型定义想要覆盖默认行为(也就是说,您不想要包含属性组),您可以将 <xs:complexType> 元素上的 defaultAttributesApply 属性设置为 false。在 清单 21 中,名为 person<xs:complexType> 覆盖默认属性的默认行为(通过指出您不想包含默认属性列表)。

清单 21. 模式片段 - 覆盖默认属性的行为
<xs:schema ...
           defaultAttributes="tns:dimensionGroup"/>

<xs:attributeGroup name="dimensionGroup">
  <xs:attribute name="width" type="xs:int"/>
  <xs:attribute name="height" type="xs:int"/>
</xs:attributeGroup>

<xs:complexType name="dimensionType">
  ...
</xs:complexType>

<xs:complexType name="person" defaultAttributesApply="false">
  ...
</xs:complexType>

...

</xs:schema>

利用默认属性组,可以更容易指定模式中每个复杂类型都应该接受的属性(例如,xml:idxml:lang,或者属性通配符)。

结束语

在本文中,我们讨论了 XML Schema 1.1 中的一些版本控制特性,重点放在对通配符支持的改变和增加开放内容上,以允许 XML 模式作者编写可与未来版本兼容的模式。在本系列的第 4 部分中,我们将探索更多的版本控制特性,比如条件包含和组件覆盖。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=460020
ArticleTitle=XML Schema 1.1,第 3 部分: XML Schema 1.1 简介
publish-date=12312009