你的分享就是我们的动力 ---﹥

XML Schema——理解XML Schema: XML Schema 初步(一)

时间:2011-10-09 22:00来源:www.chengxuyuans.com 点击:

本文章系列是XML Schema的一个从入门到进阶的基本教程。内容主要翻译整理了W3C关于XML Schema的入门级规范:XML Schema Part 0: Primer( http://www.w3.org/TR/xmlschema-0/),同时译者添加了一些个人的编注,并重新整理安排了章节。奉献给大家,旨在让更多的读者来了解,熟悉XML Schema. XML Schema是W3C的推荐标准,于2001年5月正式发布,经过数年的大规模讨论和开发,终于最终奠定下来,使得XML建模有了一个国际标准。XML Schema一确定下来,立刻成为全球公认得首选XML环境下的建模工具,已经基本取代了DTD在XML刚刚成为W3C推荐标准时的地位。由于XML是 SGML的一个子集,因此它也继承了SGML世界中用于建模的DTD,当时使用DTD的好处是可以利用大量的在SGML世界中现有的DTD工具,使得开发应用代价维持在一个相对较低的水平。然而,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="1999-10-20">
    <shipTo country="US">
        <name>Alice Smith</name>
        <street>123 Maple Street</street>
        <city>Mill Valley</city>
        <state>CA</state>
        <zip>90952</zip>
    </shipTo>
    <billTo country="US">
        <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>1999-05-21</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 2000 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="US"/>
 </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="US"/>
 </xsd:complexType>
 
    这个定义的结果将是,在实例文档中出现的任何类型声明为USAddress的元素(比如在po.xml中的shiptTo)必须包含五个元素和一个属性。这些元素必须被命名为name、street、city、state和zip,这些名称就如同在模式声明中name属性的值所指的那样。并且这些属性必须按照模式声明中的同样的顺序出现。前四个元素必须包含一个字符串元素内容而第五个必须包含一个十进制数字类型的元素内容。声明为USAddress类型的元素可以带有一个country属性,该属性必须包含字符串"US".

    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或者更多时,一个元素就必须出现。一个元素可以出现的最大数量由声明中的maxOccurs属性所决定的。这个值也许是一个正的整型如41,或者以"unbounded"的形式来表明不限最大的出现数量。minOccurs和maxOccurs属性的默认值是1.因此,当一个元素如comment,没有定义maxOccurs属性,元素不可以出现超过一次。如果你仅仅指定了minOccurs属性的值,它必须小于等于maxOccurs的默认值,也就是说minOccurs如果单独出现,其取值只能为0或者1.同样的,如果你只指定了maxOccurs属性,它必须大于等于minOccurs的默认值,也就是必须取值为1或者更多。如果两个属性都被省略了,那么元素必须出现且仅出现一次。

    而对于属性而言,它可以出现一次或者更本不出现,不会有其他的出现次数,所以指定属性出现次数的语法与元素的语法有所不同。特别的,属性声明能够使用一个use属性来指明属性是否需要出现(参阅po.xsd中partNum属性的声明)。

    属性和元素的默认值都是使用default属性来声明,不过这个属性在不同的情况下有些许不同的语义表示结果。当一个属性使用默认值来声明的时候,如果属性在实例文档中出现了,那么属性的值就是属性在实例文档中出现的那个值。如果属性没有在实例文档中出现,模式处理器将认为这个属性的值等于声明中default属性的值。需要注意的是,属性默认值只在属性本身为"可选的"时候才有意义,如果在声明中,即指定了默认值,同时又设置了use属性为除"optional"以外的其他值的话,处理器就会产生错误。

    而当一个元素声明中有默认值定义的时候,模式处理器在处理默认的元素值的时候,与处理属性的默认值相比有一些不同。如果实例文档中元素出现且带有自身的内容的时候,元素的值就是是实例文档中元素的内容,如果元素没有内容,那么模式处理器就认为这个元素的值(内容)等于声明中default属性的值。然而,如果元素在实例文档中并不出现,模式处理器则更本不认为该元素出现。总而言之,元素和属性默认值之间的区别可以认为是:当属性不出现时默认的属性值被应用,当元素内容为空的时候,默认的元素值被应用。

    属性和元素声明中都使用到fixed属性来确保属性和元素被设置为特殊的值,如po.xsd中包含了一个country属性的声明,这个country属性声明就有一个fixed属性,值为US.这个声明意味着在实例文档中country属性的出现是可选的(use属性的默认值是optional),但是如果属性出现,他的值必须为"US",如果属性不出现,模式处理器将自动设置country属性值为"US".需要注意的是,fixed值的概念和 default值的概念是互斥的。所以如果同时声明fixed和default属性就会令模式处理器产生一个错误。

    在元素和属性声明中用于约束他们出现行为的属性的值被概括地罗列在下表中:

元素 - 使用(minOccurs, maxOccurs) fixed, default  属性 - 使用use, 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)。这些行为一般都包含着命名,因此,问题自然就出现了:如果我们给两个对象赋予同样的名称会如何?答案取决于问题中的两个对象,尽管,一般来说两个对象越相近,他们越有可能引起冲突。

    这里有一些例子来说明什么时候同样的名称会导致问题的出现。如果两个对象都是类型,如果我们去定义了一个复合类型称为USStates,同时又定义了一个简单类型称为USStates,此时就出现了冲突。如果两个对象是类型和元素或者是类型或者属性,当我们定义了一个复合类型叫USAddress,同时我们又定义了一个元素称为USAddress,此时是没有冲突发生的。如果两个对象是不同类型的元素(一般的,并非是全局元素),当我们声明了一个元素名字作为USAddress类型的一部分并且第二个元素名字作为item类型的一部分,此时就没有冲突(类似的元素有时候称为局部元素声明)。最后,如果两个对象都是类型,你自己定义了其中的一个,而XML Schema规范内置定义了另外的一个,比如你自己定义了一个简单类型称为decimal,那么此时没有冲突发生。最后一个例子中之所以没有命名冲突发生的原因是,因为他们属于不同的命名空间,我们将在后讨论在XML Schema中的XML命名空间的使用。


