16.9. Controlling Type Derivation
Just as some object-oriented programming languages allow the creator of an object to dictate the limits on how an object can be extended, the schema language allows schema authors to place restrictions on type extension and restriction.
16.9.1. Abstract Elements and Types
The abstract attribute applies to type and element declarations. When it is set to true, that element or type cannot appear directly in an instance document. If an element is declared as abstract, a member of a substitution group based on that element must appear. If a type is declared as abstract, no element declared with that type may appear in an instance document.
16.9.2. The Final Attribute
Until now, the schema has placed no restrictions on how other types or elements could be derived from its elements and types. The final attribute can be added to a complex type definition and set to either #all, extension, or restriction. On a simple type definition it can be set to #all or a list containing any combination of the values list, union, and/or restriction, in any order. When a type is derived from another type that has the final attribute set, the schema processor verifies that the desired derivation is legal. For example, a final attribute could prevent the physicalAddressType type from being extended:
<xs:complexType name="physicalAddressType" final="extension">
Since the main schema in address-schema.xsd attempts to redefine the physicalAddressType in an xs:redefine block, the schema processor generates the following errors when it attempts to validate the instance document:
ComplexType 'physicalAddressType': cos-ct-extends.1.1: Derivation by extension is forbidden by either the base type physicalAddressType_redefined or the schema. Attribute "addr:latitude" must be declared for element type "physicalAddress". Attribute "addr:longitude" must be declared for element type "physicalAddress".
The first error is a result of trying to extend a type that has been marked to prevent extension. The next two errors occur because the new, extended type was not parsed and applied to the content in the document. Now that you've seen how this works, removing this particular "feature" from the physicalAddressType definition gets the schema working again.
16.9.3. Setting fixed Facets
Similar to the final attribute, the fixed attribute is provided to mark certain facets of simple types as immutable. Facets that have been marked as fixed="true" cannot be overridden in derived types.
16.9.4. Uniqueness and Keys
Perhaps one of the most welcome features of schemas is the ability to express more sophisticated relationships between values in elements and attributes of a document. The limitations of the primitive index capability provided by the XML 1.0 ID and IDREF attributes became readily apparent as documents began to include multiple distinct types of element data with complex data keys. The two facilities for enforcing element uniqueness in schemas are the xs:unique and xs:key elements.
18.104.22.168. Forcing uniqueness
The xs:unique element enforces element and attribute value uniqueness for a specified set of elements in a schema document. This uniqueness constraint is constructed in two phases. First, the set of all of the elements to be evaluated is defined using a restricted XPath expression. Next, the precise element and attribute values that must be unique are defined.
To illustrate, let's add logic to the address schema to prevent the same phone number from appearing multiple times within a given contacts element. To add this restriction, the element declaration for contacts includes a uniqueness constraint:
<xs:element name="contacts" type="addr:contactsType" minOccurs="0"> <xs:unique name="phoneNums"> <xs:selector xpath="phone"/> <xs:field xpath="@addr:number"/> </xs:unique> </xs:element>
Now, if a given contacts element contains two phone elements with the same value for their number attributes, the schema processor will generate an error.
This is the basic algorithm that the schema processor follows to enforce these restrictions:
TIP: The very perceptive among you, are right: the contactsType type definition only permits a single phone child element. So this particular restriction would not be very useful. Modifying the contactsType definition to permit multiple child elements is not difficult.
22.214.171.124. Keys and references
The xs:key element is closely related to the xs:unique element. Logically, the xs:key element functions exactly the same way the xs:unique element does. It uses the xs:selector element to define a set of elements it applies to, then one or more xs:field elements are used to define which values make up this particular key. The major difference is that, in the case of the xs:key element, uniqueness is not the only desired property of these elements. The goal of the xs:key element is to define a set of elements that can be referenced using the xs:keyref element. Having created a fairly full-featured address element, creating a collection of these elements called addressBook would be an excellent way to show this feature in operation.
First, the new addressBook element is declared, including a key based on the ssn attribute of each address entry:
<xs:element name="addressBook"> <xs:complexType> <xs:sequence maxOccurs="unbounded"> <xs:element ref="addr:address"/> </xs:sequence> </xs:complexType> <xs:key name="ssnKey"> <xs:selector xpath="addr:address"/> <xs:field xpath="@addr:ssn"/> </xs:key> </xs:element>
Now that the key is defined, you can add a new element to the address element declaration that connects a particular address record with another record. For example, to list references to the children of a particular person in the address book, add the following declaration for a kids element:
<xs:element name="address"> <xs:complexType> <xs:sequence> <xs:element name="fullName"> . . . </xs:element> <xs:element name="kids" minOccurs="0"> <xs:complexType> <xs:sequence maxOccurs="unbounded"> <xs:element name="kid"> <xs:complexType> <xs:attribute name="ssn" type="addr:ssn"/> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> . . . </xs:sequence> <xs:attributeGroup ref="addr:nationality"/> <xs:attribute name="ssn" type="addr:ssn"/> <xs:anyAttribute namespace="http://www.w3.org/1999/xlink" processContents="skip"/> </xs:complexType> </xs:element>
Now, an xs:keyref element in the addressBook element declaration enforces the constraint that the ssn attribute of a particular kid element must match an ssn attribute on an address element in the current document:
<xs:element name="addressBook"> . . . <xs:key name="ssnKey"> <xs:selector xpath="addr:address"/> <xs:field xpath="@addr:ssn"/> </xs:key> <xs:keyref name="kidSSN" refer="addr:ssnKey"> <xs:selector xpath="addr:address/kids/kid"/> <xs:field xpath="@addr:ssn"/> </xs:keyref> </xs:element>
Copyright © 2002 O'Reilly & Associates. All rights reserved.