Substitution Groups
Substitution groups are a feature that allows you to declare that an element can be substituted for other elements in an instance document. You achieve this goal by assigning an element to a special groupa substitution groupthat can be substituted for the element at the head of that group, effectively creating an equivalence relation between document elements of the same type (or subtype).
While this isn't exactly like polymorphic behavior in object-oriented programming languages because the base-type/derived type relationship isn't implicit, this feature is immensely useful for creating extensible schemas with open content models.
To illustrate this point, consider the schema in Listing 5.
Listing 5 Using Substitution Groups
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="PersonType"> <xs:sequence> <xs:element name="firstname" type="xs:string" minOccurs="0"/> <xs:element name="surname" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:complexType name="CastMemberType"> <xs:complexContent> <xs:extension base="PersonType"> <xs:sequence> <xs:element name="character" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="CrewMemberType"> <xs:complexContent> <xs:extension base="PersonType"> <xs:sequence> <xs:element name="function" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <!-- Declare substitution group and head element --> <xs:element name="person" type="PersonType" abstract="true"/> <xs:element name="cast-member" type="CastMemberType" substitutionGroup="person"/> <xs:element name="crew-member" type="CrewMemberType" substitutionGroup="person"/> <!-- Now define the actual document --> <xs:element name="cast-and-crew"> <xs:complexType> <xs:sequence> <xs:element ref="person" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
The schema in Listing 5 demonstrates how to use substitution groups to deal with element-level substitutionsa kind of polymorphic behavior for instance documents. The substitution group consists of the elements cast-member and crew-member declaring themselves to be substitutable for a person element through the substitutionGroup="person" attribute declaration. This is a valid substitution group because both cast-member and crew-member are types derived from the PersonType type.
The definition of the cast-and-crew element references the person element from within a sequence, setting the maxOccurs attributes to allow any number of person elements to exist within an instance. However, because person is an abstract element (and thus cannot appear as an element in its own right), this schema actually supports the substitution of person elements for any other element declared to be in the same substitution group. Therefore, this schema will validate instance documents such as the one in Listing 6.
Listing 6 Supporting Polymorphic Behavior with Substitution Groups
<?xml version="1.0" encoding="UTF-8"?> <cast-and-crew> <crew-member> <firstname>Lucas</firstname> <surname>George</surname> <function>director</function> </crew-member> <cast-member> <firstname>Ewan</firstname> <surname>McGregor</surname> <character>Obi Wan Kenobi</character> </cast-member> </cast-and-crew>
The instance document in Listing 6 shows how types from the person substitution group can be used in places where the original schema has specified a PersonType element. In this case, because both cast-member and crew-member are part of the person substitution group, the document is valid.