内容


XML Schema 1.1,第 2 部分

XML Schema 1.1 简介

XPath 2.0 中的同现约束

Comments

系列内容:

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

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

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

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

简介

XML Schema 1.0 同时提供复杂类型和简单类型定义,允许模式作者指定并限制元素的内容和属性值。根据 XML Schema 1.0 规范的描述,复杂类型定义通过提供属性声明对元素进行约束,属性声明可以约束属性的外观和内容,具体方法是将元素限制为空元素或遵循指定的内容模型,比如只包含元素的模型、混合模型,或由内容的简单类型定义确定的简单内容。

复杂类型定义还定义了一种可以约束类型定义层次结构的机制,它可以确定如何通过扩展或限制,从其他复杂类型派生出不同的复杂类型或简单类型。复杂类型中的替换组可以使用派生类型的元素控制元素替换,另一方面,简单类型可以约束元素和属性内容的字符值。

本文将讨论同现(co-occurrence)约束,这是 XML Schema 1.1 中新引入的一个特性,它不仅可以约束元素和属性的内容,还可以约束它们的存在。

历史回顾

我们在 本系列第一篇文章 中提到,XML Schema 1.0 存在某些限制。除了第一篇文章提到的限制以外,XML 模式作者经常需要强制使用更多复杂规则来判断和限制元素和属性的内容,比如根据属性值限制某些子元素的外观,使子元素的总数不超过特定的值,或者根据子元素的查找范围将它的值设置为有效值。

不幸的是,XML Schema 1.0 并没有提供实施这些规则的途径。要实现这些约束,您需要执行下面的操作:

  • 编写应用程序级别的代码(在对 XML 模式进行验证之后)
  • 使用样式表检查(即验证后处理)
  • 使用不同的 XML 模式语言,比如 RelaxNG 或 Schematron

由于 XML Schema 1.0 用户社区一直要求提供同现约束检查的支持,因此 XML Schema 1.1 工作组将断言和类型替换的概念引入到 XML Schema 1.1 中,允许 XML 模式作者表达此类约束。

断言

断言可以使 XML 模式作者灵活地控制元素和属性的出现和值。

使用场景

在进一步研究 XML Schema 1.1 如何定义断言之前,首先查看一些使用场景。

  1. 根据两个或更多属性的值指定一个约束规则。对于 清单 1 显示的 XML 片段,您可以在 widthheight 属性之间指定一个规则,使高度永远小于宽度。
    清单 1. XML 片段 - 具有两个属性的元素
    <dimension width="10" height="5"/>
  2. 在属性和元素之间指定一个约束规则。在 清单 2 中,其中一个元素具有一个属性和两个子元素。您可以在属性和子元素之间指定一个规则,使属性的值等于子元素的数量。
    清单 2. XML 片段 - 具有一个属性和两个子元素的元素
    <parent children="2">
      <child name="one"/>
      <child name="two"/>
    </parent>
  3. 指定一个约束规则,确定属性之间的次序和选项。对于 清单 3 中定义的元素,可以指定一个规则,根据这条规则,timer 可以具有 timeiterations 属性,但不能同时拥有两者。
    清单 3. XML 片段 - timer 元素
    <timer time="30" iterations="2000"/>
  4. 指定一个规则,将一组元素和属性组成一个模型组。例如,制定一个规则将元素内容限制为 childgrandchild,或者两者均具有属性 namedob,这样可以限制 parent 元素(在 清单 4 中定义)的内容。
    清单 4. XML 片段 - 一个父元素
    <parent>
      <child name="abc" dob="1/1/1997"/>
      <grandchild name="xyz" dob="1/1/2007"/>
    </parent>
  5. 在具有混合内容的元素的文本中指定一个约束规则。清单 5 中的 parent 元素具有混合内容。那么可以指定一条规则,将混合内容文本限制为最多只有 10 个字符。
    清单 5. XML 片段 - 具有混合内容的父元素
    <parent>2 children
      <child>abc</child>
      <child>xyz</child>
    </parent>

为了满足这些以及其他使用场景,XML Schema 1.1 通过 XML Schema 1.1 断言提供了更具表达性的约束。XML Schema 1.1 中的断言类似于 Schematron 和 RelaxNG 等其他模式语言中使用的断言。

