Conditional Enumeration
I recently came across a case where a new schema replaced an old schema in a way that was not backwards compatible. The old schema defined an address type as an enumeration of several codes. The new schema defined the address type as a choice of an element of those same codes, or an element for proprietary codes. This meant the new schema would invalidate old documents using valid codes. I wanted to explore how a new schema could support both, in a backwards compatible way. That is, backwards compatible means a new schema can validate an old document.
Note that the examples which follow are designed to include an invalid address line, in order to check that validation of the address type is applied.
Remove the <bad/> element to make them valid examples.
Old Address
<Address ... > <Type>POST</Type> <Line>My <bad/> Address Line </Line> </Address>
New Address
<Address ... > <Type> <Code>POST</Code> </Type> <Line>My <bad/> Address Line </Line> </Address>
New Address with Proprietary Code
<Address> <Type> <Proprietary>My Special Code</Proprietary> </Type> <Line>My <bad/> Address Line </Line> </Address>
Old Address Schema

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:element name="Type" type="TypeCode"/> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
New Address Schema
There are several options to designing a new address schema:
- Extend the old element with new children.
The schema is invalid because it’s element add child elements which is incompatible with its base enumerated type. - Replace the old element with a new element with new children.
This is not backwards compatible, as old values now invalid. - Add new children to a new element.
This is backwards compatible, as new element is used for new values. - Use conditional type assignment. This is backwards compatible, but requires an attribute to indicate whether new children used.
- Assert on mixed content.
This is backwards compatible, but the assert contains values of the enumeration separately to the enumerated type.
Extend with New Children
This schema is invalid. Demonstrates this simple approach is wrong.
Simple Content prohibits elements, and ComplexContent requires a complexType base.

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Address"> <xs:complexType mixed="true"> <xs:complexContent> <xs:extension base="TypeCode"> <xs:sequence> <xs:element name="Type" type="TypeCode"/> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
Replace with New Children

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:element name="Type"> <xs:complexType> <xs:choice> <xs:element name="Type" type="TypeCode"/> <xs:element name="Propietary" type="xs:token"/> </xs:choice> </xs:complexType> </xs:element> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
New Element

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" elementFormDefault="qualified" attributeFormDefault="unqualified" vc:minVersion="1.1"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:choice> <xs:element name="Type" type="TypeCode"/> <xs:element name="TypeChoice"> <xs:complexType> <xs:choice> <xs:element name="Type" type="TypeCode"/> <xs:element name="Proprietary" type="xs:token"/> </xs:choice> </xs:complexType> </xs:element> </xs:choice> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
Conditional Type

Notice that in the schema the default type is not set with the element’s type attribute. Instead it uses the “kind” attribute on the element in the document, to decide:
- when @kind is ‘new’, it must have a Code or Proprietary element.
- when @kind is ‘old’, it must have a text value from TypeCode.
- otherwise, including if @kind is absent, it must be a TypeCode.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" elementFormDefault="qualified" attributeFormDefault="unqualified" vc:minVersion="1.1"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:element name="Type"> <xs:alternative test="@kind='new'"> <xs:complexType> <xs:choice> <xs:element name="Code" type="TypeCode"/> <xs:element name="Proprietary" type="xs:string"/> </xs:choice> <xs:attribute name="kind"/> </xs:complexType> </xs:alternative> <xs:alternative test="@kind='old'"> <xs:complexType> <xs:simpleContent> <xs:extension base="TypeCode"> <xs:attribute name="kind" type="xs:token"/> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:alternative> <xs:alternative type="TypeCode"/> </xs:element> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
Assert on Mixed Content

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" elementFormDefault="qualified" attributeFormDefault="unqualified" vc:minVersion="1.1"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:element name="Type"> <xs:complexType mixed="true"> <xs:choice minOccurs="0"> <xs:element name="Code" type="TypeCode"/> <xs:element name="Proprietary" type="xs:token"/> </xs:choice> <xs:assert test="exists(*) or (text() = ('POST','HOME','WORK')) "/> </xs:complexType> </xs:element> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:string"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>
Conclusion
There are trade offs amonst all these options.
Assert on Mixed content provides backwards compatibility for documents,
and supports new children, but not backwards compatibility for types.
Conditional Type assignment provides backwards compatibility for documents and types, but requires an attribute to support new children.
A new element with new children provides backwards compatibility for documents and types, but requires the new element to support new children.
Replace element with new children breaks backwards compatibility for documents and types, but is simpler for new documents.
Alternative Conditional Type
One last alternative to consider is to use Conditional Type assignment to enable addtional proprietary codes, whilst keeping the old type the same.
Only a new proprietary code is indicated by a kind attribute.
<Address ... > <Type kind="Proprietary">My private code</Type> <Line>String</Line> </Address>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" elementFormDefault="qualified" attributeFormDefault="unqualified" vc:minVersion="1.1"> <xs:element name="Address"> <xs:complexType> <xs:sequence> <xs:element name="Type"> <xs:alternative test="@kind='Proprietary'"> <xs:complexType> <xs:simpleContent> <xs:extension base="xs:token"> <xs:attribute name="kind" use="required" fixed="Proprietary"/> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:alternative> <xs:alternative type="TypeCode"/> </xs:element> <xs:element name="Line" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <xs:simpleType name="TypeCode"> <xs:restriction base="xs:token"> <xs:enumeration value="POST"/> <xs:enumeration value="HOME"/> <xs:enumeration value="WORK"/> </xs:restriction> </xs:simpleType> </xs:schema>