Sean B. Durkin
Sean B. Durkin

Reputation: 12729

XML schema for element whose simple type is driven by an attribute

I want to write an XML schema (xsd 1.1) for a document containing options. Each option has a name and a type (like boolean, integer, string, etc ...) and a datum matching that type. The list of types is fixed, but quiet long. (Only 3 are listed in listing 3 for simplicity.)

How do I do this without a ridiculous amount of repetition?

Use case 1

Here is a valid document for this schema..

Listing 1:

<abc:options>
  <abc:option name="is-enabled" type="boolean">false</abc:option>
  <abc:option name="wing-span"  type="float">1.2</abc:option>
</abc:options>

Use case 2

This document is not valid for this schema because the simple type bit is wrong for the @type attribute.

<abc:options>
  <abc:option name="is-enabled" type="boolean">24</abc:option>
  <abc:option name="wing-span"  type="float">this-is-not-a-number!</abc:option>
</abc:options>

What I have tried so far ...

Listing 3 is my attempt so far. But it is bad because I have to re-declare the @name attribute for each datum type. Is there a better solution? That is to say, one where I don't have to re-declare the @name attribute for each possible datum type.

Listing 3:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:abc="http://www.example.com"
           targetNamespace="http://www.example.com"
           elementFormDefault="qualified"
           attributeFormDefault="unqualified">

  <xs:element name="options">
    <xs:complexType>
      <xs:sequence minOccurs="1" maxOccurs="unbounded">
        <xs:element name="abc:option" type="option-Type"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:simpleType name="option-Datum-Type">
    <xs:restriction base="xs:string">
      <xs:enumeration value="boolean"/>
      <xs:enumeration value="integer"/>
      <xs:enumeration value="float"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="option-Type-boolean">
    <xs:simpleContent>
      <xs:extension base="xs:boolean">
        <xs:attribute name="name" type="xs:token" use="required" />
        <xs:attribute name="type" type="abc:option-Datum-Type" use="required" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="option-Type-string">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="name" type="xs:token" use="required" />
        <xs:attribute name="type" type="abc:option-Datum-Type" use="required" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="option-Type-float">
    <xs:simpleContent>
      <xs:extension base="xs:double">
        <xs:attribute name="name" type="xs:token" use="required" />
        <xs:attribute name="type" type="abc:option-Datum-Type" use="required" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <xs:complexType name="option-Type">
    <xs:alternative test="@type='boolean'"  type="abc:option-Type-boolean"/>
    <xs:alternative test="@type='string'"   type="abc:option-Type-string" />
    <xs:alternative test="@type='float'"    type="abc:option-Type-float"  />
    <xs:alternative type="xs:error"/>
  </xs:complexType>

</xs:schema>

Upvotes: 2

Views: 499

Answers (1)

sergioFC
sergioFC

Reputation: 6016

If the type can only be one of the atomic types you can use xs:assert like this:

<xs:complexType name="option-Type">
    <xs:simpleContent>
        <xs:extension base="xs:string">
            <xs:attribute name="name" type="xs:token" use="required" />
            <xs:attribute name="type" type="xs:string" use="required" />
            <xs:assert
                test="if (@type='boolean')  then . castable as xs:boolean
                else if (@type='float')     then . castable as xs:float
                else if (@type='int')       then . castable as xs:int
                else if (@type='string')    then . castable as xs:string
                else false()"/>
        </xs:extension>
    </xs:simpleContent>
</xs:complexType>

Notes:

  • You don't need to declare any new type. If you want you can even also skip the declaration of the enumeration.

  • Using this approach you need a new line for every new possible type (you don't really need a new line but it is easy to read with every type in a different line).

  • You can use text() instead of . if you found it to be more clear

  • Notice how simply this approach would be if XPath 2.0 had an eval function similar to javascript and other languages eval function:

    <xs:assert test="eval(text() || ' castable as xs:' || @type)"/>
    

    I though an eval/parse function was going to be added to XPath 3.0 spec but I think it finally has not been added.

  • Unlike instance of you cannot use lists (*,+) other than ? with castable as operator. You can only use atomic types using this approach.

  • Cast to string string should always succed as the type is declared as xs:string.

Upvotes: 2

Related Questions