Designing XML Schema Libraries

 

Aaron Skonnard
DevelopMentor

May 2003

Applies to:
   XML Schema Definition Language (XSD)
   Microsoft® IntelliSense®

Summary: XML Schema Definition Language (XSD) facilitates reuse and long-term maintenance of your core libraries through its inclusion mechanisms such as include and import, and makes it possible to factor schema definitions into multiple files and namespaces. Learn about designing schema libraries capable of modeling a wide range of programming language-specific type hierarchies in use today. (21 printed pages)

Contents

Introduction
Type Hierarchies
Deriving from Complex Types by Restriction
Deriving from Complex Types by Extension
Blocking Derivation
Abstract Types
Substitution with xsi:type
Substitution Groups
Blocking Substitution
Reuse
Where Are We?
References

Introduction

The XML Schema definition language (XSD) has become the mainstream language for describing XML documents. XML Schema makes it possible to define simple and complex types. Simple types define custom value spaces that can be applied to text-only elements and attributes, while complex types make it possible to arrange such value spaces into structures. XML Schema is quite expressive and capable of providing powerful services to XML-based applications, particularly those in the Web services domain. These services include validation, reflection, automatic serialization (type mappings), remote method invocations, and better tool support with features like IntelliSense for XML.

In my first article on Understanding XML Schema, we covered the core XSD language constructs and use cases. XSD is actually capable of much more than what we had space to discuss there. Complex type definitions support derivation by extension and restriction, allowing you to define complex type hierarchies. With complex type hierarchies in place, it's also possible to leverage substitution techniques in instance documents. XSD also makes it possible to factor schema definitions into multiple files and namespaces, which can then be included or imported to increase reuse and simplify maintenance. These more advanced, design-oriented topics are the focus of this article.

Type Hierarchies