在撰写本文时,您可以对简单和复杂类型指定断言。谓词通过 XPath 2.0 表达式指定,XPath 2.0 表达式是类型指定的断言的一部分。

复杂类型断言

在 XML Schema 1.1 中,复杂类型定义可以包含一个断言模式组件,该组件是一个复杂类型定义的 <xs:assert> 子元素序列。序列的次序无关紧要。断言约束元素和属性的存在和它们的值。<xs:assert> 模式组件包含一个 test 属性(该属性是一个 XPath 表达式属性记录)和一个 annotations 属性。

xs:assert 元素信息项的 test 属性的值是一个 XPath 表达式,其值等于 true 或 false。您可以使用一个特殊的变量 $value 引用被检查的元素或属性的简单内容值。表达式的计算是在父元素的上下文内完成的。XPath 表达式必须是一个有效的 XPath 2.0 表达式,或至少遵循 XML Schema 1.1 规范定义的最小 XPath 子集。

如果指定的 XPath 表达式是无效的,将报告 xpath-valid 错误。如果 xs:assert 没有得到正确指定,那么模式处理程序将报告一个 as-props-correct 错误。如果测试表达式的计算结果为 true 并且没有导致动态的或类型错误,那么则认为元素在逻辑上是有效的。如果表达式计算结果为 false,那么将报告一个常见的 cvc-assertion 错误。

清单 6 是一个复杂类型的例子,其中的断言约束两个属性的值。如果 height 的值小于 width 的值,那么断言表达式的计算结果为 true,否则为 false

清单 6. 复合类型的断言 - @height < @width
<xs:element name="dimension">
  <xs:complexType>
    <xs:attribute name="height" type="xs:int"/>
    <xs:attribute name="width" type="xs:int"/>
    <xs:assert test="@height < @width"/>
  </xs:complexType>
</xs:element>

在上面的示例中,我们将 xs:assert 元素信息项定义为 xs:complexType 的直接子元素。我们还可以在使用复杂内容(xs:complexContent)或简单内容(xs:simpleContent)定义复杂类型时对 xs:restrictionxs:extension 指定 xs:assert。一个元素要想成为有效的元素,其断言序列中的每个断言的值都必须为 true。这个序列包含对复杂类型定义的所有断言,以及复杂类型的祖先的所有断言。

清单 7 中,我们给出了两个复杂类型,baseTypederivedType,并且每个类型分别拥有自己的断言。baseType 上的断言检查 mustUnderstand 属性是否在元素中给出,derivedType 上的断言检查 mustUnderstand 属性的值是否是 YES 并且至少给出一个 body 子元素;否则将要求 mustUnderstand 的值为 NOderivedType 的序列包含两个断言,即来自 baseType 的断言和它本身的断言。要使 message 元素有效,其内容必须按照 complexType 定义那样定义为有效内容,并且所有断言的值必须为 true。

清单 7. 具有复杂内容的复杂类型的断言
<xs:complexType name="baseType">
  <xs:sequence>
   <xs:element name="body" minOccurs="0" maxOccurs="unbounded"/>
  </xs:sequence>
  <xs:attribute name="mustUnderstand" type="xs:string"/>
  <xs:assert test="@mustUnderstand"/>
</xs:complexType>

<xs:complexType name="derivedType">
  <xs:complexContent>
    <xs:restriction base="baseType">
      <xs:sequence>
        <xs:element name="body" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute name="mustUnderstand" type="xs:string"/>
      <xs:assert test="( @mustUnderstand eq 'YES' and fn:count(./body) > 0 )
                       or ( @mustUnderstand eq 'NO' )"/>
    </xs:restriction>
  </xs:complexContent>
</xs:complexType>

<xs:element name="message" type="derivedType"/>

在使用简单内容定义复杂类型时,可以指定两种类型的断言。第一种断言充当 facet 并限制简单内容类型(比如,将简单值限制为 7 的倍数),而第二种断言作为整个元素的断言,包括它的属性。由于 xs:simpleContent/xs:restriction 的内容模型的语法并没有区分这两种断言,因此引入了一个新的元素信息项 xs:assertion 来表示一个断言 facet。我们将在下一节讨论简单类型定义断言时介绍 xs:assertion

简单类型断言

