XSD for an element, when an element can be both mandatory and optional

38 Views Asked by At

I have two possible variants of an XML:

  1. Both 'ElementB' and 'ElementC' are mandatory.
    <ElementA>
        <ElementB /> 
        <ElementC />
    </ElementA>
  1. both 'ElementB' and 'ElementD' are mandatory, but 'ElementC' is optional.
    <ElementA>
        <ElementB /> 
        <ElementC />
        <ElementD />
    </ElementA>

I need something like


<xs:group name="ElementsD">
   <xs:sequence>
      <xs:element name="ElementC " type="ElementCType" minOccurs="0" maxOccurs="1" />
      <xs:element name="ElementD " type="ElementDType" minOccurs="1" maxOccurs="1" />
   </xs:sequence>
</xs:group>

<xs:element name="ElementA">
   <xs:complexType>
        <xs:sequence>
            <xs:element name="ElementB " type="ElementBType" minOccurs="1" maxOccurs="1" />
            <xs:choice>
                <xs:element name="ElementC" type="ElementCType" minOccurs="1" maxOccurs="1" />
                <xs:group ref="ElementsD" />
            </xs:choice>
        </xs:sequence>
   </xs:complexType>
</xs:element>

This schema does not work due to

ElementC and ElementC (or elements from their substitution group) violate "Unique Particle Attribution". During validation against this schema, ambiguity would be created for those two particles.

Is there any good solution?

2

There are 2 best solutions below

3
dbollu On

You can use <xs:sequence> inside <xs:choice>. This allows you to define multiple sequences that may occur within an element (but only one of them is allowed at once in any occurrence of said element).

<xs:element name="ElementA">
   <xs:complexType>
        <xs:choice>
            <xs:sequence>
                 <xs:element name="ElementB" type="ElementBType" minOccurs="1" maxOccurs="1" />
                 <xs:element name="ElementC" type="ElementCType" minOccurs="1" maxOccurs="1" />
            </xs:sequence>
            <xs:sequence>
                 <xs:element name="ElementB" type="ElementBType" minOccurs="1" maxOccurs="1" />
                 <xs:element name="ElementC" type="ElementCType" minOccurs="0" maxOccurs="1" />
                 <xs:element name="ElementD" type="ElementDType" minOccurs="1" maxOccurs="1" />
            </xs:sequence>
        </xs:choice>
   </xs:complexType>
</xs:element>
1
mamift On

So this is possible with XSD 1.1 which includes support for xs:assert; I do not think this is possible with XSD 1.0 using just the regular sequence, choice compositors. If you need to use XSD 1.0 then I think you're out of luck.

However, in XSD 1.1, the test logic that should work is: <xs:assert test="(ElementB and ElementC) or (ElementB and ElementD)"/>.

Using the above assertion logic also means that both ElementC and ElementD should be optional (minOccurrs="0").

Here's the schema (I've added a Root element to allow testing multiple permutations in a sample XML document):

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:complexType name="ElementBType"/>
    <xs:complexType name="ElementCType"/>
    <xs:complexType name="ElementDType"/>

    <xs:element name="ElementA">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="ElementB" type="ElementBType"/>
                <xs:element name="ElementC" type="ElementCType" minOccurs="0"/>
                <xs:element name="ElementD" type="ElementDType" minOccurs="0"/>
            </xs:sequence>
            <xs:assert test="(ElementB and ElementC) or (ElementB and ElementD)"/>
        </xs:complexType>
    </xs:element>
    
    <xs:element name="Root">
        <xs:complexType>
            <xs:sequence>
                <xs:element maxOccurs="unbounded" ref="ElementA"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Here's an example XML document that is associated with the above schema and demonstrates the test cases:

<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SO77537687.xsd">
    <!-- OK: B, C -->
    <ElementA>
        <ElementB></ElementB>
        <ElementC></ElementC>
    </ElementA>
    <!-- OK: B, C, D -->
    <ElementA>
        <ElementB></ElementB>
        <ElementC></ElementC>
        <ElementD></ElementD>
    </ElementA>
    <!-- OK: B, D -->
    <ElementA>
        <ElementB></ElementB>
        <ElementD></ElementD>
    </ElementA>
    <!-- All of these fail: B alone fails, C alone fails, and D alone fails. -->
    <!-- Also C, D fails because B isn't present -->
    <ElementA>
        <ElementB></ElementB>
    </ElementA>
    <ElementA>
        <ElementC></ElementC>
    </ElementA>    
    <ElementA>
        <ElementD></ElementD>
    </ElementA>    
    <ElementA>
        <ElementC></ElementC>
        <ElementD></ElementD>
    </ElementA>
</Root>