Julian Gold
Julian Gold

Reputation: 1286

XML Schema for variable element type

I have an XML element for a User Interface tool which looks like this:

<Layout Type="{type}"...>

If Type is "Stack" then the element looks like this:

<Layout Type="Stack" Orientation="Horizontal"/>

But if it's "Margin" then the element is

<Layout Type="Margin" North="1" Center="3"/>

I'm struggling to write an XML schema that copes with this. Some assistance would be appreciated.

Upvotes: 0

Views: 2442

Answers (1)

C. M. Sperberg-McQueen
C. M. Sperberg-McQueen

Reputation: 25034

One simple approach is to declare the element Layout as having attributes named Type (required), Orientation, North, and Center (all optional) and specify that if @Type = "Stack" then Orientation is meaningful and North and Center are not, whereas if @Type = 'Margin' then North and Center mean this or that, and Orientation has no meaning.

This has the advantage that you can do it in virtually any schema language and it keeps things relatively simple. It has the disadvantage that the responsibility for verifying the co-occurrence constraint shifts from the validator to the consuming software.

A second approach, using XSD 1.1, is to use conditional type assignment: declare two types with appropriate attributes and then specify the conditions under which each is used.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:tns="http://www.example.com/nss/layout"
  targetNamespace="http://www.example.com/nss/layout"
  elementFormDefault="qualified"> 

  <!--* Strictly speaking, this type is not essential,
      * but it gives an overview of the kinds of layout
      * we expect and allows a reviewer to have more
      * confidence that the conditional type assignment
      * covers all the necessary bases. *-->
  <xs:simpleType name="layout-types">
    <xs:restriction base="xs:NMTOKEN">
      <xs:enumeration value="Stack" />
      <xs:enumeration value="Margin" />      
    </xs:restriction>
  </xs:simpleType>

  <!--* Two different types for the two different
      * kinds of layout. *-->
  <xs:complexType name="StackLayout">
    <xs:attribute name="Type" 
                  type="tns:layout-types" 
                  use="required"/>
    <xs:attribute name="Orientation" 
                  type="xs:string" 
                  use="required"/>
  </xs:complexType>
  <xs:complexType name="MarginLayout">
    <xs:attribute name="Type" 
                  type="tns:layout-types" 
                  use="required"/>
    <xs:attribute name="North" 
                  type="xs:int" 
                  use="required"/>
    <xs:attribute name="Center" 
                  type="xs:int" 
                  use="required"/>
  </xs:complexType>

  <!--* The Layout element is bound to type tns:StackLayout
      * if its Type attribute has the value 'Layout'.
      * Otherwise, it's bound to tns:MarginLayout, if its
      * Type attribute = 'Margin'.  Otherwise, the Layout
      * element instance is invalid.
      * If there's a default type you can use, you don't
      * have to use xs:error.
      *-->
  <xs:element name="Layout">
    <xs:alternative test="@Type='Layout'" 
                    type="tns:StackLayout"/>
    <xs:alternative test="@Type='Margin'" 
                    type="tns:MarginLayout"/>
    <xs:alternative type="xs:error"/>
  </xs:element>

</xs:schema>

If you don't have access to an XSD 1.1 validator, you can check the co-occurrence constraint using Schematron, or you can write a Relax NG schema that validates the constraints.

Upvotes: 2

Related Questions