Search code examples
xmlxpathxsduniquexsd-1.0

XSD unique element XPath limitations


In order to allow an element to appear multiple times but restrict it to element values only being allowed once, I'm applying the unique element. I've only got it working in an inefficient manner due to the XPath limitations within XSD 1.0 though. XSD 1.1 is unfortunately not an option for me.

Below is a simplified version of the XSD:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">  
    <xs:simpleType name="DeliveryMethod">
        <xs:restriction base="xs:string">
            <xs:enumeration value="POST"/>
            <xs:enumeration value="DIGITALARCHIVE"/>
        </xs:restriction>
    </xs:simpleType>

    <xs:complexType name="Mailpack">
        <xs:sequence>
            <xs:element maxOccurs="unbounded" name="deliveryMethod" type="DeliveryMethod"/>
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="Letter">
        <xs:complexContent>
            <xs:extension base="Mailpack">
                <xs:sequence>
                    <xs:element name="content" type="xs:string"/>
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:element name="batch">
        <xs:complexType>
            <xs:sequence>
                <xs:element minOccurs="0" name="id" type="xs:string"/>

                <xs:choice>
                    <xs:element maxOccurs="unbounded" name="letter" type="Letter">
                        <xs:unique name="oneOfEachDeliveryMethodType">
                            <xs:selector xpath="deliveryMethod"/>
                            <xs:field xpath="." />
                        </xs:unique>
                    </xs:element>
                </xs:choice>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

With the following XML:

<?xml version="1.0" encoding="utf-8"?>
<batch>
    <id>123</id>
    <letter>
        <deliveryMethod>POST</deliveryMethod>
        <deliveryMethod>DIGITALARCHIVE</deliveryMethod>
        <deliveryMethod>DIGITALARCHIVE</deliveryMethod>
        <content>Narf!</content>
    </letter>
</batch>

This works as expected and gives me the error that "DIGITALARCHIVE" is included twice. The problem is that this method forces me to include the unique element in each possible extended Mailpack type. So if I e.g. introduce a Parcel type as an extension of Mailpack then I have to duplicate the same unique element from the batch/letter element in the new batch/parcel element.

Making the unique element part of the deliveryMethod element as below is not an option because the .. parent selector is not allowed in XSD XPath.

<xs:complexType name="Mailpack">
    <xs:sequence>
        <xs:element maxOccurs="unbounded" name="deliveryMethod" type="DeliveryMethod">
            <xs:unique name="oneOfEachDeliveryMethodType">
                <xs:selector xpath="deliveryMethod"/>
                <xs:field xpath=".." />
            </xs:unique>
        </xs:element>
    </xs:sequence>
</xs:complexType>

Making it part of the batch element directly resulted in the same issue.

How can I include the unique element only once but make it count for all extensions of Mailpack? (if at all)


Solution

  • By wrapping the <deliveryMethod> elements in a (for example) <deliveryMethodsList> element you can move the unique element so you only have to write it once.

    As an example you only need to replace your Mailpack complexType with the following:

    <xs:complexType name="Mailpack">
        <xs:sequence>
            <xs:element name="deliveryMethodsList">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element maxOccurs="unbounded" name="deliveryMethod" type="DeliveryMethod"/>
                    </xs:sequence>
                </xs:complexType>
                <!-- Unique is only placed here -->
                <xs:unique name="oneOfEachDeliveryMethodType">
                    <xs:selector xpath="deliveryMethod"/>
                    <xs:field xpath="." />
                </xs:unique>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
    

    And now you don't need the unique element in the letters list:

    <xs:element maxOccurs="unbounded" name="letter" type="Letter" />
    

    Now the following example is not valid as <deliveryMethod>DIGITALARCHIVE</deliveryMethod> appears twice:

    <?xml version="1.0" encoding="utf-8"?>
    <batch>
        <id>123</id>
        <letter>
            <deliveryMethodsList>
                <deliveryMethod>POST</deliveryMethod>
                <deliveryMethod>DIGITALARCHIVE</deliveryMethod>
                <deliveryMethod>DIGITALARCHIVE</deliveryMethod>
            </deliveryMethodsList>
            <content>Narf!</content>
        </letter>
    </batch>