UML软件工程组织

W3C XML 架构设计模式:处理改变
来源:www.microsoft.com
摘要:W3C XML 架构提供了一种机制,用来指定 XML 文档结构和约束。为了支持常用模式,本文主要讨论以模块化方式构建灵活的、允许基本数据和架构进化的架构技术。(本文包含一些指向英文站点的链接)
简介

W3C XML 架构提供了一种机制,用来指定 XML 文档结构和约束。随着 W3C XML 架构应用范围的日渐扩大,有些使用模式已经十分常用。本文主要讨论以模块化方式构建灵活的、允许基本数据和架构进化的架构技术。

当要处理的 XML 文档的结构有可能随应用程序的更新而改变,但仍需要使用原始架构进行验证时,设计支持数据进化的架构非常有用。当多个实体共享的 XML 文档的格式可能不断改变、但又未收到更新后的架构时,这就尤其重要。

还有一些情况下处理架构改变也很重要。例如,确保旧版本的 XML 文档可以由新版本的架构验证就是一个例子。另一种情形是多个实体共享的 XML 文档的结构相似但域有明显的差别。例如,W3C XML Schema Primer 中的 address.xsd 示例,描述了一种可以扩展一般地址格式以包括区域地址格式的情形。

使用通配符创建开放式内容模型

W3C XML 架构提供了通配符 xs:any xs:anyAttribute,用来使指定命名空间中的元素和属性出现在内容模型中。使用通配符,架构作者既能使内容模型可扩展,同时又能保持对 XML 文档中的元素和属性的控制。

通配符最重要的属性是 namespaceprocessContents 属性。namespace 属性用于指定通配符匹配的元素或属性可以来自哪个命名空间。XML Schema Primer 中的 Namespace Attribute In Any 表给出了 namespace 属性的可能值。processContents 属性用于指定需要验证 XML 内容是否与通配符匹配以及其匹配程度。

下面是一个架构示例,它使用通配符使有效的实例能够添加架构中未指定的元素和属性:

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
     elementFormDefault="qualified"> 
       <xs:element name="Customer"> 
        <xs:complexType> 
    <xs:sequence>
      <xs:element name="FirstName" type="xs:string" />
      <xs:element name="LastName" type="xs:string" />
      <xs:any namespace="##targetNamespace" processContents="strict" 
            minOccurs="0" maxOccurs="unbounded" />
      <xs:any namespace="##other" processContents="lax" minOccurs="0" 
            maxOccurs="unbounded" />
    </xs:sequence>
         <xs:attribute name="customerID" type="xs:integer" />
         <xs:anyAttribute namespace="##any" processContents="skip" />
   </xs:complexType>
       </xs:element> 
       <xs:element name="PhoneNumber" type="xs:string" />
       <xs:element name="FrequentShopper" type="xs:boolean" />
      </xs:schema> 

此架构描述了一个 Customer 元素,它依次包含两个元素 FirstNameLastName 和一个属性 CustomerID。此外,使用两个通配符(xs:any 元素)来指定客户名称元素后可以出现来自 urn:xmlns:25hoursaday-com:customer 命名空间的零个或多个元素,后面紧跟来自其他命名空间的零个或多个元素。属性通配符(xs:anyAttribute 元素)用于指定 Customer 元素可以具有来自任意命名空间的属性。现在,通配符使实例文档作者能够修改其 XML 文档以符合特定的需要,同时还使内容模型能够满足一组最小约束。下面是以上架构的有效文档示例。

     <Customer  customerID="12345" xmlns="urn:xmlns:25hoursaday-com:customer">
      <FirstName>Dare</FirstName>
      <LastName>Obasanjo</LastName>
     </Customer>

     示例 1
     
     <cust:Customer  customerID="12345" numPurchases="17" 
            xmlns:cust="urn:xmlns:25hoursaday-com:customer">
      <cust:FirstName>Dare</cust:FirstName>
      <cust:LastName>Obasanjo</cust:LastName>
      <cust:PhoneNumber>425-555-1234</cust:PhoneNumber>
     </cust:Customer>

     示例 2

     <cust:Customer  customerID="12345" numPurchases="17" 
       xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
       xmlns:addr="urn:xmlns:25hoursaday-com:address" >
      <cust:FirstName>Dare</cust:FirstName>
      <cust:LastName>Obasanjo</cust:LastName>
      <cust:PhoneNumber>425-555-1234</cust:PhoneNumber>
      <addr:Address>2001 Beagle Drive</addr:Address>
      <addr:City>Redmond</addr:City>
      <addr:State>WA</addr:State>
      <addr:Zip>98052</addr:Zip>
     </cust:Customer>

     示例 3

