IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML 问题: 使用 RELAX NG 反击,第 1 部分

比 W3C XML Schema 做得更好

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz,博士 (mertz@gnosis.cx), 总裁, Gnosis Software, Inc.

2003 年 7 月 01 日

RELAX NG 模式提供了一种描述有效 XML 实例类的方法,这种方法比使用 W3C XML Schema 更强大、更简练并且在语义上更简单。RELAX NG 的优点在于:它在允许互不相关地可扩展数据类型以及方便地组合相关实例模型的同时还扩展了 DTD 经良好证明的语义。David 在这篇文章中初探了 RELAX NG,这是由三部分组成的系列文章的第一篇。

我已经留意 W3C XML Schema 很长时间了,而且在一定程度上关注着 XML 本身。不同行业和背景的各种公司和团体都对 W3C XML Schema 添加了他们各自想要的少许内容,以此对它进行拙劣地修补,从而创建了一个典型的委员会设计的且难以理解的标准。事实上,我作了许多保留,以致于我一般建议坚持将 DTD 用于验证需求,并严格地在应用程序级别上弥补任何缺口。

但是,大约一个月前,我开始认真考虑 RELAX NG。与许多读者一样,我以前听说过这种替代模式语言,但我过去一直以为 RELAX NG 与 W3C XML Schema 几乎完全相同,只不过拼写上稍有不同。我真的错了。RELAX NG 几乎在各方面都完全优于 W3C XML Schema 或 DTD!事实上,RELAX NG 支持无序(或半有序)内容模型的能力回答了我以前对 OOP 数据类型语义模型和 XML 元素线性之间不匹配方面所关注的大多数问题。

本文是讨论 RELAX NG 的 XML 问题专栏文章(共三篇)的第一篇。这篇专栏文章将讨论 RELAX NG 的常规语义,并简要阐述数据类型。下一篇专栏文章将讨论使用 RELAX NG 的工具和库。最后一篇专栏文章将更详细地讨论 RELAX NG 的紧凑语法。

语义模型

RELAX NG 的语义非常简单 - 就这一点而言,它们是 DTD 语义的自然扩展。RELAX NG 模式所描述的就是由量化、排序及交替组成的 模式。另外,RELAX NG 引入了用于无序集合的模式,DTD 和 W3C XML Schema 都不支持这种模式(SGML 支持这种模式,但没有 RELAX NG 灵活)。而且,RELAX NG 几乎以相同的方式来处理元素和属性。元素/属性的一致性很符合 XML 的概念空间,这一点比 DTD 和 W3C XML Schema 中严格分离所做的要好得多。在实际设计中,设计考虑事项中常常不能确定是选择使用属性主体还是选择使用元素主体,并且(或者)这样的选择在上下文中很敏感。

可用于 RELAX NG 的量化与 DTD 中所用的相同。任何模式的条件都可以被设定为 <oneOrMore><zeroOrMore><optional> ;它们分别对应于 DTD 量词 +*? (也可用于正则表达式及其它地方)。事实上,RELAX NG 紧凑语法所用的量词与 DTD 中使用的完全相同。不可否认的是,这些非常常规的量词却使声明特定基数约束变得更困难,就如您用 W3C XML Schema minOccursmaxOccurs 属性所做的那样。我不会介意以后看到合并了更灵活的基数约束的 RELAX NG 修订版。但是,使用命名模式来解决这些限制比在 DTD 中这样做更容易。

对多个模式排序就是以某一顺序列出这些模式的问题。但是通过使用 <choice><group><interleave> 元素可以向同一级别的一系列模式提供不同的语义。 <group> 标记的用法与 DTD 中圆括号的用法相同。就其本身而言, <group> 元素毫无意义,但是当它用于 <choice><interleave> 元素内时, group 就充当一个模式,而不是几个模式。 <choice> 元素表示被包含的模式之间的简单交替。但是, <interleave> 元素允许您在遵守所包含的每个模式的基数同时,可以分散排列这些模式。例如,假设一个图书馆顾客有姓名及标识号,还可能借阅了几本书 - 对于这些用途,我们不会关心这些功能是以什么顺序列出的。但是,可以根据书名或 ISBN 来标识一本书(但不可同时使用书名和 ISBN 进行标识 - 这可能是一个不现实的示例)。RELAX NG 描述看起来象下面这样:


清单 1. 图书馆顾客模式
<element name="patron"
         xmnln="http://relaxng.org/ns/structure/1.0">
  <interleave>
    <element name="name"><text/></element>
    <element name="id-num"><text/></element>
    <zeroOrMore>
      <element name="book">
        <choice>
          <attribute name="isbn"/>
          <attribute name="title"/>
        </choice>
      </element>
    </zeroOrMore>
  </interleave>