简单类型

   在购买订单模式文档中,几个元素和属性被声明为简单类型,其中一些简单类型如 string 和decimal是XML Schema中内置的。而其他的一些则是源于(如果使用对象技术的语言,就是继承)内置的类型。举例来说,partNum属性的类型称为 SKU(Stock Keeping Unit),是源于string.的。内置的简单类型和他们的后继版本都能够被用在所有的元素和属性声明中,下面的列表列举了所有的在XML Schema中内置的简单类型及其相应的例子。

简单类型  值示例  备注  
string Confirm this is electric   
normalizedString Confirm this is electric 参见 (3) 
token Confirm this is electric 参见 (4) 
byte -1, 126 参见 (2) 
unsignedByte 0, 126 参见 (2) 
base64Binary GpM7   
hexBinary 0FB7   
integer -126789, -1, 0, 1, 126789 参见 (2) 
positiveInteger 1, 126789 参见 (2) 
negativeInteger -126789, -1 参见 (2) 
nonNegativeInteger 0, 1, 126789 参见 (2) 
nonPositiveInteger -126789, -1, 0 参见 (2) 
int -1, 126789675 参见 (2) 
unsignedInt 0, 1267896754 参见 (2) 
long -1, 12678967543233 参见 (2) 
unsignedLong 0, 12678967543233 参见 (2) 
short -1, 12678 参见 (2) 
unsignedShort 0, 12678 参见 (2) 
decimal -1.23, 0, 123.4, 1000.00 参见 (2) 
float -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN 等同于单精度32位浮点数,其中”NaN”表示”不是一个数字”。参见 (2) 
double -INF, -1E4, -0, 0, 12.78E-2, 12, INF, NaN 等同于双精度64位浮点数。参见 (2) 
boolean true, false1, 0   
time 13:20:00.000, 13:20:00.000-05:00 参见 (2) 
dateTime 1999-05-31T13:20:00.000-05:00 这个时间表示的含义是:1999年5月31日美东标准时间下午1:20,注意后面的-05:00表示这个时间比格林尼治时间早5个小时。参见 (2) 
duration P1Y2M3DT10H30M12.3S 这表示经过了1年2个月3天又10个小时30分钟12.3秒。 
date 1999-05-31 参见 (2) 
gMonth --05-- 表示5月。参见 (2) (5) 
gYear 1999 表示1999年。参见 (2) (5) 
gYearmonth 1999-02 表示1999年2月,而并不关心是几号。参见 (2) (5) 
gDay ---31 表示31号。参见 (2) (5) 
gMonthDay --05-31 表示每个5月31号。参见 (2) (5) 
Name shipTo XML 1.0的Name类型 
QName po:USAddress XML命名空间的QName类型 
NCName USAddress XML命名空间的NCName类型,即一个不带命名空间前缀修饰的QName 
anyURI http://www.example.com/, http://www.example.com/doc.html#ID5   
language en-GB, en-US, fr XML 1.0中定义的合法语言代码 
ID   XML 1.0中定义的ID属性类型。参见 (1) 
IDREF   XML 1.0中定义的IDREF属性类型。参见 (1) 
IDREFS   XML 1.0中定义的IDREFS属性类型。参见 (1) 
ENTITY   XML 1.0中定义的ENTITY属性类型。参见 (1) 
ENTITIES   XML 1.0中定义的ENTITYS属性类型。参见 (1) 
NOTATION   XML 1.0中定义的NOTATION属性类型。参见 (1) 
NMTOKEN US, Brésil XML 1.0中定义的NMTOKEN属性类型。参见 (1) 
NMTOKENS US UK, Brésil Canada Mexique XML 1.0中定义的NMTOKENS属性类型,即一个使用空格作为元素分隔的NMTOKEN列表。参见 (1) 
注意:

    为了在XML Schema和XML 1.0 DTD之间保持兼容,简单类型ID、IDREF、IDREFS、ENTITY、ENTITIES、NOTATION、NMTOKEN、NMTOKENS只能用在属性定义中; 
