理解XML Schema: XML Schema进阶(II)
转载自:http://www.ibm.com/developerworks/cn/xml/x-schema/part4/index.html
?
在XML Schema初步这篇文章中描述的购买订单模式文档是包含在一个单独的文档中的,并且这个模式文档中的大多数构造,比如元素声明和类型定义,其构造相对都是很随意的。实际上,模式文档作者常常会想通过多个文档组合模式文档的构造,并且可以基于现有的类型定义来建立新的类型定义。在本文中,我们将来考察这样的构造和创建的机制。
由多个文档组成的模式文档
随着模式文档变得越来越大,为了便于维护、访问控制并兼顾可读性,常常会考虑把他们的内容分在几个模式文档中。基于这些原因,我们把模式文档中关于address的构造从先前的po.xsd中取出,并放在一个新的文件address.xsd中。修改后的购买订单模式文档称为ipo.xsd:
而包含address结构的文件address.xsd为:
多样化购买订单的结构和address的结构现在被包含在两个模式文档ipo.xsd 和 address.xsd里,为了把这两个结构作为国际化的购买订单模式文档的一部分,换句话来说,就是要在国际化的购买订单的命名空间中包含它们。ipo.xsd包含include元素:
回页首
通过扩展来派生类型
为了建立我们自己的address结构,我们通常通过建立一个称为address的复合类型来开始(参阅前面的address.xsd)。address类型包含一些地址的基本元素:姓名、街道和城市(这样的定义不一定对所有的城市都适用,但能够满足我们这个例子的目的)。从这一个基础的复合类型出发,我们可以得到两个新的复合类型,他们包含所有在源类型中原有的定义,同时还添加了用来特指在美国和英国使用的地址的附加元素。我们在这里通过扩展现有的类型来得到新的类型(复合类型)的技术和我们在前面的文章中扩展简单类型使用的技术是一样的。两者的不同点仅仅在于这里我们的基类型是复合类型而前面的章节中基类型是简单类型而已。
在这里,我们定义了两个新的类型,USAddress和UKAddress,使用complexType类型。另外,我们指明新类型的内容模型是复杂的,因此使用complexContent元素来包含下层子元素。并且我们指明我们通过extension元素的base属性来扩展基类型address。
当一个复合类型是通过扩展而被派生的时候,他的有效内容模型是基类型的内容模型加上在这个类型派生的过程中指定的内容模型。甚至,这两个内容模型将可被看作是一个有序组的两个子模型。在UKAddress这个例子中,UKAddress的内容模型是address的内容模型加上postcode元素的声明和一个exportCode属性。具体的来说,通过派生定义的UKAddress与下面的这个在一个文档中进行完整定义的UKAddress模式是等价的:
回页首
在实例文档中使用派生类型
在我们这个例子里,购买订单是应对客户订单的响应而生成的,这一响应可能需要包含不同国家的不同形式的送货地址或者支付地址。下面的国际化的购买订单实例ipo.xml显示了这种情况下的示例:货物将被运到英国而帐单则是寄到美国。显然,较好的方案是国际化的购买订单的模式文档并不要去清楚地说明每个国际化的支付和送货地址可能的组合。甚至我们能够仅仅通过建立新的Address类型的派生,来添加国际化地址的复合类型。XML Schema允许我们将billTo和ShipTo元素定义为Address类型(参阅ipo.xsd),而在使用Address类型实例的地方使用国际化的Address类型的实例。换句话说,如果在文档中一个地方需要Address类型,然后在这里出现的内容与UKAddress类型相符的话,这个实例文档仍将是正确的(在这里我们假设UKAddress内容本身是正确的)。为了使XML Schema的这个特性工作,并为了能识别这个类型到底是从何派生而来,在实例文档中的派生类型必须能被显式地识别。类型是通过xsi:type属性来识别,这个属性是XML Schema实例命名空间的一部分。在例子ipo.xml中,派生类型UKAddress和USAddress的使用是通过设置的xsi:type属性来识别的。
回页首
通过约束来派生复合类型
除了通过扩展内容模型来派生新的复合类型外,也可以通过约束现有类型的内容模型来得到新的类型。复合类型的约束在概念上和对简单类型的约束是一样的,不同点仅仅在于对复合类型的约束需要包括一个类型声明而不仅仅在简单类型约束中是一个简单类型值的可接受范围。一个通过约束得到的复合类型和基类型非常相象,不同点仅仅在于他的声明比在基类型中的声明有更多的限制。实际上,新类型所表现的值是基类型所表现值的一个子集(和简单类型约束的情况一样)。换句话说,一个为兼容基类型值而开发的应用如果接受到约束后类型的值就不会有任何的兼容性问题。
举例来说,假设在国际化的购买订单中我们想要更新item列表的定义,以使得在一个订单中至少包含一个item,而在ipo.xsd 中的模式允许一个items元素没有任何子item元素出现。为了建立我们这个新的ConfirmedItem类型。我们使用常用的方法来建立这个新的类型,通过指明它是从基类型items限制而得到的,并且对item元素出现的最小数量提供了一个新的值(更进一步的限制)。需要注意的是,由约束而派生出来的类型必须重复定义所有要包含在派生类型中的原基类型的定义组成部分。
通过更新,ConfirmItems被定义为至少要包含一个子元素,而不允许包含零个元素,当然也不是至少要包含多个子元素。这一定义的实质就是,将子元素出现数量的允许范围从最少零个改变为最少一个。值得注意的是,所有ConfirmItems类型元素也被视为item类型的元素,被处理器接受。
为了进一步显示约束,我们使用下面表格中的几个例子来显示了类型定义中的元素和属性声明是如何被约束的(这个表格展示了元素的约束语法)。
基类型约束注解?default="1"在先前没有给出任何值定义的地方设置一个默认值。?fixed="100"在先前没有给出任何值定义的地方设置一个固定值。?Type="string"在先前没有给出类型定义的地方定义一个类型。(minOccurs, maxOccurs)(minOccurs, maxOccurs)?(0, 1)(0, 0)将一个原本可选的组件排除在派生类型之外,这一声明的实现也许可以通过限制类型定义从而忽略成分的声明。(0, unbounded)(0, 0) (0, 37)?(1, 9)(1, 8) (2, 9) (4, 7) (3, 3)?(1, unbounded)(1, 12) (3, unbounded) (6, 6)?(1, 1)-不能进一步限制minOccurs 或者maxOccurs回页首
重新定义类型和组
在由多个文档组成的模式文档中我们描述了如何通过具有相同目标命名空间的外部模式文件来提供额外的定义和声明。include机制使你能够原样地使用外部建立的模式组件,而无需作任何修改。在本节之前,我们描述了如何通过扩展和约束来得到新的类型定义,而我们在这里描述的redefine机制将允许你重新定义从外部模式文件获得的简单和复合类型、元素组和属性组。像include机制一样,redefine同样需要满足以下条件:引入的外部组件与重定义的模式文档应当具有相同的目标命名空间,当然不包含目标命名空间的外部模式组件也可以被重新定义。然后,被重定义的组件部分就成为重定义模式的目标命名空间的一部分。
为了演示redefine机制,在国际化的购买订单ipo.xsd中我们使用它来代替include机制,并且我们使用这一机制来修正包含在address.xsd中的复合类型Address的定义:
redefine元素表现的形式非常像include元素。它从address.xsd文件中引入其包含的所有的声明和定义。复合类型Address的定义使用常见的扩展语法,来添加country元素到Address类型的定义中去。然而,这里比较特别的是,我们应该注意到它的基类型也是Address,这是redefine所特有的。在除redefine元素之外的地方,任何类似的试图为定义的类型取与其基类型有同样名字,从而定义复合类型的情况(即使在同样的命名空间中)将会导致出错。但是在redefine的场合下则不会产生错误,在这里Address的扩展定义仅仅是Address的重新定义。
现在Address类型被重新定义了,这一扩展定义被应用到所有使用Address的模式成分。举例来说,address.xsd包含从Address类型派生出来的国际化的地址类型的定义,这个派生出来的类型将重新应用重定义后的Address类型作为它的基类型,我们可以通过下面的实例来查看这一方式的应用:
我们的这个例子是周全地构造的,以使得这样重新定义的Address类型不会和从原始的Address定义中派生的类型相冲突。但是需要注意的是,使用redefine还是很容易会造成冲突。举例来说,如果派生的国际化的地址类型是通过添加一个country元素来扩展Addrss类型,然后我们又重新定义了Address类型,同样是添加了一个同样名字的元素到Address的内容模型中去,那此时冲突就会发生。一般来说,在一个内容模型中的两个同名元素(并且在一个目标命名空间内),如果具有不同类型,那么就会产生非法错误,因此此时试图按照这种方式重定义Address类型就将会导致错误。一般的来说,redefine没有提供避免类似错误的保护,它应该被慎重的使用。
回页首
置换组
XML Schema提供了一个机制,称为置换组(Substitution Groups),允许原先定义好的元素被其他元素所替换。更明确的,这个置换组包含了一系列的元素,这个置换组中的每一个元素都被定义为可以替换一个指定的元素,这个指定的元素称为头元素(Head Element),需要注意的是头元素必须作为全局元素声明。具体地来看,我们声明了两个元素customerComment和shipComment,并且将它们分配到一个置换组,该组的头元素为comment。因此customerComment和shipComment能够在任何能够使用comment的地方使用。在置换组中的元素必须具有与头元素相同的类型,或者,它们的类型是头元素类型的派生类型。为了声明这两个新元素,并且使它们能替换comment元素,我们使用如下的语法:
当这部分声明被添加到了国际化的购买订单模式文档中后,shipComment和customerComment就能够在实例文档中替换comment了,下面是一个实例文档的例子:
回页首抽象元素和类型
XML Schema提供了一个机制来强迫替换一个特定的元素或者类型。当一个元素或者类型被声明为"abstract"时,那么它就不能在实例文档中使用。当一个元素被声明为"abstract"的时候,元素的置换组的成员必须出现在实例文档中。当一个元素相应的类型被定义声明为"abstract"时,所有关联该元素的实例必须使用"xsi:type"来指明一个类型,这个类型必须是非抽象的,同时时在定义中声明的抽象类型的派生类型。
我们再来查看一下图 Error! No text of specified style in document. 10中描述的置换组的例子,如果,特别的,我们要求在实例中不允许使用comment元素也许可以使这个模式定义更加清晰,这样实例就必须使用customerComment和shipComment元素来代替原来的comment元素。为了声明comment元素为抽象元素,我们需要修改相应的在国际化的购买订单模式文档ipo.xsd中的原始声明,将其改为如下形式:
随着comment元素被声明为抽象元素,国际化的购买订单的实例现在只能包含customerComment元素和shipComment元素才是有效的。把一个元素声明为抽象元素,需要使用置换组。声明一个类型为抽象类型则只要在实例文档中使用从该抽象类型派生的类型就可以了(当然,还要通过xsi:type属性来识别)。考虑下面的模式定义:
transport元素并不是抽象元素,因此它能够在实例文档中出现。然而因为它的类型定义是抽象类型的,如果在实例中没有使用"xsi:type"属性来引用派生类型,那么它将不能出现在实例文档中。这意味着下面的这个例子是无法通过模式校验的:
上述例子无法通过模式校验的原因是因为transport元素的类型是抽象的,而下面的这个实例片断则是能够通过模式校验的:
因为它使用了一个非抽象类型Car来替换Vehicle,并且非抽象类型Car是类型Vehicle的派生类型。
回页首
控制对派生类型的创建和使用
迄今为止,我们已经能够不受任何限制地派生类型,同时在实例文档中自由地使用新的派生类型。然后在实际应用中,模式的作者有时候会需要控制从某些特定类型的派生,并且需要控制在实例文档中使用这些派生类型。
XML Schema提供了一组机制来控制类型的派生引出。其中一个机制员允许模式的作者来指定特殊的复合类型,新的类型将不可以从这些被指定的复合类型派生,可能是不能通过限制派生、或是不能通过扩展来派生,又或者是所有的方式都不行。为了显示这点,假设我们想要限制Address类型通过约束(Restriction)的方式实施派生,因为我们可能打算只将Address类型用为扩展类型的基类型,比如USAddress和UKAddress的基类型都是Address类型。为了防止任何类似的非扩展的派生,我们需要稍许修改Address类型的原始定义:
通过对final 属性赋以"restriction"这个值,能够阻止一切通过约束的派生。而如果想阻止一切派生或是要阻止通过扩展实施派生,则分别可以通过final属性值"#all"和"extension"来实现。此外,在模式文件的根元素schema元素中有一个可选的finaldefault属性,它的值能够取为final属性所允许的几个值之一。指定finalDefault属性的值的效果等于在模式文档中每个类型定义和元素声明中指定final属性,同时其值为finalDefault属性的值。
而另一个类型派生的控制机制则是应用于简单类型方面的类型派生。当定义一个简单类型时,我们可以使用fixed属性对它的所有定义参数进行修饰,以阻止这些参数在类型派生中被修改。举个例子,我们重新定义postcode这个简单类型:
当这个简单类型被定义后,我们能够派生一个新的邮编类型,在其中我们使用了一个没有在基类型定义中固定的参数:
除了控制类型派生的机制以外,XML Schema还提供了一个控制派生类型以及置换组在实例文档中使用的机制,在0 在实例文档中使用派生类型中我们描述了如何将派生类型USAddress和UKAddress应用在实例文档中的shipTo和billTo元素。这些派生类型能够代替Address类型提供的内容模型,因为它们都是从Address类型派生出来的。然而,被派生类型替换是能够被控制的,具体的说,能够被类型定义中的block属性所控制。举例来说,如果我们想组织任何通过约束而导出的派生类型在Address类型出现的地方被使用而替换Address类型(也许为了同样的理由我们定义Address的final="restriction"),我们可以修改Address的初始定义,并修改为:
取值为"restriction"的block属性将阻止在实例文档中使用通过约束的派生类型来替换Address类型。然而,它不会阻止UKAddress和USAddress来替换Address,因为它们是通过扩展而派生的。为了阻止所有的派生类型或者通过扩展而获得的派生类型去替代基类型,可以分别使用值"#all"和"extension"。和final属性一样,在模式文档的根元素schema元素里有一个可选的属性blockDefault,它的值为block属性所允许的值中的一个。指定blockDefault属性的作用等价于在模式文档中为每个类型定义和元素声明指定block属性。