</element>

理解这个模式差不多就是大声朗读它。但是让我们先来看一下与这个 XML 语法相对应的紧凑语法:


清单 2. 图书馆顾客紧凑语法
element patron {
  element name { text }   &
  element id-num { text } &
  element book {
    (attribute isbn { text } |
     attribute title { text } )
  }*
}

事实上,您 不能使用 DTD 或 W3C XML Schema 描述有效的顾客记录,至少在未精心修饰和/或对精确性折衷时,不能这样做。例如,以下是两条有效记录:


清单 3. 有效的顾客记录
<patron>
  <book isbn="0-528-84460-X"/>
  <name>John Doe</name>
  <id-num>12345678</id-num>
  <book title="Why RELAX is Clever"/>
</patron>
<patron>
  <id-num>09876545</id-num>
  <name>Jane Moe</name>
</patron>

但清单 4 是无效的,因为其它模式通常不能描述它(属性在 DTD 和 W3C Schema 中只能是可选或必需的,而不能是相关的):


清单 4. 无效的顾客记录
<patron>
  <name>John Doe</name>
  <id-num>12345678</id-num>
  <book title="Why RELAX is Clever" isbn="0-528-84460-X"/>
  <book/>
</patron>

而且,RELAX NG 模式定义姓名、标识号及书籍的无序集合的能力回答了我在讨论 YAML 和其它格式时的抱怨,即 XML 对原来无序的数据强加了任意排序。

一般的交错行为产生的有趣结果是可以将文本/PCDATA 节与子元素混合,并控制所允许的文本块的数量(基数)。例如,可以允许一个连续的 PCDATA 流在一些可选或必需的标记之间的 任何地方出现。





回页首


属性和元素的一致性

XML 一个十分常见的使用方案是元素,它 要么包含一个特殊属性, 要么包含子元素(或 PCDATA 内容)集合,但是不能同时包含它们。例如, gnosis.xml.pickle序列化格式定义了类似以下这样的异构列表:


清单 5. 类似 gnosis.xml.pickle 的列表片段
<list>
  <item type="string" value="Bar"/>
  <item type="list">
    <item type="numeric" value="17"/>
    <item type="None"/> <!-- None is singleton w/o value -->
  </item>
</list>

给定的 <item> 仅当不包含 value 属性时包含子元素,反之亦然。使用 DTD,我们可以将该规则近似地表示为:


清单 6. 用于 pickle 的近似 DTD
<!ELEMENT list (item+)>
<!ELEMENT item (item*)>
<!ATTLIST item
    type  (None | string | numeric | list) #REQUIRED
    value CDATA #IMPLIED >

该 DTD 将与前面的 XML 实例文档相匹配,但是它还会错误地匹配:


清单 7. 对 DTD 的无效错误匹配
<list>
  <item type="None" value="Some">
    <item type="string" value="More"/>
  </item>
  <item type="list"/>
</list>

一开始 W3C XML Schema 十分神秘,但就该例而言,最终它还是没有象 DTD 那样对任何事物都描述得那么详细。例如,以下是简化例子的模式,其中 <item> 可能只包含 PCDATA,而没有包含子元素:


清单 8. 近似的 W3C XML Schema
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="list">
  <xsd:element name="item" minOccurs="1" maxOccurs="unbounded">
    <xsd:complexType>
      <xsd:simpleContent>
        <xsd:extension base="xsd:string">
          <xsd:attribute name="type" type="xsd:string"/>
          <xsd:attribute name="value" type="xsd:string"
                         use="xsd:optional"/>
        </xsd:extension>
      </xsd:simpleContent>
    </xsd:complexType>
  </xsd:element>
</xsd:element>

这里 DTD 和 W3C XML Schema 中都有这样一个问题:它们使属性过于特殊,并且处理属性所用的方式与处理元素的方式完全不同。但是,RELAX NG 处理它们的方式是一致的(目前,这里显示了 PCDATA 的简化例子,跳过了命名模式):


清单 9. 用于 pickle 的 RELAX NG XML 语法
<element name="list" xmlns="http://relaxng.org/ns/structure/1.0>
  <oneOrMore>
    <element name="item">
      <choice>
        <text/>
        <attribute name="value"/>
      </choice>
    </element>
  </oneOrMore>
</elment>

使用紧凑语法,我们可以编写:


清单 10. 用于 pickle 的 RELAX NG 紧凑语法
element list { element item { attribute type {text},
                      (attribute value {text} | {text}) }+ }