在 XML Schema 1.1 中,和复杂类型一样,xs:simpleType 子元素中的 xs:restriction 元素可以包含 xs:assertion 元素。简单类型中的断言类似于其他简单类型约束 facet。断言简单类型组件表示一组约束 facet,通过令值满足 test 属性值中的 XPath 表达式指定的条件,对简单类型的值空间施加限制。

和复杂类型定义一样,断言是一组被指定为简单类型定义 facet 的一组有序的 xs:assertion 元素序列。断言序列的具体顺序并不重要,因为序列中所有断言的值必须为 true,这样该类型的元素或属性才是有效的。断言模式组件包含一个值属性,这是一个由来自基类型的断言(如果有的话)和派生 simpleType 定义的断言组成的序列。

如 XML Schema 1.1 规范定义的那样,xs:assertion 元素 facet 的 test 属性的值是一个 XPath 2.0 表达式或 XPath 2.0 子集,其值为 true 或 false。计算在父元素的上下文内完成。如果所有断言 facet 有效,那么具有简单内容的元素或属性就是有效的(即每个 xs:assertiontest 属性的值为 true,没有出现动态或类型错误)。

清单 8 中,我们展示了一个具有简单内容的元素示例,如果该元素的值是 10 的倍数,那么它的断言 facet 则计算为 true。

清单 8. 值为 10 的倍数的具有简单内容的元素
<xs:element name="message">
 <xs:simpleType>
   <xs:restriction base="xs:int">
     <xs:assertion test="($value mod 10) = 0"/>
  </xs:restriction>
 </xs:simpleType>
</xs:element>

对于限制另一种简单类型的派生简单类型,如果它满足派生类型(及其限制 facet)以及同时属于基类型和派生类型的断言,那么值是有效的。在 清单 9 中,一个字符串值只有在由 3 到 25 个字符组成并且以 “xyz” 为结尾时才是有效的。

清单 8. 对派生简单类型定义的断言
<xs:simpleType name="base">
  <xs:restriction base="xs:string">
    <xs:maxLength value="25"/>
    <xs:assertion test="fn:ends-with($value, 'xyz')"/>
  </xs:restriction>
</xs:simpleType>

<xs:simpleType name="derived">
  <xs:restriction base="base">
    <xs:assertion test="fn:string-length($value) > 3 "/>
  </xs:restriction>
</xs:simpleType>

错误消息定制

如前面小节所示,XML Schema 1.1 断言可以使用任何 XPath 2.0 表达式,并且这些表达式可以非常复杂。当断言失败时,非常有必要提供易于理解的错误消息。

模式错误代码

当模式约束被违背后,模式规范要求报告相应的错误代码。比如,如果看到错误代码 cvc-attribute.3,则表明违背了约束 Attribute Locally Valid 的子句 3,表示属性值在类型方面是无效的。

错误代码包含了有关上下文的一些信息(比如元素或属性名、行和列号或涉及的值),因此通常足够用来进行问题诊断。将其应用于断言,如果未能满足断言,那么将报告错误代码 cvc-assertion。即使获得了所有上下文信息,您仍然不知道真正的问题所在以及修复方法,除非您查看模式并尝试理解(可能非常复杂的)XPath 表达式。

Schematron 方法

Schematron 用户(参见 参考资料)常常发现,对违背规则后报告的消息进行定制的能力非常有用(清单 10)。

清单 10. Schematron 规则
<report test="@min > @max">
  On element "<sch:value-of select="local-name(.)"/>", value of the
  "min" attribute "<sch:value-of select="@min"/>" can not be greater
  than that of the "max" attribute "<sch:value-of select="@max"/>".
</report>

以下 XML 片段(清单 11)违背了这一规则。

清单 11. 违背 Schematron 规则的 XML 片段
<range min="30" max="10"/>

这个片段将生成一条消息:On element "range", value of the "min" attribute "30" can not be greater than that of the "max" attribute "10"

该方法具有两个重要的优点:

  1. 人类可读的错误消息可以被关联到验证规则,从而可以更加轻松地诊断验证失败。
  2. 错误消息还可以使用 XPaths 引用被验证的实例的值,提供更多引起违背的原因的信息。在上例中,range、30 和 10 这些信息在每个实例中都不相同。

本地化支持

验证规则可以部署到使用不同本地语言的系统中,并且用户将要求使用不同的人类语言查看错误消息。要使用本地化的消息,Schematron 建议同时使用 diagnostics 属性和 xml:lang 属性,如 清单 12 所示。