这个类型的值能够表示为不止一种格式,如100和1.0E2都是有效的float格式,它们都表示”一百”这个数值。然而,对于这个类型而言,已经为其定义了规范的格式规则。 
换行符、制表符和回车符在normalizedString中将会在处理前被转化成空格字符 
作为normalizedString,相邻的空格字符将会被合并为一个空格字符,第一个和最后的空格将被移走 
“g”前缀表示罗马历的时间和年代。 
 

    新的简单类型通过从现有的简单类型(内置的简单类型以及源于内置简单类型的简单类型)引出定义。特别的,我们通过重新约束一个现存的简单类型来引出一个新的简单类型。换句话说,新类型的合法值范围是现有类型的值范围的子集。我们使用simpleType元素来定义和命名新的简单类型,我们使用 restriction元素来指出现有的基类型。并且用它来标识约束值范围的细节。

   假设我们希望建立一个新的整数类型称为myInteger,他的值范围为10000到99999。我们的定义应当基于简单类型integer,然后定义他的值范围为10000到99999。为了定义myInteger,我们这样来约束integer的范围(参见下图):


使用基类型来定义新的简单类型

<xsd:simpleType name="myInteger">
  <xsd:restriction base="xsd:integer">
    <xsd:minInclusive value="10000"/>
    <xsd:maxInclusive value="99999"/>
  </xsd:restriction>
</xsd:simpleType>

转载注明地址:http://www.chengxuyuans.com/xml/22577.html

推荐文章