Search code examples
xmlxsdpolymorphismxsd-validationxml-validation

Can I validate polymorphic XML elements based on a child Type element value?


I want to validate polymorphic Shape elements, differentiated by the Type child element (not attribute) value. Below are sibling Circle and Rectangle Shape elements. Circles have a Radius and only 1 Point. Rectangles don't have Radius and have 4 Point elements:

<?xml version="1.0" encoding="UTF-8" ?>
<Shapes>
  <Shape>
    <Type>Circle</Type>
    <ID>A1234</ID>
    <Label>This is round</Label>
    <Radius>5.4</Radius>
    <Points>
      <Point>
        <X>5.00</X>
        <Y>2.00</Y>
      </Point>
    </Points>
  </Shape>
  <Shape>
    <Type>Rectangle</Type>
    <ID>B4567</ID>
    <Label>This is not round</Label>
    <Points>
      <Point>
        <X>0.00</X>
        <Y>0.00</Y>
      </Point>
      <Point>
        <X>4.00</X>
        <Y>0.00</Y>
      </Point>
      <Point>
        <X>4.00</X>
        <Y>2.00</Y>
      </Point>
      <Point>
        <X>0.00</X>
        <Y>2.00</Y>
      </Point>
    </Points>
  </Shape>
</Shapes>

Here's a NON-functional schema along the lines of what I was hoping to do:

  <xsd:simpleType name="ShapeTypeEnum">
    <xsd:restriction base="xsd:string">
      <xsd:enumeration value="Circle"/>
      <xsd:enumeration value="Rectangle"/>
    </xsd:restriction>
  </xsd:simpleType>

  <xsd:complexType name="ShapeBase">
    <xsd:sequence>
      <xsd:element name="Type" type="ShapeTypeEnum"/>
      <xsd:element name="ID" type="xsd:string"/>
      <xsd:element name="Label" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>

  <xsd:complexType name="Shape" type="Circle">
    <xsd:complexContent>
      <xsd:extension base="ShapeBase">
        <xsd:all>
          <xsd:element name="Radius" type="xsd:decimal"/>
          <xsd:element name="Points">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element minOccurs="1" maxOccurs="1" name="Point" type="Point"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </xsd:all>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:complexType name="Shape" type="Rectangle">
    <xsd:complexContent>
      <xsd:extension base="ShapeBase">
        <xsd:all>
          <xsd:element name="Points">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element minOccurs="4" maxOccurs="4" name="Point" type="Point"/>
              </xsd:sequence>
            </xsd:complexType>
          </xsd:element>
        </xsd:all>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:complexType name="Point">
    <xsd:all>
      <xsd:element name="X" type="xsd:decimal"/>
      <xsd:element name="Y" type="xsd:decimal"/>
    </xsd:all>
  </xsd:complexType>

The lines <xsd:complexType name="Shape" type="Rectangle"> and <xsd:complexType name="Shape" type="Circle"> don't work. Is it possible to validate identically named elements with different schema sections based on the value of a child Element (Type)?


Solution

  • In XSD 1.0, it can't be done. In XSD 1.1, it can, using assertions.

    Though even using assertions, it's not that easy (it would be much easier if Type were an attribute). You need to define a content model that's effectively a union of all the different models for different shapes (you can't use a simple xs:choice in this example because it would violate UPA), and then you need to define assertions like

    <xs:assert test="exists(radius) = (type = 'Circle')"/>
    
    <xs:assert test="count(points) = 4 or type != 'Rectangle'"/>
    

    XSD 1.1 is supported in Altova, Saxon, and Xerces, but not for example by the Microsoft schema processor.