清单 12. Schematron 本地化消息示例
<sch:pattern>
  <sch:rule context="person">
    <sch:assert test="name" diagnostics="d1 d2">
      A person must have a name.
    </sch:assert>
  </sch:rule>
</sch:pattern>

<sch:diagnostics>
  <sch:diagnostic id="d1" xml:lang="en">
    A person must have a name.
  </sch:diagnostic>
  <sch:diagnostic id="d2" xml:lang="fr">
    Une personne doit avoir un nom.
  </sch:diagnostic>
</sch:diagnostics>

Schematron 实现现在可以根据期望的语言选择正确的 diagnostic

SML 方法

在本地化方面,Schematron 方法仍然不是很完美。在新添了一种语言支持后,需要对 Schematron 规则进行更新,必须同时向 diagnostics 属性添加新的 diagnostic 条目,并添加新的 ID

Java™ 编程语言使用属性包(property bundles)解决这一问题。在添加了一种新语言后,将引入一个新的属性包,并且只要它遵循某种命名规则,就可以自动发现,而不需要对使用消息的位置进行修改。

Service Modeling Language (SML) 使用 Schematron 作为其众多验证机制中的一种。它引入了 “location ID” 概念(清单 13)允许使用和 Java 环境类似的资源管理策略。

清单 13. 使用 location ID 概念的 SML
<sch:assert test="name" sml:locid="person:nameRequired">
  A person must have a name.
</sch:assert>

locid 属性的类型为 QName。其名称空间可用于定位包(其值可能包含与某人有关的所有错误消息等的信息),而 local name 用于标识错误消息以显示相应的规则。在 清单 14清单 15 中,我们使用英语和法语展示了一些消息属性示例。

清单 14. 英语消息属性片段
nameRequired = A person must have a name.
清单 15. 法语消息属性片段
nameRequired = Une personne doit avoir un nom.

对断言使用错误消息定制

XML Schema 1.1 并没有介绍如何对断言定制错误消息,但是它允许在标注中内嵌特定于应用程序的信息。比如,清单 16 展示了如何在一个标注内的 “appinfo” 元素中包括一个定制的错误消息,并使用 “documentation” 提供有关该消息的额外信息。用户会从 XML Schema 1.1 处理程序使用标注定制断言错误的最佳实践中获益。通常还会包括启用错误消息本地化的机制。

清单 16. 使用标注定制错误消息
<xs:complexType name="rangeType">
  <xs:attribute name="min" type="xs:int"/>
  <xs:attribute name="max" type="xs:int"/>
  <xs:assert test="@min <= @max">
    <xs:annotation>
      <xs:appinfo>
        Value of the "min" attribute can not be greater than that of the "max"
        attribute.
      </xs:appinfo>
      <xs:documentation>
        When this assertion fails, the content of the above "appinfo" is used
        to produce the error message.
      </xs:documentation>
    </xs:annotation>
  </xs:assert>
</xs:complexType>

类型替换

XML Schema 1.1 引入了一种称为类型替换(type alternatives)的新机制,允许模式作者在元素声明中指定类型替换。

条件类型分配简介

在 XML Schema 1.0 中,xsi:type 被作为一种类型替换机制引入。要在实例文档中对元素指定 xsi:type,使用派生类型替换声明的类型。如果特别针对 XML Schema 使用设计 XML 词汇表,那么这种机制非常有用,并且词汇表实例需要使用 xsi:type 实现类型替换。然而,如果针对已经具有自己的类型替换的词汇表编写 XML 模式,那么 xsi:type 不会起作用。这种词汇表的实例使用某些其他机制选择类型。一种例子就是 Atom Syndication Format,这是一种用于 Web 提要的 XML 语言。

Atom 允许实例对包含文本结构的元素指定一个 xsi:type 属性。如果给出该属性的话,其值必须为 texthtmlxhtml 其中之一。所允许的内容由该属性的值决定。由于该属性不是 xsi:type,因此无法编写使用 XML Schema 1.0 语言建模 Atom 的模式。如果选择类型的条件非常复杂,比如 @height < @width(比较两个属性值),则不能简单地使用 xsi:type 在实例中替换它。