第三个示例尤其适用于将来自多个词汇表的元素组合起来,允许用户使用不同的架构验证 XML 实例,而这些架构对来自无关命名空间的元素不加过问。这样,只能处理文档不同部分的应用程序能够验证和管理它们所了解的部分,而忽略其他部分,这一点对于可扩展性很重要。其次,如果实例文档的格式改变,且改变后的文档中出现了更多的客户信息,只要未从内容模型中删除原来声明的元素和属性(本例中为 FirstNameLastNamecustomerID),它们对原始架构以及任意的后续架构来说就仍然是有效的。

但是,在使用 xs:any 通配符时,也需要注意一些问题。首先是使用 xs:any 时很容易在无意中创建不确定的内容模型,而在架构中很难找出这种情况。以下就是这样的一个架构示例:

     
      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
     elementFormDefault="qualified"> 
       <xs:element name="Customer"> 
        <xs:complexType> 
    <xs:sequence>
      <xs:element ref="cust:FirstName" />
      <xs:element ref="cust:LastName" minOccurs="0" />
      <xs:any namespace="##targetNamespace" processContents="strict"   />   
    </xs:sequence>         
   </xs:complexType>
       </xs:element>    
       <xs:element name="FirstName" type="xs:string" />
       <xs:element name="LastName" type="xs:string"  />  
       <xs:element name="PhoneNumber" type="xs:string" />
      </xs:schema> 

上面的架构是不确定的,因为当遇到 LastName 元素时,验证程序不能区分序列是否结束,因为该元素既可以被验证为 FirstName 后可选的 LastName 元素,也可以根据通配符(允许出现来自 urn:xmlns:25hoursaday-com:customer 命名空间的任意元素)进行验证。

使用通配符时的另一个问题是要注意使用 namespace 属性 xs:anyxs:anyAttribute 的方式。尤其应当注意此属性的 ##other 值,XML Schema Primer 的 Namespace Attribute In Any 表中给出了其含义,“来自(其类型不是正在定义的类型的)目标命名空间的任意有效 XML”,该描述不完全准确。事实上,##other 表示“来自(其类型不是正在定义的类型的)目标命名空间的任意有效 XML,但无命名空间的元素除外”。

创建一个通配符,除了使用 xs:choice 包含的目标命名空间以外,还允许来自任意命名空间的元素。如下面的架构所示:

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
     elementFormDefault="qualified"> 
       <xs:element name="Customer"> 
        <xs:complexType> 
    <xs:sequence>
      <xs:element ref="cust:FirstName" />
      <xs:element ref="cust:LastName" />
      <!-- allow any element except those from target namespace -->
      <xs:choice minOccurs="0" maxOccurs="unbounded" > 
      <xs:any namespace="##other" processContents="strict"  />   
      <xs:any namespace="##local" processContents="strict"  />
      </xs:choice>
    </xs:sequence>         
   </xs:complexType>
       </xs:element>    
       <xs:element name="FirstName" type="xs:string" />
       <xs:element name="LastName" type="xs:string"  />  
      </xs:schema>           

值得注意的是,使用了一个选项的原因是通配符 namespace 属性的 ##other 值不能与其他值组合使用。

使用替换组和抽象元素增强灵活性

W3C XML 架构借用了面向对象编程中的许多概念,包括抽象类型、类型替换和多态。使用抽象元素和替换组,架构作者可以创建或利用定义了通用基本类型的架构,并将这些类型扩展为更适合专用领域,而不影响原始架构。