回页首


命名模式

子模式的嵌套(以及上下文敏感)定义只是 RELAX NG 中可用的一个样式。您也许还可以在某种语法内使用命名模式。另外,通过使用某种语法,RELAX NG 模式可以显式指出根元素以便验证。一个语法包含一个根 <grammar> 元素、一个 <start> 元素以及零或多个 <define> 元素。尤其是, <define> 元素可以包含递归元素。

样本语法演示了如何使用定义和引用。让我们定义以上几个示例中跳过的嵌套 <item> 标记:


清单 11. 用于 pickle 的改进 RELAX NG 语法
<grammar xmlns="http://relaxng.org/ns/structure/1.0>
  <start>
    <element name="list">
      <ref name="items"/>
    </element>
  </start>
  <define name="items">
    <oneOrMore>
      <element name="item">
        <choice>
          <ref name="items"/>
          <attribute name="value"/>
        </choice>
      </element>
    </oneOrMore>
  </define>
</grammar>

它最后向我们提供了对上面描述的属性或子元素序列化形式的精确验证约束。实际情况中,通常我们定义的命名模式要比这里的多;每一个模式都可以自由引用其它模式(在量化和选择等等内)。





回页首


数据类型

尽管 W3C XML Schema 拥有复杂的内置数据类型集合,而且 DTD 实际上根本没有包含任何数据类型,但 RELAX NG 包含了完全模块化的且可扩展的数据类型系统。通常,RELAX NG 用户只是使用伴随 W3C XML Schema 的数据类型的整个集合。就如同名称空间的规范,在定义了属性 datatypeLibrary 的最直接外围元素中可以找到数据类型库。因此,例如, 可以用每个数据值来定义数据类型库:


清单 12. 数据类型的详细规范
<element name="foo">
  <choice>
    <data type="integer"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
    <data type="float"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
  </choice>
</element>

但是,一个更简便的方法是在顶层指明数据类型库。如果特定数据值需要遵循另一个数据类型库,那么可以在单个 <data> 标记内覆盖它。例如:


清单 13. 数据类型的简明规范
<element name="foo"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
  <choice>
    <data type="integer"/>
    <data type="float"/>
  </choice>
</element>

您已经看到了一个很好的特性:定义复合数据类型时使用的模式与定义元素和属性的完全相同。choice 通常是最实用的模式元素,但其它元素(包括 <list> ,我们将在下面讨论)有时也很实用。

使用 <value> 元素替代 <data> 元素可以指定枚举。例如:


清单 14. 使用 choice 的枚举值
<element name="foo">
  <choice>
    <value type="integer">1</value>
    <value type="integer">2</value>
    <value type="integer">3</value>
    <value type="string">infinity</value>
  </choice>
</element>

库中有些数据类型可以允许参数化。如果存在该选项,那么可以缩小可接受值的范围(不必自始至终限于显式枚举)。该强大功能与库中的功能相同 - 也就是,与 W3C XML Schema 所允许的相同。例如:


清单 15. 数据类型的参数化规范
<element name="foo">
  <data type="string">
    <param name="maxLength">10</param>
  </data>
</element>

当利用数据类型库时,仍可以使用 <list> 元素构造有点定制的数据类型。列表只是用空格分隔的标记序列,每个标记与某些数据类型相匹配。与别处一样,列表可以描述元素或属性内容。例如,假设想让属性包含的集合中有一个或多个整数值:


清单 16. 数据类型的复合规范
<element name="foo">
  <attribute name="numbers">
    <list>
      <oneOrMore>
        <data type="integer"/>
      </oneOrMore>
    </list>
  </attribute>
</element>

相匹配的文档是:

<foo numbers="1 2 3 988765"/>

无效文档示例是:

<foo numbers="word"/>





回页首


接下来讨论什么?

还有少许 RELAX NG 的内容本文尚未谈及 - 但只是极少的内容。十分值得注意的是用这么少的简单概念能产生多么强大的能力。XML 问题的后两篇专栏文章将粗略地讨论象合并语法、增加信息集(或缺少信息集)、插入基数约束以及其它数个语义概念这样的问题。但是我们将花大部分篇幅主要讨论工具和紧凑语法。



参考资料



关于作者

David Mertz 在他的格言愿望中表示,他希望自己已经创造了以下观点:关于标准,最重要的事情是可以有众多选择。但另一方面,他在 OS 设计方面也是模糊的。可以通过 mertz@gnosis.cx与 David 联系;在 http://gnosis.cx/publish/上可以了解他的生活。非常欢迎对过去的、这一篇和将来的专栏文章提出建议和意见。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款