xmlxsdcontainersrestrictionancestor

Restict XML elements based on the type they extend


I have the following xsd schema :

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:complexType name="Vehicle">
  <xs:sequence>
    <xs:element name="numberOfWheels" type="xs:int" minOccurs="0" maxOccurs="1"/>
  </xs:sequence>
</xs:complexType>

<xs:element name="car" type="Car"/>
<xs:complexType name="Car">
  <xs:complexContent>
    <xs:extension base="Vehicle">
      <xs:sequence>
        <xs:element name="numberOfSeats" type="xs:int" minOccurs="0" maxOccurs="1"/>
      </xs:sequence>
    </xs:extension>
  </xs:complexContent>
</xs:complexType>

<xs:element name="motorCycle" type="MotorCycle"/>
<xs:complexType name="MotorCycle">
  <xs:complexContent>
    <xs:extension base="Vehicle">
      <xs:sequence>
        <xs:element name="hasLuggageCompartment" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
      </xs:sequence>
    </xs:extension>
  </xs:complexContent>
</xs:complexType>

<xs:element name="wagon" type="Wagon"/>
<xs:complexType name="Wagon">
  <xs:complexContent>
    <xs:extension base="Vehicle">
      <xs:sequence>
        <xs:element name="typeOfCargo" type="xs:string" minOccurs="0" maxOccurs="1"/>
      </xs:sequence>
    </xs:extension>
  </xs:complexContent>
</xs:complexType>

<xs:element name="vehicleList" type="VehicleList"/>
<xs:complexType name="VehicleList">
  <xs:sequence>
    <xs:element <!-- what to do here? --> minOccurs="1" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

</xs:schema>

As the schema shows, we have the types "Car", "MotorCycle" and "Wagon", all of them extending the "Vehicle" type.

I now want to introduce a container element in my schema (VehicleList) that enables storing whatever kind of element, as long as it extends "Vehicle", so that we can store elements of type "Car", "MororCycle" or "Wagon", but not, for instance, elements of type "Cow" because they would extend the "Animal" type.

I searched already a lot, and I do not think it is possible to introduce this kind of restriction in XML schema, based on an "ancestor" type, like we are often used to do in OO-programming.

So my question : is it possible to declare a container element restricting the elements it contains based on the type they extend, and, if yes, how?

Thanks in advance for your answers and suggestions.


Solution

  • Using ComplexTypes and xsi:type

    The easiest and most extensible way is to just set the type to be Vehicle.

    <xs:complexType name="VehicleList">
        <xs:sequence>
            <xs:element name="VehicleElm"
                           type="Vehicle"
                           minOccurs="1"
                           maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
    

    However the resulting XML would look something like this, which may not be entirely what you want (with the addition of the xsi:type attribute)

    <?xml version="1.0" encoding="utf-8"?>
    <!-- Created with Liquid Studio 2018 - Developer Bundle 16.0.0.0 (https://www.liquid-technologies.com) -->
    <vehicleList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                     xsi:noNamespaceSchemaLocation="XSDFile.xsd">
        <VehicleElm xsi:type="Car">
            <numberOfWheels>4</numberOfWheels>
            <numberOfSeats>2</numberOfSeats>
        </VehicleElm>
        <VehicleElm xsi:type="Wagon">
            <numberOfWheels>6</numberOfWheels>
            <typeOfCargo>stuff</typeOfCargo>
        </VehicleElm>
    </vehicleList>
    

    Using substitution groups

    The alternative is to use substitution groups, which is slightly more complex in the XSD, but gives cleaner XML

    enter image description here

    <?xml version="1.0" encoding="utf-8" ?>
    <!--Created with Liquid Studio 2018 - Developer Bundle 16.0.0.0 (https://www.liquid-technologies.com)-->
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:complexType name="VehicleType">
            <xs:sequence>
                <xs:element name="numberOfWheels"
                               type="xs:int"
                               minOccurs="0"
                               maxOccurs="1" />
            </xs:sequence>
        </xs:complexType>
        <xs:element name="Vehicle"
                       type="VehicleType"
                       abstract="true" />
        <xs:element name="car"
                       substitutionGroup="Vehicle">
            <xs:complexType>
                <xs:complexContent>
                    <xs:extension base="VehicleType">
                        <xs:sequence>
                            <xs:element name="numberOfSeats"
                                           type="xs:int"
                                           minOccurs="0"
                                           maxOccurs="1" />
                        </xs:sequence>
                    </xs:extension>
                </xs:complexContent>
            </xs:complexType>
        </xs:element>
        <xs:element name="motorCycle"
                       substitutionGroup="Vehicle">
            <xs:complexType>
                <xs:complexContent>
                    <xs:extension base="VehicleType">
                        <xs:sequence>
                            <xs:element name="hasLuggageCompartment"
                                           type="xs:boolean"
                                           minOccurs="0"
                                           maxOccurs="1" />
                        </xs:sequence>
                    </xs:extension>
                </xs:complexContent>
            </xs:complexType>
        </xs:element>
        <xs:element name="wagon"
                       substitutionGroup="Vehicle">
            <xs:complexType>
                <xs:complexContent>
                    <xs:extension base="VehicleType">
                        <xs:sequence>
                            <xs:element name="typeOfCargo"
                                           type="xs:string"
                                           minOccurs="0"
                                           maxOccurs="1" />
                        </xs:sequence>
                    </xs:extension>
                </xs:complexContent>
            </xs:complexType>
        </xs:element>
        <xs:element name="vehicleList"
                       type="VehicleList" />
        <xs:complexType name="VehicleList">
            <xs:sequence>
                <xs:element ref="Vehicle"
                               minOccurs="1"
                               maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:schema>
    

    The resulting XML then looks something like this

    <?xml version="1.0" encoding="utf-8"?>
    <!-- Created with Liquid Studio 2018 - Developer Bundle 16.0.0.0 (https://www.liquid-technologies.com) -->
    <vehicleList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                     xsi:noNamespaceSchemaLocation="XSDFile2.xsd">
        <car>
            <numberOfWheels>4</numberOfWheels>
            <numberOfSeats>2</numberOfSeats>
        </car>
        <motorCycle>
            <numberOfWheels>2</numberOfWheels>
            <hasLuggageCompartment>false</hasLuggageCompartment>
       </motorCycle>
    </vehicleList>
    

    Basically where every you can use the element Vehicle, any of the substitution group members can be used in its place, and because the Vehicle element is abstract, it can't be used itself (so you have to use on of the substitution group members).