替换组包含的元素可以在 XML 实例文档中互换出现,就象面向对象编程语言中的子类型多态一样。替换组中的元素必须具有相同的类型,或者是同一类型层次结构中的成员。标记为“abstract”的元素声明表示在实例文档中必须用其替换组的成员来替换它。在下面的架构示例中,一个架构定义了一个抽象元素,另一个架构定义了一个元素用于替换该抽象元素,其类型源自抽象元素的类型。

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
          elementFormDefault="qualified"> 

       <xs:element name="Customers">
         <xs:complexType>
          <xs:sequence>
           <xs:element ref="cust:Customer" maxOccurs="unbounded" />
          </xs:sequence>
         </xs:complexType>
       </xs:element>

       <xs:element name="Customer" type="cust:CustomerType" abstract="true" /> 

        <xs:complexType name="CustomerType" > 
          <xs:sequence>
            <xs:element ref="cust:FirstName" />
            <xs:element ref="cust:LastName" />   
          </xs:sequence>         
          <xs:attribute name="customerID" type="xs:integer" />
         </xs:complexType>

       <xs:element name="FirstName" type="xs:string" />
       <xs:element name="LastName" type="xs:string"  />  
       <xs:element name="PhoneNumber" type="xs:string" />

      </xs:schema> 
     cust.xsd

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          xmlns:addr="urn:xmlns:25hoursaday-com:address" 
          targetNamespace="urn:xmlns:25hoursaday-com:address" 
          elementFormDefault="qualified"> 

       <xs:import namespace="urn:xmlns:25hoursaday-com:customer" 
            schemaLocation="cust.xsd"/> 

       <xs:element name="MyCustomer" substitutionGroup="cust:Customer" 
            type="addr:MyCustomerType"  /> 

        <xs:complexType name="MyCustomerType" > 
         <xs:complexContent>
          <xs:extension base="cust:CustomerType">
           <xs:sequence>
            <xs:element ref="cust:PhoneNumber" /> 
            <xs:element ref="addr:Address" />    
            <xs:element ref="addr:City" />    
            <xs:element ref="addr:State" />    
            <xs:element ref="addr:Zip" />    
           </xs:sequence>     
          </xs:extension>
         </xs:complexContent>
   </xs:complexType> 

   <xs:element name="Address" type="xs:string" />
   <xs:element name="City" type="xs:string" />
   <xs:element name="State" type="xs:string" fixed="WA" />   

   <xs:element name="Zip">
    <xs:simpleType>
     <xs:restriction base="xs:token" >
      <xs:pattern value="[0-9]{5}(-[0-9]{4})?"/>
     </xs:restriction>
    </xs:simpleType>
   </xs:element>

     </xs:schema> 
     my_cust.xsd

my_cust.xsd 架构包含一个 addr:MyCustomer 元素声明,代替 cust:Customer 元素出现在实例文档中。这样,cust:Customers 元素可以包含 addr:MyCustomer 子元素,而不是 cust:Customer 元素,因为后者是抽象的。my_cust.xsd 架构可以验证以下 XML 实例文档:

     <cust:Customers xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
              xmlns:addr="urn:xmlns:25hoursaday-com:address">
      <addr:MyCustomer customerID="12345" >
       <cust:FirstName>Dare</cust:FirstName>
       <cust:LastName>Obasanjo</cust:LastName>
       <cust:PhoneNumber>425-555-1234</cust:PhoneNumber>
       <addr:Address>2001</addr:Address>
       <addr:City>Redmond</addr:City>
       <addr:State>WA</addr:State>
       <addr:Zip>98052</addr:Zip>
       </addr:MyCustomer>
      </cust:Customers>

值得注意的是,与通配符一样,替换组也允许词汇表混合,但无需原始架构作者专门规划。架构作者只需将元素声明为全局元素,就可以参加替换组。然而,通过限制或扩展方式派生得到的内容模型的开放性不如使用通配符的内容模型好。尽管它看起来象缺点,但实际上是一个优点,因为它使架构作者能够更好地控制出现在有效 XML 实例文档中的其他内容的外观和结构。