In Understanding XML Schema we presented the repertoire of simple datatypes that are built into XSD, as shown in Figure 1. These datatypes are defined as a hierarchy of types. In Figure 1, the arrows indicate derivation by restriction (e.g., byte derives from short by restriction and therefore has a value space that is a subset of short's value space). We can leverage the hierarchy to infer relationships between types. For example, when using derivation by restriction, an instance of a derived type is also a valid instance of a base type (e.g., a byte is also a short, which is also an int, which is also a long, etc.).

XSD makes it possible for you to define custom simple types to extend this hierarchy. When defining a new simple type, you're essentially creating a new type that derives from one of the built-in datatypes and restricts one or more of its facets. In the following example, tns:PersonAge derives from xsd:byte and restricts its value space to a range of 0 to 100 inclusive:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
   ...
   <xsd:simpleType name="PersonAge">
      <xsd:restriction base="xsd:byte">
         <xsd:minInclusive value="0"/>
         <xsd:maxInclusive value="100"/>
      </xsd:restriction>
   </xsd:simpleType>
   ...
</xsd:schema>

Now there is a type relationship between tns:PersonAge and xsd:byte—an instance of tns:PersonAge is also a valid instance of xsd:byte, which is also a valid instance of xsd:short, which is also a valid instance of xsd:int, etc. This relationship always holds true when deriving types by restriction. For detailed coverage on restricting simple types, be sure to check out Understanding XML Schema.

With complex types you can derive types by restriction or extension when defining type hierarchies like the one illustrated above. You cannot, however, derive by restriction and extension at the same time (e.g., there's no 'restrension'). You can only derive by either restriction or extension in a single type definition. So if you want to restrict a base type and then extend it, you would have to do so in two separate type definitions.

Figure 1. XSD built-in datatypes

Deriving from Complex Types by Restriction

As with simple types, it's also possible to derive complex types by restriction. This means that you can derive a new complex type from another existing complex type and restrict the elements and attributes that make up its structure. For example, consider the following complex type definition that represents a person:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
   ...
   <xsd:complexType name="Person">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string"/>
         <xsd:element name="age" type="xsd:byte " 
                      minOccurs="0"/>
         <xsd:element name="phone" type="xsd:string"
                      minOccurs="1" maxOccurs="5"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string "/>
   </xsd:complexType>
   <xsd:element name="person" type="tns:Person"/>
   ...
</xsd:schema>

The following XML document contains a valid instance of the tns:Person type. Notice that the local elements and attributes are unqualified (local elements and attributes are unqualified by default). In addition, the age element has been omitted and there are three occurrences of the phone element, all of which are allowed by the type definition.

<tns:person xmlns:tns="https://example.org/person"
   id="333-22-4444"
>
   <name>Bob Smith</name>
   <phone>801-444-5555</phone>
   <phone>801-555-6666</phone>
   <phone>801-666-7777</phone>   
</tns:person>

If you wish to restrict certain facets of the elements and attributes that constitute tns:PersonType, you can derive a new complex type, by restriction, through the xsd:complexContent and xsd:restriction elements as shown here:

...
<xsd:complexType name="RestrictedPerson">
   <xsd:complexContent>
      <xsd:restriction base="tns:Person">
        <!-- redefine base type's particles here -->
        ...
      </xsd:restriction>
   </xsd:complexContent>
</xsd:complexType>
...

The xsd:complexContent element indicates that the complex type is being derived from some other complex type. The xsd:restriction element indicates the base type we're restricting and it contains the new definitions for all of the base type's particles (e.g., compositors, element definitions, wildcards, etc.). When doing this, you must list all of the particles of the base type as well as those of the base type's ancestors, if it has any.

You can restrict a base type in two ways, by either specifying tighter value spaces or occurrence constraints for its particles. You can specify a "tighter value space" for a particle by changing its type to one that derives from the simple type used in the base type. For example, the age element is of type xsd:byte in tns:PersonType. We could restrict age's value space by changing its type to tns:PersonAge, which effectively restricts its text content to a smaller set of possible values.

You can also restrict the base type by specifying tighter occurrence constraints. "Tighter occurrence constraints" means that the new occurrence constraints must be within the range of the base type's occurrence constraints. For example, the phone element above is defined to occur at least once and a maximum of five times. When restricting this element in a derived type you can't set the phone element's minOccurs attribute to less than 1 or its maxOccurs attribute to greater than 5. The following, however, would represent narrower occurrence constraints since now we're saying the element must appear exactly twice (which is still valid according to the base type's occurrence constraints):

...
   <xsd:element name="phone" type="xsd:string"
                minOccurs="2" maxOccurs="2"/>
...

Due to the way this works, if an element is defined as mandatory in the base type, as is the case with name and phone, you can't remove them in a derived type (note: the default for minOccurs and maxOccurs is 1). If however, you had an optional element in the base type, such as age, you could either restrict it out of existence (by setting maxOccurs="0") or into a mandatory element (by setting minOccurs="1"). Again, it's legal as long as the occurrence constraints of the derived type are within the range of those defined by the base type.

The same holds true for attributes. If an attribute is optional (the default) in the base type, it can be restricted to be either required or prohibited in the derived type ('prohibited' only applies during restriction otherwise it's ignored). For example, the following attribute declaration illustrates how to prohibit the id attribute in a derived type:

...
<xsd:attribute name="id" type="xsd:string"
               use="prohibited" />
...

Now let's look at a complete example of a restricted complex type. The following example defines a few new simple types that restrict xsd:string with pattern facets. These types are then used to restrict some of the element and attribute declarations used in the base type. It also restricts the occurrence constraints of several element and attribute declarations:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
   <xsd:simpleType name="PersonAge">
      <xsd:restriction base="xsd:byte">
         <xsd:minInclusive value="0"/>
         <xsd:maxInclusive value="100"/>
      </xsd:restriction>
   </xsd:simpleType>

   <xsd:simpleType name="Phone">
      <xsd:restriction base="xsd:string">
         <xsd:pattern value="\d{3}-\d{3}-\d{4}"/>
      </xsd:restriction>
   </xsd:simpleType>

   <xsd:complexType name="Person">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string"/>
         <xsd:element name="age" type="xsd:byte " 
                      minOccurs="0"/>
         <xsd:element name="phone" type="xsd:string"
                      minOccurs="1" maxOccurs="5"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string "/>
   </xsd:complexType>
   <xsd:element name="person" type="tns:Person"/>

   <xsd:complexType name="RestrictedPerson">
      <xsd:complexContent>
         <xsd:restriction base="tns:Person">
            <!-- redefine base particles here -->
            <xsd:sequence>
               <xsd:element name="name" type="xsd:string"/>
               <xsd:element name="age" type="tns:PersonAge" 
                            minOccurs="1"/>
               <xsd:element name="phone" type="tns:Phone"
                            minOccurs="1" maxOccurs="1"/>
            </xsd:sequence>
           <xsd:attribute name="id" use="prohibited" />
         </xsd:restriction>
      </xsd:complexContent>
   </xsd:complexType>

   <xsd:element name="person" type="tns:Person"/>
   <xsd:element name="resPerson" type="tns:RestrictedPerson"/>
...
</xsd:schema>

In this example, we restricted the value spaces of the age and phone elements using our newly defined simple types (tns:PersonAge and tns:Phone). We also tightened the occurrence constraints on the age element (it's now mandatory), the phone element (it must now appear exactly once), and the id attribute (it's now prohibited instead of being optional). The following document contains a valid instance of tns:RestrictedPerson:

<tns:resPerson xmlns:tns="https://example.org/person">
   <name>Bob Smith</name>
   <age>33</age>
   <phone>333-333-4444</phone>
</tns:resPerson>

Since every aspect of this new type is a restriction of the base type, a valid instance of tns:RestrictedPerson is also a valid instance of tns:Person. The reverse, however, is not always true. A valid instance of tns:Person (like the one showed earlier) is not necessarily a valid instance of tns:RestrictedPerson.

Deriving from Complex Types by Extension

Although derivation by restriction is a valuable and core part of XML Schema, it's not supported in most programming languages. Hence, when mapping schema definitions to programmatic types, most of what we did in the last example won't naturally translate into programmatic classes. Derivation by extension, on the other hand, is quite common in most object-oriented programming languages and luckily it's also supported in XSD.

Only complex types support derivation by extension (e.g., you cannot derive new simple types from existing simple types by extension). You can, however, derive new complex types by extension from existing simple or complex types. You specify whether you're deriving from a simple or complex type using the xsd:simpleContent or xsd:complexContent elements, then you use a nested xsd:extension element to indicate that you're deriving by extension.

When deriving by extension from a simple type, you can only add attributes to the new type (if you added elements, it would no longer be a simple type). The following example illustrates how to extend a new complex type, tns:PhoneWithLabel, from an existing simple type, tns:Phone, by adding an attribute to it:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <xsd:simpleType name="Phone">
      <xsd:restriction base="xsd:string">
         <xsd:pattern value="\d{3}-\d{3}-\d{4}"/>
      </xsd:restriction>
   </xsd:simpleType>

   <xsd:complexType name="PhoneWithLabel">
      <xsd:simpleContent>
         <xsd:extension base="tns:Phone">
           <!-- add attributes here -->
           <xsd:attribute name="label" type="xsd:string"
                          use="required" />
         </xsd:extension>
      </xsd:simpleContent>
   </xsd:complexType>
   <xsd:element name="phone" type="tns:PhoneWithLabel"/>
...
</xsd:schema>

Xsd:simpleContent appears as a child of the xsd:complexType to indicate that the content model of the new type contains only character data and no child elements. In this case, we've simply added attributes to the type, which is enough to make the new type a complex type (simple types cannot have any structure, not even attributes). The following represents a valid instance of tns:PhoneWithLabel:

<tns:phone xmlns:tns="https://example.org/person"
   label="mobile"
>333-333-4444</tns:phone>

You can derive from an existing complex type the same way, only you use xsd:complexContent to indicate that the content model of the new type will contain more than just character data, such as some additional child elements. The following example illustrates how to derive a new complex type from tns:Person:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <xsd:complexType name="Person">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string"/>
         <xsd:element name="age" type="xsd:byte " 
                      minOccurs="0"/>
         <xsd:element name="phone" type="xsd:string"
                      minOccurs="1" maxOccurs="5"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string "/>
   </xsd:complexType>
   <xsd:element name="person" type="tns:Person"/>

   <xsd:complexType name="ExtendedPerson">
      <xsd:complexContent>
         <xsd:extension base="tns:Person">
            <xsd:sequence>
               <xsd:element name="sex" type="xsd:string"/>
               <xsd:element name="weight" type="xsd:double"/>
               <xsd:element name="height" type="xsd:double"/>
            </xsd:sequence>
         </xsd:extension>
      </xsd:complexContent>
   </xsd:complexType>
...
</xsd:schema>

When deriving by extension, you cannot change anything in the base type—you can only add new particles to the base type. Since you can't make changes to the base type, you don't have to redefine the particles like we did with derivation by restriction. Instead you simply define the new content that will follow the content of the base type. The new content logically appears after the base type's content, as if the two content models were wrapped within an xsd:sequence element as illustrated here:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <!-- the logical content model of the derived type -->
   <xsd:complexType name="ExtendedPerson">
      <xsd:sequence>
         <!-- base type's content model -->
         <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
            <xsd:element name="age" type="xsd:byte " 
                         minOccurs="0"/>
            <xsd:element name="phone" type="xsd:string"
                         minOccurs="1" maxOccurs="5"/>
         </xsd:sequence>
         <!-- derived type's content model -->
         <xsd:sequence>
            <xsd:element name="sex" type="xsd:string"/>
            <xsd:element name="weight" type="xsd:double"/>
            <xsd:element name="height" type="xsd:double"/>
         </xsd:sequence>
         <xsd:attribute name="id" type="xsd:string "/>
      </xsd:sequence>
   </xsd:complexType>
...
</xsd:schema>

Hence, the following document illustrates what a valid instance of tns:ExtendedPerson looks like:

<tns:extPerson xmlns:tns="https://example.org/person"
   id="333-22-4444"
>
   <name>Bob Smith</name>
   <phone>801-444-5555</phone>
   <phone>801-555-6666</phone>
   <phone>801-666-7777</phone>   
   <sex>M</sex>
   <weight>175</weight>
   <height>72</height>
</tns:extPerson>

Unlike derivation by restriction, instances of extended types are usually not valid instances of their base types. As you can see here, tns:extPerson is definitely not a valid instance of the tns:Person base type.

There are a few restrictions to derivation by extension when it comes to xsd:all compositors. If a complex type uses xsd:all as its top-level compositor, you can't extend it by adding new particles—you can only add attributes in this case. Also, you can't extend another complex type with an xsd:all unless it has an empty content model.

Blocking Derivation

In some situations you may want to prevent derivation from a particular type, similar to a 'sealed' class definition. XSD makes this possible through the final attribute on xsd:complexType (note: you can also use the final attribute on element declarations when using substitution groups). Using final, you can specify which derivation mechanisms are prohibited from the particular type. The possible values for the final attribute are extension, restriction, and #all. For example, the following complex type definition prohibits both derivation by restriction and extension from the tns:Person base type:

...
   <xsd:complexType name="Person" final="#all">
      ... <!-- omitted for brevity -->
   </xsd:complexType>
...

You can also specify a schema-wide default for the final attribute using the finalDefault attribute on the xsd:schema element as illustrated here:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
   finalDefault="restriction"
>
...

This means that, by default, derivation by restriction is not allowed between complex types unless a given complex type overrides this setting using the final attribute shown above.

Abstract Types

In most object-oriented languages it's possible to define abstract types. An abstract type in this context is one that cannot be instantiated. Abstract base classes and interfaces are examples of such types. One purpose of abstract types is to define a contract that other derived types will implement. This makes it possible to build applications around abstract interfaces, supporting numerous possible implementations. The assumption is that some derived type will be substituted for the abstract type at runtime.

XSD also makes it possible to define abstract complex types. To do so, you use the abstract attribute (boolean) on the xsd:complexType element as illustrated here:

...
   <xsd:complexType name="Person" abstract="true">
      ... <!-- omitted for brevity -->
   </xsd:complexType>
...

The default for this attribute is false, which means that complex types are concrete unless you specify otherwise as we've done here. An abstract schema type means that the type may not appear in XML instance documents. Instead, a derived type must be substituted where the abstract type was expected.

Substitution with xsi:type

One benefit of defining type hierarchies is the ability to substitute instances of derived types. As with polymorphism in object-oriented technologies, you can substitute a derived type wherever a base type is expected. One way to accomplish this in XML documents is through xsi:type.

Using xsi:type you can explicitly specify the type of an element. This makes it possible for an element to assert that it's of a particular type even though there may not be an element declaration for it in the schema. This attribute is most commonly used when an instance of a derived type is used where an instance of the base type was expected. In this case, the schema process ensures that the type specified in the xsi:type attribute indeed derives from the expected base type. For example, consider the following complex type definition named tns:Family, which contains a list of person elements of type tns:Person:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <xsd:complexType name="Family">
      <xsd:sequence>
         <xsd:element name="person" type="tns:Person"
                      maxOccurs="unbounded"/>
      </xsd:sequence>
   </xsd:complexType>
   <xsd:element name="family" type="tns:Family"/>
...
</xsd:schema>

The following document contains a valid instance of tns:Family. Notice that an instance of tns:RestrictedPerson and tns:ExtendedPerson have been substituted where an instance of tns:Person was expected.

<tns:family xmlns:tns="https://example.org/person"
   xmlns:xsi="https://www.w3.org/2001/XMLSchema"
>
   <person id="333-22-4444">
      <name>Bob Smith</name>
      <phone>801-444-5555</phone>
      <phone>801-444-6666</phone>
   </person>
   <person xsi:type="tns:RestrictedPerson">
      <name>Mary Smith</name>
      <age>33</age>
      <phone>333-333-4444</phone>
   </person>
   <person xsi:type="tns:ExtendedPerson" id="333-22-5555">
      <name>Jenny Smith</name>
      <phone>801-444-5555</phone>
      <phone>801-444-6666</phone>
      <sex>F</sex>
      <weight>105</weight>
      <height>65</height>
   </person>
</tns:family>

As you can see, this style of substitution is based on type names, not element names. This means you can't solely look at the element name to determine its type. XSD provides another substitution mechanism, however, based completely on element names.

Substitution Groups

Substitution groups allow element-based substitution. To form a substitution group, a global element declaration serves as the 'head' of the group (any global element can serve as the 'head' without doing anything special). Then, other global element declarations can join a substitution group by using the substitutionGroup attribute, specifying the name of the head element as shown here:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <xsd:element name="person" type="tns:Person"/>
   <xsd:element name="resPerson" type="tns:RestrictedPerson"
                substitutionGroup="tns:person" />
   <xsd:element name="extPerson" type="tns:ExtendedPerson"
                substitutionGroup="tns:person" />
...
</xsd:schema>

The members of a substitution group must be related to the type of the head element. This means that a member element can either be the same type as the head element or of a restricted/extended type. This rule ensures that substituted elements make sense when used in place of the head element.

For example, with the above substitution group in place, we can change the complex type definition for tns:Family to reference the global tns:person element:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
...
   <xsd:complexType name="Family">
      <xsd:sequence>
         <xsd:element ref="tns:person" maxOccurs="unbounded"/>
      </xsd:sequence>
   </xsd:complexType>
   <xsd:element name="family" type="tns:Family"/>
...
</xsd:schema>

Then, we can substitute any members of the tns:person substitution group at this location as illustrated here:

<tns:family xmlns:tns="https://example.org/person">
   <tns:person id="333-22-4444">
      <name>Bob Smith</name>
      <phone>801-444-5555</phone>
      <phone>801-444-6666</phone>
   </tns:person>
   <tns:resPerson>
      <name>Mary Smith</name>
      <age>33</age>
      <phone>333-333-4444</phone>
   </tns:resPerson>
   <tns:extPerson id="333-22-5555">
      <name>Jenny Smith</name>
      <phone>801-444-5555</phone>
      <phone>801-444-6666</phone>
      <sex>F</sex>
      <weight>105</weight>
      <height>65</height>
   </tns:extPerson>
</tns:family>

Notice that since tns:person, tns:resPerson, and tns:extPerson are all global elements they must be qualified by the schema's target namespace. Also notice that since we're using element-based substitution, we no longer need to use xsi:type to specify type information. Now an element's type can be derived solely from its name.

Blocking Substitution

As with derivation, you may also wish to prevent substitution from being used on particular types or elements in the case of substitution groups. You can prevent substitution on a particular type/element by using the block attribute on xsd:complexType or xsd:element. The block attribute allows you to specify what derived types/elements may be used in place of the type/element at hand. The allowed values for the block attribute are extension, restriction, substitution, and #all. For example, the following complex type definition prohibits instances of extended types from being substituted where tns:Person instances are expected:

...
   <xsd:complexType name="Person" block="extension">
      ... <!-- omitted for brevity -->
   </xsd:complexType>
...

You can also specify a schema-wide default for the block attribute using the blockDefault attribute on the xsd:schema element. The following example means that, by default, all forms of substitution are prohibited for types in this schema unless a particular complex type overrides this setting using the block attribute.

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
   blockDefault="#all"
>
...

As with xsd:complexType, you can use the abstract and final attributes on xsd:element to control how an element is used in a substitution group.

Reuse

When designing your schema libraries, you will most likely have some global types that need to be shared by a variety of schemas. In such situations, you want to define these types in a separate schema file that can be included or imported into another schema file. It is very common for a schema to be constructed from multiple schema files.

XSD facilitates this type of reuse through the xsd:include and xsd:import elements. Xsd:include makes it possible to include a schema found in a separate file. In this case, the target namespace of both schema files must be the same. It's possible, however, to include a schema file with no target namespace. When this occurs, the included constructs become part of the new target namespace. Here's an example of how xsd:include works:

<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
   <xsd:include schemaLocation="globals.xsd"/>
   ...

Although xsd:include can be handy for certain situations, xsd:import is more commonly used in practice. Xsd:import makes it possible to 'import' types from another namespace into the new schema for use. In this case, the imported types are still in their original namespace but they can be used to construct new types in the new namespace. For example, consider the following schema definition that contains the tns:Person type:

<!-- personBase.xsd -->
<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:tns="https://example.org/person"
   targetNamespace="https://example.org/person"
>
   <xsd:complexType name="Person">
      <xsd:sequence>
         <xsd:element name="name" type="xsd:string"/>
         <xsd:element name="age" type="xsd:byte " 
                      minOccurs="0"/>
         <xsd:element name="phone" type="xsd:string"
                      minOccurs="1" maxOccurs="5"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string "/>
   </xsd:complexType>
</xsd:schema>

We can import this schema into a new schema and use the type as the base type of a new complex type definition:

<!-- person.xsd -->
<xsd:schema xmlns:xsd="https://www.w3.org/2001/XMLSchema"
   xmlns:basetns="https://example.org/person"
   xmlns:tns="https://example.org/person/derived"
   targetNamespace="https://example.org/person/derived"

>
   <xsd:import namespace="https://example.org/person"
               schemaLocation="personBase.xsd"/>

   <xsd:complexType name="ExtendedPerson">
      <xsd:complexContent>
         <xsd:extension base="basetns:Person">
            <xsd:sequence>
               <xsd:element name="sex" type="xsd:string"/>
               <xsd:element name="weight" type="xsd:double"/>
               <xsd:element name="height" type="xsd:double"/>
            </xsd:sequence>
         </xsd:extension>
      </xsd:complexContent>
   </xsd:complexType>
</xsd:schema>

When importing types across namespaces, you need to be careful with derivation by restriction. If imported complex types have qualified local elements (e.g., elementFormDefault="qualified") and you define a new restricted complex type, you're effectively changing the namespace of the local elements making the restriction invalid. If the local elements of the imported types are unqualified, however, you won't experience this because the new restricted type isn't changing their namespace.

Where Are We?

As you can see, XSD is a powerful and flexible language capable of representing many of the type hierarchies commonly used in today's object-oriented systems. Even better, XSD makes it possible for applications to take advantage of type relationships through XML substitution techniques. This makes it possible to model not only the type hierarchies themselves, but also how they're used in practice.

XSD also facilitates reuse and long-term maintenance of your core libraries through its inclusion mechanisms (e.g., include and import). Although XSD is far from perfect, it provides a solid foundation for designing schema libraries capable of modeling a wide range of programming language-specific type hierarchies in use today. For more information on XML Schema, check out the electronic version of the Essential XML Quick Reference (freely available online) and the other references listed below.

References

XML Schema Part 0: Primer

XML Schema Part 1: Structures

XML Schema Part 2: Datatypes

Essential XML Quick Reference