DTD缺陷:1 )DTD 是基于正则表达式的,描述能力有限;2) DTD 没有数据类型的支持,在大多数应用环境下能力不足;3) DTD 的约束定义能力不足,无法对XML 实例文档作出更细致的语义限制;4) DTD 的结构不够结构化,重用的代价相对较高;5 )DTD 并非使用XML 作为描述手段,而DTD 的构建和访问并没有标准的编程接口,无法使用标准的编程方式进行DTD 维护。而XML Schema 正是针对这些DTD 的缺点而设计的,XML Schema 是完全使用XML 作为描述手段,具有很强的描述能力、扩展能力和处理维护能力。
XML Schema 的主要目的是用来定义一类XML 文档( 一个XML Application) 。因此模式的" 实例文档" 形式常常用来描述一个与特定XML Schema 相一致的XML 文档。事实上,文档实例和Schema 文档都不是必须要以文档的形式存在,他们可以存在以于应用之间传递的字节流的形式存在,或者作为一个数据库记录或者作为XML 的" 信息项" 的集合而存在。让我们开始考虑一个在文件po.xml 中的实例文档。它描述了一个由家庭产品采购和支付应用生成的购买订单。
po.xml ,购买订单的XML 实例文档
<?xml version="1.0"?> <purchaseOrder orderDate="2008-4-6"> <shipTo country="CN"> <name>Janwer zhang</name> <street>123 nanjiang load</street> <city>shanghai</city> <state>SH</state> <zip>201204</zip> </shipTo> <billTo country="CN"> <name>Robert Smith</name> <street>8 Oak Avenue</street> <city>Old Town</city> <state>PA</state> <zip>95819</zip> </billTo> <comment>Hurry, my lawn is going wild!</comment> <items> <item partNum="872-AA"> <productName>Lawnmower</productName> <quantity>1</quantity> <USPrice>148.95</USPrice> <comment>Confirm this is electric</comment> </item> <item partNum="926-AA"> <productName>Baby Monitor</productName> <quantity>1</quantity> <USPrice>39.98</USPrice> <shipDate>2008-4-6</shipDate> </item> </items> </purchaseOrder>
购买订单由一个主元素purchaseOrder 和子元素shipTo 、billTo 、comment 和items 组成。这些子元素( 除了comment) 也依次包括其他子元素等等。USPrice 子元素包含的是一个数字而不是任何子元素。元素如果包含子元素或者是带有属性则被称为复合类型 ;反之元素如果仅仅包含数字、字符串或者其他数据等,但不包含任何子元素则称为简单类型 。
在实例文档中复合类型和一些简单类型是在购买定单的模式文档中定义。而其他一些标准的简单类型则是作为XML Schema 内置的简单类型的指令表的一部分定义的。
观察实例文档你可以看到购买订单模式文档并没有被提及。一个实例文档实际上不需要引用模式文档,当然尽管很多实例文档确实引用了,为了使这第一节简单化,一开始选择不引用。
购买订单模式文档
购买订单模式文档包含在文件po.xsd 中:
po.xsd ,购买订单的Schema 文档
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:annotation> <xsd:documentation xml:lang="en"> Purchase order schema for Example.com. Copyright 2008 Example.com. All rights reserved. </xsd:documentation> </xsd:annotation> <xsd:element name="purchaseOrder" type="PurchaseOrderType"/> <xsd:element name="comment" type="xsd:string"/> <xsd:complexType name="PurchaseOrderType"> <xsd:sequence> <xsd:element name="shipTo" type="USAddress"/> <xsd:element name="billTo" type="USAddress"/> <xsd:element ref="comment" minOccurs="0"/> <xsd:element name="items" type="Items"/> </xsd:sequence> <xsd:attribute name="orderDate" type="xsd:date"/> </xsd:complexType> <xsd:complexType name="USAddress"> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="street" type="xsd:string"/> <xsd:element name="city" type="xsd:string"/> <xsd:element name="state" type="xsd:string"/> <xsd:element name="zip" type="xsd:decimal"/> </xsd:sequence> <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="CN"/> </xsd:complexType> <xsd:complexType name="Items"> <xsd:sequence> <xsd:element name="item" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="productName" type="xsd:string"/> <xsd:element name="quantity"> <xsd:simpleType> <xsd:restriction base="xsd:positiveInteger"> <xsd:maxExclusive value="100"/> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="USPrice" type="xsd:decimal"/> <xsd:element ref="comment" minOccurs="0"/> <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/> </xsd:sequence> <xsd:attribute name="partNum" type="SKU" use="required"/> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> <!-- Stock Keeping Unit, a code for identifying products --> <xsd:simpleType name="SKU"> <xsd:restriction base="xsd:string"> <xsd:pattern value="\d{3}-[A-Z]{2}"/> </xsd:restriction> </xsd:simpleType> </xsd:schema>
购买订单模式文档由一个schema 元素和一系列子元素组成,大多数子元素为element, complexType, 和simpleType ,这些决定了在实例文档中的元素的表现方式和内容。
通过出现在schema 元素中的命名空间声明xmlns:xsd="http://www.w3.org/2001/XMLSchema" ,在模式文档中的每一个元素都有一个与XML Schema 命名空间相联系的命名空间前缀"xsd:" 。尽管任何前缀都能够被使用,但是,前缀"xsd:" 被约定用于表示XML Schema 命名空间。通过使用同样的前缀,这样同样的关联也出现在内置的简单类型的名字中。例如,xsd:string 。这种形式的关联的目的是用来标识元素和简单类型是属于XML Schema 语言的词汇表而不是模式文档作者自己的词汇表。为了在这里清楚表示,先仅提及元素的名字和简单类型( 如simpleType) 而忽略了它们的前缀"xsd:" 。
复合类型定义,元素和属性声明
在XML Schema 中,对于允许元素有他们自己的内容以及可以携带自身属性的复合类型与那些不能够有元素内容和属性的简单类型之间,有着基本的不同。而在实例文档中,对于能建立新的类型( 无论简单和复杂) 的定义和允许元素和属性有特定的名字和类型( 无论是简单还是复杂) 的声明之间,也有着显著的差别。
新的复合类型使用 complexType 元素来定义,这样的定义典型的包含一组元素声明,元素引用和属性声明。这些元素声明与其说是它们自身的类型,不如说是一由相关模式控制的名与控制这些名在实例文档中的表现形式的约束之间的关联。元素使用 element 元素声明,同时属性使用 attribute 来声明。举例来说, USAddress 被定义为一个复合类型并且在USAddress 定义中看到五个元素的声明和一个属性的声明。
<xsd:complexType name="USAddress" > <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="street" type="xsd:string"/> <xsd:element name="city" type="xsd:string"/> <xsd:element name="state" type="xsd:string"/> <xsd:element name="zip" type="xsd:decimal"/> </xsd:sequence> <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="CN"/> </xsd:complexType>
这个定义的结果将是,在实例文档中出现的任何类型声明为USAddress 的元素( 比如在po.xml 中的shiptTo) 必须包含五个元素和一个属性。这些元素必须被命名为name 、street 、city 、state 和zip ,这些名称就如同在模式声明中name 属性的值所指的那样。并且这些属性必须按照模式声明中的同样的顺序出现 。前四个元素必须包含一个字符串元素内容而第五个必须包含一个十进制数字类型的元素内容。声明为USAddress 类型的元素可以带有一个country 属性,该属性必须包含字符串"CN" 。
USAddress 类型定义仅仅包含引用简单类型的声明:string 、decimal 和NMTOKEN 。与之对比,PurchaseOrderType 类型定义则包含了引用复合类型的元素声明,如USAddress ,虽然两个类型声明都使用同样的type 属性来标识类型,而无需管类型是简单的还是复合的。
<xsd:complexType name="PurchaseOrderType"> <xsd:sequence> <xsd:element name="shipTo" type="USAddress"/> <xsd:element name="billTo" type="USAddress"/> <xsd:element ref="comment" minOccurs="0"/> <xsd:element name="items" type="Items"/> </xsd:sequence> <xsd:attribute name="orderDate" type="xsd:date"/> </xsd:complexType>
在PurchaseOrderType 的类型定义中,对于shipTo 和 billTo 这两个子元素的声明,它们将不同的元素名字和相同的复合类型相关联,这个复合类型是USAddress 。这个定义的结果是,如果在实例文档中出现的任何元素( 如po.xml 中) ,当元素类型被声明为PurchaseOrderType 时,那么这个元素必须包含两个名为shipTo 和billTo 的元素,这两个元素都要包含五个子元素(name 、street 、city 、state 和zip) ,这五个子元素是作为USAddress 声明的一部分而出现的。应USAddress 的相关类型定义,shipTo 和billTo 元素也可以包含country 属性。
PurchaseOrderType 类型定义包含了一个orderDate 属性声明,就像在UAAddress 中的country 属性声明一样,它被标识为一个简单类型,实际上,所有的属性声明必须引用简单类型 。这是因为不像元素声明,属性不能包含其他元素或者其他属性 。迄今为止描述的元素声明对于每一个名字都和一个现存的类型定义相关联。然而有时候,使用一个现存的元素比定义一个新的元素更方便。
<xsd:element ref="comment" minOccurs="0"/>
这个声明定义引用了一个现存的元素comment ,该元素在购买订单模式文档中的其他地方有定义。一般的来说,ref 属性的值必须指向一个全局元素,且应当是在下面声明的而不是作为复合类型定义的一部分声明的 。这个声明的结果为一个叫comment 的元素可以出现在实例文档的关于这个定义的相关部分中,他的内容必须和那个被引用的元素的类型一致,在这个情况下是 "string" 。
约束
在前面中的元素声明中minOccurs 属性的值为0 ,所以comment 元素在PurchaseOrderType 类型中是一个可选项。一般,当minOccurs 的值为1 或者>1 ,一个元素就必须出现。一个元素可以出现的最大数量由声明中的maxOccurs 属性所决定的。这个值也许是一个正的整型如41 ,或者以"unbounded" 的形式来表明不限最大的出现数量。minOccurs 和maxOccurs 属性的默认值是1 。因此,当一个元素如comment ,没有定义maxOccurs 属性,元素不可以出现超过一次。如果你仅仅指定了minOccurs 属性的值,它必须小于等于maxOccurs 的默认值,也就是说minOccurs 如果单独出现,其取值只能为0 或者1 。同样的,如果你只指定了maxOccurs 属性,它必须大于等于minOccurs 的默认值,也就是必须取值为1 或者>1 。如果两个属性都被省略了,那么元素必须出现且仅出现一次。
而对于属性而言,它可以出现一次或者根本不出现,指定属性出现次数的语法与元素的语法有所不同。特别的,属性声明能够使用一个use 属性来指明属性是否需要出现 ( 参阅po.xsd 中partNum 属性的声明) 。
属性和元素的默认值都是使用default 属性来声明,不过这个属性在不同的情况下有些许不同的语义表示结果。当一个属性使用默认值来声明的时候,若属性在实例文档中出现了,那属性的值就是实例文档中出现的那个值。若属性没有在实例文档中出现,这个属性的值等于声明中default 属性的值。需要注意的是,属性默认值只在属性本身为" 可选的" 时候才有意义,如果在声明中,既指定了默认值,又设置了use 属性(除"optional" 以外的其他值)的话,处理器就会产生错误。
而当一个元素声明中有默认值定义的时候,模式处理器在处理默认的元素值的时候,与处理属性的默认值不同。若实例文档中元素出现带有自身内容的时候,其值就是文档中元素的内容,若元素没有内容,那处理器就认为这个元素的值( 内容) 等于声明中default 属性的值。然而,如果元素在实例文档中并没出现,模式处理器则不认为该元素出现。总而言之,元素和属性默认值之间的区别可以认为是:当属性不出现时默认的属性值被应用,当元素内容为空的时候,默认的元素值被应用 。
属性和元素声明中都使用到fixed 属性来确保属性和元素被设置为特殊的值,如 po.xsd 中包含了一个country 属性的声明,这个country 属性声明就有一个fixed 属性,值为CN 。这个声明意味着在实例文档中 country 属性的出现是可选的(use 属性的默认值是optional) ,但是如果属性出现,他的值必须为"CN" ,如果属性不出现,模式处理器将自动设置country 属性值为"CN" 。需要注意的是,fixed 值的概念和default 值的概念是互斥的 。所以如果同时声明fixed 和 default 属性就会令模式处理器产生一个错误。
在元素和属性声明中用于约束他们出现行为的属性的值概括如下表中:
元素- 使用 |
属性- 使用 |
注解 |
(1, 1) -, - |
required, -, - |
元素/ 属性必须出现一次,它可以有任何值 |
(1, 1) 37, - |
Required, 37, - |
元素属性必须出现一次,他的值为37 |
(2, unbounded) 37, - |
无相关描述 |
元素必须出现两次或者多次,他的值必须为37 ,一般说来,minOccurs 和maxOccurs 可以为正数,maxOccurs 可以为"unbounded" |
(0, 1) -, - |
optional, -, - |
元素/ 属性可以出现一次,他可以有任何值 |
(0, 1) 37, - |
optional, 37, - |
元素/ 属性可以出现一次,如果出现他的值必须为37 ,如果不出现他的值为37 |
(0, 1) -, 37 |
optional, -, 37 |
元素/ 属性可以出现一次,如果不出现值为37 ,否则他的值为给出的值 |
(0, 2) -, 37 |
无相关描述 |
元素可以出现一次、两次或者更本不出现,如果元素不出现,则默认值不发生作用,如果出现并且他为空元素,则其值为37 ,否则值为实例中给出的值。一般说来,minOccurs 和maxOccurs 可以为正数,maxOccurs 可以为"unbounded" |
(0, 0) -, - |
prohibited, -, - |
元素/ 属性必须不出现 |
在这里,值得注意的是,在全局的元素和属性声明中,minOccurs 、maxOccurs 、use 都没有出现。
全局元素和属性
全局的元素和属性是在全局声明时被建立的,全局声明都是作为元素的子元素出现的。一旦经过定义,全局元素或者全局属性可以像先前我们描述的那样使用 ref 属性在一个或多个声明中引用。一个引用全局元素的声明,允许被引用的元素在实例文档中出现在引用这个元素的声明相关的元素中。所以,举例来说, po.xml 中的comment 元素同样可以在shipTo 、billTo 和items 元素中出现,因为引用comment 的复合类型定义的声明同样出现在这三个元素的声明中。
一个全局元素的声明也允许元素在实例文档中以顶级的文档元素出现,因此 purchaseOrder 元素,在po.xsd 中是作为一个全局元素声明的,能够作为po.xml. 中的顶级元素出现。值得注意的是,基于这个基本原理, comment 元素作为顶级元素出现在文档如po.xml 中也是被允许的。
关于使用全局的元素和属性有很多忠告,其中一个忠告是全局的声明不能够包含引用 。全局的声明定义不能包含ref 属性,他们必须使用type 属性( 或者,像我们简短描述的,跟随一个匿名的类型的定义) 。第二个忠告是约束不能够放在全局声明中 。尽管他们能够放在引用全局声明的局部声明中。换句话说,全局声明不能够包含minOccurs 、maxOccurs 、或者use 属性。
命名冲突
现在已经讨论了如何定义新的复合类型( 比如PurchaseOrderType) ,声明元素( 比如purchaseOrder) 和声明属性( 如 orderDate) 。这些行为一般都包含着命名,因此,问题自然就出现了:如果给两个对象赋予同样的名称会如何?答案取决于问题中的两个对象,尽管,一般来说两个对象越相近,他们越有可能引起冲突。
这里有一些例子来说明什么时候同样的名称会导致问题的出现
最后一个例子中之所以没有命名冲突发生的原因是,因为他们属于不同的命名空间。