通过 xsi:type 和抽象类型实现运行时多态

抽象类型是复杂类型定义,abstract 属性值为 true 时,表示实例文档中的元素不能属于该类型,而必须由通过限制或扩展方式派生出来的另一个类型来替换。只要新类型与元素的原始类型处于同一类型层次结构,就可以通过 xsi:type 属性改变 XML 实例文档中元素的类型。尽管无需将抽象类型与 xsi:type 结合起来使用,但是,当创建通用格式(大多数用户将从它创建专用领域的扩展)时,两者结合起来使用就会有些好处。在下面的架构示例中,声明了一个抽象类型和一个将该抽象类型用作类型定义的元素,随后的架构定义了从抽象类型派生的两种类型。

       <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
           elementFormDefault="qualified"> 

       <xs:element name="Customers">
        <xs:complexType>
         <xs:sequence>
          <xs:element ref="cust:Customer" maxOccurs="unbounded" />
         </xs:sequence>
        </xs:complexType>
       </xs:element>

       <xs:element name="Customer" type="cust:CustomerType" /> 

        <xs:complexType name="CustomerType" abstract="true" > 
         <xs:sequence>
           <xs:element ref="cust:FirstName" />
           <xs:element ref="cust:LastName" />
              <xs:element ref="cust:PhoneNumber" minOccurs="0"/>   
         </xs:sequence>         
         <xs:attribute name="customerID" type="xs:integer" />
        </xs:complexType>

        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string"  />  
        <xs:element name="PhoneNumber" type="xs:string" />

       </xs:schema> 
      cust.xsd

     <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
             elementFormDefault="qualified"> 

     <xs:include schemaLocation="cust.xsd"/> 

     <xs:complexType name="MandatoryPhoneCustomerType" > 
      <xs:complexContent>
        <xs:restriction base="cust:CustomerType">
         <xs:sequence>
           <xs:element ref="cust:FirstName" />
           <xs:element ref="cust:LastName" />
           <xs:element ref="cust:PhoneNumber" minOccurs="1" />
         </xs:sequence>           
        </xs:restriction>
       </xs:complexContent>
      </xs:complexType> 
    

        <xs:complexType name="AddressableCustomerType" > 
          <xs:complexContent>
          <xs:extension base="cust:CustomerType">
           <xs:sequence>    
            <xs:element ref="cust:Address" />    
            <xs:element ref="cust:City" />    
            <xs:element ref="cust:State" />    
            <xs:element ref="cust:Zip" />    
           </xs:sequence>     
          </xs:extension>
         </xs:complexContent>
        </xs:complexType> 

   <xs:element name="Address" type="xs:string" />
   <xs:element name="City" type="xs:string" />
   <xs:element name="State" type="xs:string" fixed="WA" />   

   <xs:element name="Zip">
    <xs:simpleType>
     <xs:restriction base="xs:string" >
      <xs:pattern value="\d{5}(-\d{4})?"/>
     </xs:restriction>
    </xs:simpleType>
   </xs:element>

     </xs:schema> 
     
     derived_cust.xsd

由架构验证的实例文档中的 Customer 元素使用 xsi:type 来声明它们的类型,即使在原始架构中被声明为抽象的 CustomerType 元素,也是如此。值得注意的是,基本类型的限制和扩展类型都可以成为 xsi:type 属性的目标。

     <cust:Customers xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
      <cust:Customer customerID="12345" xsi:type="cust:MandatoryPhoneCustomerType" >
       <cust:FirstName>Dare</cust:FirstName>
       <cust:LastName>Obasanjo</cust:LastName>
       <cust:PhoneNumber>425-555-1234</cust:PhoneNumber>
      </cust:Customer>
      <cust:Customer customerID="67890" xsi:type="cust:AddressableCustomerType" >
       <cust:FirstName>John</cust:FirstName>
       <cust:LastName>Smith</cust:LastName>
       <cust:Address>2001</cust:Address>
       <cust:City>Redmond</cust:City>
       <cust:State>WA</cust:State>
       <cust:Zip>98052</cust:Zip>
       </cust:Customer>
      </cust:Customers>

