XML 文件:XML 架构快速指南 - MSDN Magazine,2002年4月

来源:百度文库 编辑:神马文学网 时间:2024/04/29 15:02:46
XML 架构快速指南
发布日期: 4/1/2004 | 更新日期: 4/1/2004
Aaron Skonnard
XML 文件存档
下载本文的代码:XML0204.exe (35KB)

在 所有的 XML 技术中,XML 架构对软件开发人员最具价值,因为是它最终使在 XML 文档中加入类型信息成为可能。本专栏是介绍 XML 架构基础知识的两部分系列文章中的第一部分。
首先,我们来回顾一下 XML 架构之前的知识。XML 1.0 规范附带了一个描述 XML 词汇的内置语法,称为文档类型定义 (DTD)。DTD 实际顾及到 XML 1.0是从前任语言 — 标准通用标记语言 (SGML) 那里继承语法的已经有一段时间了。
DTD 使您能够描述 XML 文档的结构。例如,假定要使用以下 XML 词汇描述员工信息:
Monica1997-12-0242000.00
以下 DTD 描述了本文档的结构:

然后,该 DTD 能够通过一个 DOCTYPE 声明和原始文档相关联,如下所示:
Monica1997-12-0242000.00
验证是使用 DTD 的主要优势。当 XML 1.0 验证分析器读取该 XML 1.0 文件时,它也能够读取相关联的 DTD 并验证是否符合该定义。使用 DTD 进行验证能够减少您必须置入应用程序的错误处理量。
尽管 DTD 非常适合很多基于 SGML 的电子出版应用程序,但当应用到诸如那些围绕当今 Web 应用的现代软件开发领域时,其局限性很快就显现出来了。DTD 的主要限制是 DTD 语法和 XML 不兼容,而且 DTD 不支持命名空间、典型编程语言数据类型或定义自定义类型。
由于 DTD 语法本身不是 XML,所以不能使用标准的 XML 工具来程序化地处理这些定义。大多数 XML 1.0 处理器虽然支持 DTD 验证,但由于语法的复杂性,它不支持对 DTD中找到的信息进行程序化访问。
因为 DTD 甚至在命名空间存在以前就产生了,所以它们不能很好地协同工作就不奇怪了。事实上,使用 DTD 描述可识别命名空间的文档就像将一个方形木栓敲进一个圆孔一样。有关如何才能艰难地实现上述功能的详细信息,请查看 2001 年 5 月期的XML 文件专栏。在该专栏中,我提供了一个可识别命名空间的示例 DTD。因此,大多数开发人员要么选用DTD,要么选用命名空间,但没有两个同时选用的。
DTD 也是专门为以文挡为中心的系统而设计的,在这 种系统中通常不存在程序化数据类型。因此,只存在少数类型标识符用于描述属性(参见图 1)。这些类型标识符和您过去在编程语言里惯于使用的大不相同。它们实际上仅仅是文本的特例 (CDATA)。而且,这些类型不能应用于纯文本元素,只能应用于属性。
最后,DTD 类型系统是不可扩展的。这意味着您不得不使用图 1中描述的类型。创建在您的问题领域中有意义的自定 义类型不属于 DTD 问题的范畴。在面对 XML 架构展现的全新而又令人振奋的未来时,这些局限足以让任何 XML 开发人员回避使用 DTD。
XML 架构基础知识
XML 架构本身是一个用于描述 XML 实例文档的 XML 词汇。我之所以使用“实例”一词,是因为一个架构会描述一类文档,这类文档会有许多不同的实例(参见图 2)。这类似于现在面向对象系统中类和对象之间的关系。类相对于架构,对象相对于 XML 文档。因此,在使用 XML 架构时,您通常要使用不止一个文档,还有架构以及一个或多个 XML 实例文档。

图 2 命名空间标识符链接
架构定义中使用的元素来自 http://www.w3.org/2001/XMLSchema 命名空间,在本专栏的以下部分,我会将其绑定到 xsd。以下为基本的架构模板:

架构定义必须具有一个根 xsd:schema元素。各种元素都可能嵌套在 xsd:schema 中,包括但不限于 xsd:element、xsd:attribute 和 xsd:complexType,所有这些我都会进行讨论。
架构定义是一个 XML 文档这一事实解决了 DTD 的首要限制。您可以使用标准的 XML 1.0 工具和服务(如 DOM、SAX、Xpath 和 XSLT)来处理架构定义文档。这样做所固有的简单性对各种架构工具造成了冲击。
XML 架构和命名空间
置于 xsd:schema 元素中的定义会自动与 targetNamespace 属性中指定的命名空间相关联。在前面的示例情况下,架构定义将与 http://example.org/employee/ 命名空间相关联。
命名空间标识符是联系 XML 文档和对应的架构定义的关键(参见 图 2 )。例如,以下 XML 实例文档包含了位于 http://example.org/employee/ 命名空间的 employee 元素:

该 employee 元素的命名空间与架构定义中的 targetNamespace 相同。
为了在处理 employee 元素时利用该架构,处理器需要找到正确的架构定义。架构处理器如何为一个特定的命名空间找到架构定义在规范中没有进行定义。然而,大多数处理器会允许您加载一个架构的内存缓存,它会在处理文档时使用。例如,以下基于 JScript? 的代码展示了一种使用 MSXML 4.0 实现这个功能的简单方法:
var sc = new ActiveXObject("MSXML2.XMLSchemaCache.4.0);sc.add("http://example.org/employee/", "employee.xsd");var dom = new ActiveXObject("MSXML2.DOMDocument.4.0");dom.schemas = sc;if (dom.load("employee.xml"))WScript.echo("success: document conforms to Schema");elseWScript.echo("error: invalid instance");
它在 Microsoft? .NET 和大多数其他可识别 XML 架构的处理器中的运行方式都是类似的。
您可以从本文顶部的链接下载一个命令行验证实用工具,以试验本专栏中讨论的原理。该验证实用工具允许您指定想验证的实例文档以及所需要的任意多的架构定义。命令行用法如下:
c:>validate instance.xml -s schema1.xsd -s schema2.xsd ...
XML架构还提供了 schemaLocation 属性,用于在实例文档中提供关于所需架构定义位置的提示。schemaLocation 属性位于http://www.w3.org/2001/XMLSchema-instance 命名空间中,该命名空间是专门为只在实例文档中使用的属性而保留的。从现在起,我会将这个命名空间绑定到 xsi 前缀。xsi:schemaLocation 属性采用一个以空格为分隔符的命名空间标识符和 URL 对的列表,如下所示:

在这种情况下,如果处理器还没有访问到适合于http://example.org/employee/ 命名空间的架构定义,则可以从http://develop.com/aarons/employee.xsd 下载。
元素和属性
使用 xsd:element 和 xsd:attribute 元素,可以分别将元素和属性定义为 targetNamespace 的一部分。例如,假定您要描述以下可识别命名空间的实例文档:
Monica1997-12-0242000.00
完成该操作最简单的方法是通过以下架构定义:

请注意,仅在 xsd:schema 元素中放置 xsd:element/xsd:attribute 声明,会自动将其与 http://example.org/employee/ 命名空间相关联。由于这些声明是根 xsd:schema 元素的子级,它们在架构中被认为是全局的。
由于该架构把这些元素/属性指定为 http://example.org/employee/ 命名空间的组成部分,它们在实例文档中必须与该命名空间相关联(就像我在前面 列出的原始实例一样)。对实例进行细微的命名空间更改将导致它无效。例如,您应考虑以下包含不合格的 name、hiredate 和 salary 元素以及不合格 id 属性 的文档:
Monica1997-12-0242000.00
因为前面的架构定义说明这些元素/属性均来自 http://example.org/employee/ 命名空间,而这次它们未与命名空间相关联,所以根据架构这个实例无效。
即使是一个细微的更改也会修改原始文档,所以它使用默认的命名空间声明,而不是一个命名空间前缀:
Monica1997-12-0242000.00
虽然在此情况下,所有的元素都与默认命名空间 (http://example.org/employee/) 关联,但 id 属性仍然是不合格的,因为默认命名空间不适用于属性。因此,根据架构,这个文档实例也被认为是无效的。
如您所见,XML 命名空间恰恰是 XML 架构的核心。在使用 XML 架构时,必须全面了解命名空间是如何工作的,因为如果实例文档与架构指定的不一致,则它会无效。
您可能已经注意到这个简单示例既没有限制任何元素的内容,也未定义命名空间中各元素之间的结构关系。它等效于以下 DTD(暂时省略了属性声明):

因此,即使以下 XML 实例文档没有任何意义,根据架构,该文档也是有效的:
42.000Monica
XML 架构使通过复杂类型定义描述元素的结构成为可能。
定义复杂类型
使用 DTD,一个元素的内容模型在 ELEMENT 声明中定义,如下所示:

该 ELEMENT 声明表明一个 employee 元素包含一个 name 元素,然后是一个 hiredate 元素,最后是一个 salary 元素。
XML 架构能够用在 xsd:element 声明中嵌套 xsd:complexType 元素的类似方式来定义元素内容模型,如下所示:

XML 架构模型更像一门编程语言,在其中,您可以将变量绑定到正式类型定义。xsd:complexType 允许您定义一个传递其结构的元素类型。在元素声明中嵌套 xsd:complexType 会有效地将其绑定到那个元素(类似一个变量)。从类型定义方面进行思考是从 DTD 工作方式的一个主要的形式转变。
您放入 xsd:complexType 元素中的内容与您放入DTD ELEMENT 声明括号中的内容是类似的。前面的 employee ELEMENT 声明指定了 name、hiredate 和 salary 元素的顺序。使用管道 (|) 分隔符而不是逗号改变了一个元素选择的意义:

在 XML 架构中,您通过一个排序元素指定内容模型的特征,它被作为 xsd:complexType 元素的子级进行嵌套。XML 架构定义了三个排序元素: xsd:sequence、xsd:choice 和 xsd:all (如图 3 所示)。
xsd:sequence 和 xsd:choice 元素等效于上述所示的 DTD 示例。然而,xsd:all 是一个新概念 — 它指定内容模型由以任何顺序排列的所有项组成。 DTD 语法中没有设计这种概念,但是您可以像下面所示的那样通过显式地指定所有可能的排列来定义此类语义:

如您所见,组合数学很快开始对您造成不利了。由于 "all" 像排列和组合一样,是一个一级排序,所以XML 架构方法更为简洁。
排序元素可以包含对全局元素声明、局部元素声明、其他排序以及一些其他诸如通配符和组引用等构造的引用。图 4 所示的架构示例说明了如何定义一个对在架构其他地方定义的全局元素进行引用的 xsd:complexType。
请注意,ref 属性采用了一个带有前缀的元素名称。切记,一旦在架构中声明了一个全局元素,它就会自动地与 targetNamespace 相关联。当您按 name 引用全局元素时,它们将被看作合格的 name。如果使用 ref="name" 而不是 ref="tns:name",架构处理器则会寻找未与命名空间相关联(或默认命名空间,如果有一个已在使用)的 name 元素,但是不会找到,因为在架构中声明的唯一 name 元素是来自 http://example.org/employee/ 命名空间的 name 元素。
如果我已经把 http://example.org/employee/ 作为文档的默认命名空间,我就能够在不使用命名空间前缀(例如,ref="name")的情况下引用全局元素名称了,如图 5 所示。
图 4和图 5 中的示例架构在逻辑上是等效的 — 它们只是以稍微不同方式进行了序列化。两个示例架构都对 employee 元素的内容进行了限制。现在,employee 元素必须包含 name、hiredate 和 salary 元素,并且所有这些元素都必须与 http://example.org/employee/ 命名空间相关联。
局部元素声明
由于 employee 是我计划在实例文档中作为顶层元素使用的唯一元素,因此实在没有理由将 name、hiredate 和 salary 定义为全局元素。相反,我仅仅能够在 employee 元素的内容模型中局部定义 name、hiredate 和 salary 元素。
例如,图 6中显示的架构包含了一个 employee 元素声明,声明中包含了一个局部元素声明的序列。在这个示例中,name、hiredate 和 salary 元素实际上作为 employee 元素的一部分进行声明,而且不能在实例的其他位置使用。只有 employee 元素声明作为根 xsd:schema 元素的一个子级全局出现。这就提出了一个有趣的问题:局部元素应该与目标命名空间相关联吗?
局部范围和命名空间
为了有助于理解该问题的答案,让我们以支持命名空间的编程语言(如 C#)编写的类似示例为例。请仔细查看以下在 "example" 命名空间中定义的C# 类定义:
namespace example {public class employee {public string name;public string hiredate;public double salary;}}
在该命名空间中,什么标识符是真正可见的?只有一个 — employee。name、hiredate 和 salary 标识符只是在 employee 类中可见。因此,您需要用命名空间标识符而不是局部成员名称来限定 employee,如下所示:
// employee is namespace-qualifiedexample.employee c = new example.employee();// local members are unqualifiedc.name = "Monica";c.hiredate = "1997-12-02";c.salary = 42000.00;// this does not work, nor make sense// c.example.name = "Monica";
XML 架构设计者好象在沿着局部范围的思路进行思考,因为在默认模式下它是以这种同样的方式工作的。在 XML 架构中,实例文档中只有全局元素需要用目标命名空间限定,而且局部元素必须保持为非限定的。下面显示的 XML 文档是图 6所示的完整架构的一个有效实例:
Monica1997-12-0242000.00
如果修改该实例以便 name、hiredate和 salary 元素均由 http://example.org/employee/ 命名空间限定,根据架构,它会变得无效。切记,即使是对默认命名空间声明进行细微的改变也会导致该结果的发生。
由于不是每个人都满意该方法,XML 架构设计者使在实例中控制对局部元素应该进行限定还是取消限定成为可能。您可以在元素/元素基础上通过窗体属性对此进行控制,如下所示:

employee元素的一个有效实例现在将具有一个限定的子级 name 元素和一个非限定的子级 hiredate 元素,在其后是一个限定的子级 salary 元素,如该有效实例所示:
Monica1997-12-0242000.00
您还可以通过 elementFormDefault 属性切换架构中所有局部元素声明的默认设置,如下所示:
•••
现在,在默认情况下,所有局部元素都必须在实例中进行限定(假定还没有使用窗体属性重写特定元素的设置),如该有效实例所示:
Monica1997-12-0242000.00
由于所有元素在这种情况下都是合格的,用户现在可以选用一个默认的命名空间声明和仍将有效的实例:
Monica1997-12-0242000.00
出现约束
在 DTD 中,您可以通过 *、+ 和 ? 修饰符控制内容模型中元素出现的次数。XML 架构摆脱了这些修饰符并仅定义了两个属性,minOccurs 和 maxOccurs,它们可以在元素声明、排序和一些其他架构构造中使用。
一个项必须出现的最小次数和它能出现的最大次数分别由 minOccurs 和 maxOccurs 指定。两个属性的默认值都是 1。您还可以使用 maxOccurs 中 的 "unbounded" 值来指定可以接受的无限次出现数。
考虑以下 DTD ELEMENT 声明:

可以使用一些嵌套的排序和 minOccurs/maxOccurs 在 XML 架构中对 ELEMENT 声明进行重写,如图 7所示。
复杂类型和属性
使用 DTD 时,属性是为一个特定的元素定义的。下面的 ATTLIST 声明将 id 属性和 employee 元素相关联:

在 DTD 中定义全局属性是不可能的。它们始终必须同一个特定元素相关联,如上所示。
XML 架构使得属性可以像元素那样进行全局和局部定义。全局属性是通过在根 xsd:schema 元素中使用 xsd:attribute 元素定义的。所示的第一个架构示例定义了一个命名为 id 的全局属性。全局属性是指您在各种情况下都不能预见它们会在何处使用。
属性也能够包含在一个 xsd:complexType 定义中,使它们对于该特定类型而言是局部的。在 xsd:complexType 元素中使用时,xsd:attribute 元素必须在子级排序之后,如下面全局元素声明所示:

对于元素,您必须限定全局属性并且不能在实例文档中通过默认方式限定局部属性。以下是以前定义的 employee 元素的一个有效实例:
Monica
然而,对于元素,如果您希望限定局部属性,可以通过窗体或 attributeFormDefault 属性改变该行为。
已命名类型和重用
到此为止,我一直在通过 xsd:complexType 定义来定义元素的类型(或结构)。然而,在和新声明的元素联系时,这些类型定义不会被命名。该方法类似于在 C++ 中使用匿名类型。例如,以下 C++ 代码将 pt 变量映射到它前面的匿名结构:
struct {double x;double y;} pt;
使用匿名类型明显的负面影响在于您不能重用类型定义。因此,大多数 C++ 开发人员使用已命名类型:
struct Point {double x;double y;};Point pt1;
XML 架构也支持已命名类型,实际上因其有价值的重用潜力,所以才成为大多数开发人员首选的方法。您不但能够在单个架构中重用已命名类型,而且还能够通过 xsd:include 和 xsd:import 元素在各架构间重用已命名类型。
您通过名称属性来命名 xsd:complexType。然后,能够通过类型属性将元素声明绑定到一个已命名类型。以下架构阐释了该元素绑定如何运行:

请注意,由于 xsd:complexType 定义现在是架构中的一个全局构造,它会被自动地与 targetNamespace 相关联。这意味着您在引用类型属性中的 EmployeeType 时,必须使用限定名称。您可回溯本文中显示的所有示例架构,并将匿名 xsd:complexType 转换至已命名类型。在这两种方法之间转换很容易。
使用已命名类型的另一个好处在于,如果您不需要,则不必在架构中实际使用全局元素声明。相反,您可以通过 xsi:type 属性显式地指定实例文档中的元素类型,该属性也来自 http://www.w3.org/2001/XMLSchema-instance 命名空间。例如,您应考虑以下实例来完成该任务:
Monica1997-12-0242000.00
foo 元素在架构的任何位置都未被声明,但我已经显式地指定了它的类型,对于处理器这足以明了如何处理 foo 的内容。该方法类似于大多数编程语言中的强制转换。
介绍数据类型
我已经介绍了通过 xsd:element、xsd:attribute和 xsd:complexType 定义描述文档结构细节的主要内容。然而,现在为止,您所看到的架构还没有描述 name、hiredate 和 salary 元素以及 id 属性的特征。在这一点上,它们能够合法地包含任何内容。name、hiredate 和 salary 元素以及 id 属性应真正地包含文本并且每个使用特定的格式。
在 DTD 中,您可以通过 #PCDATA 标记指定元素必须只包含文本:

遗憾地是,您不能指定有关包含在元素中的文本格式的任何内容。

图 8 XML 架构内置数据类型
这就是XML 架构比 DTD 大有进步的地方,特别是对于软件开发人员。XML 架构定义了一组内置数据类型,可以用于约束纯文本元素和属性的内容(参见 图 8 )。每个数据类型具有一个显式定义的值空间和一个显式定义的词法空间(或者也就是说,可能在 XML 文档中使用的字符串格式)。例如,double 值 4200 能够以各种词法方式进行表示(参见 图 9)。关于给定的数据类型的值和词法空间更详细的内容,请参阅 XML 架构规范第二部分(相关 URL 可在“推荐阅读”侧面栏中找到)。

图 9 词法表示
因此,为了限制在元素或属性中使用的文本,选择具有合适值/词法空间的数据类型并在声明类型属性中使用它即可,如以下代码所示:

当一个架构处理器验证前一个架构的示例时,它会确保每个元素/属性中包含的文本符合其定义类型的合法的词法表示。
如您在 图 8 中所见,对于每种情况都有一个数据类型。然而,您一定会遇到内置数据类型不能精确满足您的需要的情况。例如,在以前的架构定义中,id 属性被定义为类型字符串,但它实际需要采用社会保障号格式。XML 架构确实能够实现在这些情况下定义自定义简单类型,但我会将这个主题留给本专栏的第二部分。
我们所处的位置 XML 架构克服了 DTD 中所有的局限和缺点。XML 架构语法是 XML 1.0。XML 架构完全围绕命名空间进行设计。而且,更重要的是,XML 架构支持典型的编程语言数据类型以及自定义的简单和复杂类型。
尽管 W3C 最近才发布了最终的 XML 架构建议(2001 年 5 月),但在各种 XML 和 Web 服务相关的基础结构中已经存在了对于该规范的广泛支持。该基础结构依靠 XML 架构来自动生成 XML 处理代码、构建即时动态代理/存根、在编辑器和其他工具中提供 IntelliSense? 以及通过架构验证技术来简化错误处理。MSXML 4.0、SOAP Toolkit 2.0 和 .NET 都是使用 XML 架构可以完成的绝好示例。
本月的专栏只涉及了 XML 架构的基础知识。我介绍了 DTD 提供的所有功能和另外一些内容。在该系列的第二部分,我将向您展示该语言的更高级功能。
要向 Aaron 提出问题和意见,请发送电子邮件至xmlfiles@microsoft.com。
Aaron Skonnard 是 DevelopMentor 的教师兼研究员。在那里,他开发了关于 XML 和 Web 服务的课程。Aaron 与人合著了《Essential XML Quick Reference》(Addison-Wesley, 2001) 和《Essential XML》(Addison-Wesley, 2000)。您可以通过以下网址与 Aaron 联系:http://staff.develop.com/aarons。
摘自2002 年 4 月期 的MSDN Magazine。此杂志可以通过各地的报摊购买,也可以在此订阅。
转到原英文页面