为了克服 xsi:type 的缺陷,可以使用类型替换机制。它允许模式作者在元素声明中根据 XPath 表达式的值指定类型替换。在下一小节中我们将使用 Atom 作为例子展示这种方法。

类型替换的原理

在 XML Schema 1.1 中,元素声明的类型表中可以包含一个类型替换组件序列和一个默认类型定义(也是一个类型替换)。在 XML 模式文档中,这些内容被指定为元素声明的 xs:alternative 子元素的序列。类型替换模式组件包含一个 test 属性(一个 XPath 表达式属性记录)、一个类型定义和一个 annotations 属性。

xs:alternative 上的 test 属性的值对应于 test 属性,后者是一个值为 true 或 false 的 XPath 表达式。表达式被限制为 XPath 2.0 的一个子集,特别是那些只选择属性 axis 的表达式。这表示只有当前元素上的属性才能执行 XPath 计算。注意,为计算而构建的 XDM 数据模型并没有包含任何类型定义。这样做是为了避免模式处理程序为判断元素类型而猜测属性类型的情况。除非元素类型已确定,否则无法知晓实际的属性类型。

元素声明中的最后一个 xs:alternative 子元素可以忽略 test 属性。如果给出此属性的话,类型替换就是默认的类型定义。如果没有指定任何此类 xs:alternative,那么元素将声明默认的类型定义。

xs:alternativetype 属性的值对应于类型替换模式组件的类型定义属性。如果 test 属性上的 XPath 表达式最终值为 true,那么将选择指定的 type 替换元素中声明的类型。指定的 type 必须派生于声明的类型或称为 xs:error 的特殊简单类型定义(没有有效实例)。xs:error 类型如果满足类型替换的条件,则可以将元素置为无效。

如果元素声明具有类型替换,那么将按照在模式中指定的顺序进行计算。其 XPath 表达式计算为 true 的第一个类型替换使用选择的类型。如果所有 XPath 表达式的值都不是 true,那么将选择默认类型定义作为元素的 type

现在我们已经介绍了类型替换的原理,现在来看一个示例(清单 17),看看如何使用它编写针对 Atom 的模式。如 上一节 所述,包含文本结构的元素的 type 属性为元素指定了允许的内容。下面的片段展示了如何在 Atom 中为一个 title 元素编写声明。

清单 17. 类型替换 xsd 示例
<xs:element name="title" type="xs:anyType">
  <xs:alternative test="@type='text'" type="xs:string"/>
  <xs:alternative test="@type='html'" type="htmlContentType"/>
  <xs:alternative test="@type='xhtml'" type="xhtmlContentType"/>
  <xs:alternative test="@type" type="xs:error"/>
  <xs:alternative type="xs:string"/>
</xs:element>

title 的元素声明有一个 xs:anyType 基类型并且指定了 5 个类型替换。这些类型替换将依次执行计算,直到其中一个 XPath 表达式的值为 true(如果值均不为 true,则选择默认的类型定义)。前三个类型替换根据 type 属性的值(texthtmlxhtml)选择类型。如果类型属性的值不是这三个值,前三个类型替换的 XPath 表达式将计算为 false。第四个类型替换将检查 type 属性是否存在。如果模式处理程序达到了这个步骤,type(如果该属性存在的话)的值不是 Atom 允许的值。我们将这个替换的类型指定为 xs:error 以表示如果条件得到满足,元素就是无效的。如果没有 XPath 表达式的值是 true,那么选择默认的类型定义(xs:string)。

清单 18 所示,title 元素的实例演示了如何选择不同的类型替换。

清单 18. 类型替换 xml 实例元素
<!-- 1st type alternative selected: xs:string -->
<title type="text">My News</title>

<!-- 3rd type alternative selected: xhtmlContentType -->
<title type="xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml">My
 <xhtml:em>News</xhtml:em>!</title>

<!-- default type alternative selected: xs:string -->
<title>My News</title>

<!-- 4th type alternative selected: xs:error. Invalid. -->
<title type="unknown">Oops! Error.</title>

结束语

在本文中,我们概述了 XML Schema 1.1 中的同现约束支持,着重介绍了新增的断言和类型替换特性,这些特性可以进一步限制元素和属性的存在和值。在本系列的第 3 部分中,我们将探讨通配符支持以及它如何允许您改进 XML 模式。


相关主题


评论

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

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