如果在 XQuery 1.0 和 XSLT 2.0 成为标准后,类型相关的 XML 处理变得很普遍,那么类型替换和多态将变得更加有用。为了进一步增强可扩展性,通过创建具有抽象类型的抽象元素,应用程序可以将类型层次结构中的抽象类型和抽象元素结合起来。

使用 xs:redefine 更新类型定义

W3C XML 架构提供了一种机制,可以在从某个类型定义有效地派生类型的过程中更新该类型定义。用于重定义的 xs:redefine 执行两项任务。首先是作为 xs:include 元素,从另一个架构引入声明和定义,并将它们成为当前目标命名空间的一部分。包含的声明和类型必须来自具有相同目标命名空间或无命名空间的架构。其次,可以使用与用新定义替换旧定义的类型派生相同的方式来重定义类型。

下面的类型重定义示例显示了两个具有包含关系的架构及其有效的实例文档。

      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
          elementFormDefault="qualified"> 

       <xs:element name="Customers">
        <xs:complexType>
         <xs:sequence>
          <xs:element ref="cust:Customer" maxOccurs="unbounded" />
         </xs:sequence>
        </xs:complexType>
       </xs:element>

       <xs:element name="Customer" type="cust:CustomerType" /> 

        <xs:complexType name="CustomerType"> 
          <xs:sequence>
           <xs:element ref="cust:FirstName" />
           <xs:element ref="cust:LastName" />
          </xs:sequence>         
          <xs:attribute name="customerID" type="xs:integer" />
         </xs:complexType>

       <xs:element name="FirstName" type="xs:string" />
       <xs:element name="LastName" type="xs:string"  />  
       <xs:element name="PhoneNumber" type="xs:string" />

      </xs:schema> 
     cust.xsd


      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
          xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
          targetNamespace="urn:xmlns:25hoursaday-com:customer" 
          elementFormDefault="qualified"> 

       <xs:redefine schemaLocation="cust.xsd"> 

     <xs:complexType name="CustomerType" > 
       <xs:complexContent>
          <xs:extension base="cust:CustomerType">
           <xs:sequence>
            <xs:element ref="cust:PhoneNumber" />
           </xs:sequence>           
          </xs:extension>
         </xs:complexContent>
        </xs:complexType> 
    
    </xs:redefine> 
   </xs:schema> 
     redefined_cust.xsd
     
    <cust:Customers xmlns:cust="urn:xmlns:25hoursaday-com:customer" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
      <cust:Customer customerID="12345" >
       <cust:FirstName>Dare</cust:FirstName>
       <cust:LastName>Obasanjo</cust:LastName>
       <cust:PhoneNumber>425-555-1234</cust:PhoneNumber>
      </cust:Customer>
      <cust:Customer customerID="67890" >
       <cust:FirstName>John</cust:FirstName>
       <cust:LastName>Smith</cust:LastName>
        <cust:PhoneNumber>425-555-5555</cust:PhoneNumber>
       </cust:Customer>
     </cust:Customers>
     cust.xml

类型重定义会同时影响具有包含关系的两个架构中的元素,所以其影响相当广泛。这样,所有对两个架构中原始类型的引用都将引用重定义的类型,同时原始类型定义被屏蔽。这将在一定程度上削弱性能,因为重定义的类型可以逆向与派生类型结合,产生冲突。常见的冲突是当派生类型使用扩展向类型的内容模型添加元素或属性时,重定义也向内容模型添加类似的命名元素或属性。如果通过添加具有不同于重定义类型的 PhoneNumber 元素进行扩展,那么只要显示的两个架构中任何一个具有从 CustomerType 派生的某种类型,就会出现这样的冲突。

 

 

版权所有:UML软件工程组织