Question

I'm trying to make two XML attributes to be mutually exclusive. How can one create an XSD schema to capture this kind of scenario?

I would like to have one of these

<elem value="1" />
<elem ref="something else" />

but not

<elem value="1" ref="something else" />
Was it helpful?

Solution

Since RelaxNG was mentioned in Alnitak's answer, here is a solution with RelaxNG (a language which is, in most cases, better than W3C Schema). Do note the OR (|) in the definition of elem:

start = document
document = element document {elem+}
elem = element elem {ref | value}
ref = attribute ref {text}
value = attribute value {xsd:integer}

If I have this XML file:

<document>
    <elem value="1" />
    <elem ref="something else" />
</document>

It is accepted by rnv and xmlint:

 % rnv attributes-exclusive.rnc attributes-exclusive.xml             
 attributes-exclusive.xml

 % xmllint --noout --relaxng attributes-exclusive.rng attributes-exclusive.xml 
 attributes-exclusive.xml validates

If I add in the XML file:

<elem value="1" ref="something else" />

I get validation errors, as I want (do note that the error messages are suboptimal):

% rnv attributes-exclusive.rnc attributes-exclusive.xml    
attributes-exclusive.xml
attributes-exclusive.xml:4:0: error: attribute ^ref not allowed
required:
       after

% xmllint --noout --relaxng attributes-exclusive.rng attributes-exclusive.xml
attributes-exclusive.xml:4: element elem: Relax-NG validity error : Invalid attribute value for element elem
attributes-exclusive.xml fails to validate

OTHER TIPS

You can't do with attributes, but you can with child elements...

<element name="elem">
    <complexType>
        <choice>
            <element name="value"/>
            <element name="ref"/>
        </choice>
    </complexType>
</element>

This way you can have...

<elem>
    <value>1</value>
</elem>

or...

<elem>
    <rel>something else</rel>
</elem>

Unfortunately AFAIK you can't do that with XML Schema, I've had the same problem myself.

I've seen it suggested that if you need both of:

<elem type="xxx"> 
<elem ref="yyy">

then <elem> itself should be split into two types, since they've clearly got different attributes...

XSD has abstract types for this: http://www.tek-tips.com/viewthread.cfm?qid=1364846 (see post by tsuji)

Basically, you give the element at hand an abstract, complex type and define it's common attributes in there, which are exactly the same for all different use cases (not needed for your example).

Then you create 2 (or more) additional complex types which extend the abstract type I've just mentioned. Within these new types you define the differing sets of attributes between each use case. That's it for the XSD part.

Finally, you need to add a XSI type attribute to the resulting element in the schema instance document. So to be valid, the element must now have either one set of attributes or the other.

Not straight-forward, but flexible, and as we all know: the more flexible, the more difficult something becomes.

Actually, it is possible to define this in XSD 1.0 using identity constraints via xs:unique or xs:key. Which one you choose depends on how elements without either of the two attributes shall be treated: They're valid with xs:unique but invalid with xs:key. The code sample below contains both variants; make sure to remove one of them, otherwise the "stricter" xs:key takes precedence over xs:unique, i.e., one of the two attributes is required.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified" attributeFormDefault="unqualified">
  <xs:element name="container">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="elem" maxOccurs="unbounded">
          <xs:complexType>
            <xs:attribute name="value" use="optional"/>
            <xs:attribute name="ref" use="optional"/>
          </xs:complexType>
          <!-- Note: Use either xs:unique or xs:key -->
          <xs:unique name="attrsExclusiveOptional">
            <xs:annotation>
              <xs:documentation>Ensure that @value and @ref cannot occur simultaneously.
Both may be omitted.</xs:documentation>
            </xs:annotation>
            <xs:selector xpath="."/>
            <xs:field xpath="@value | @ref"/>
          </xs:unique>
          <xs:key name="attrsExclusiveRequired">
            <xs:annotation>
              <xs:documentation>Ensure that @value and @ref cannot occur simultaneously.
One of them is required.</xs:documentation>
            </xs:annotation>
            <xs:selector xpath="."/>
            <xs:field xpath="@value | @ref"/>
          </xs:key>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

This validates the following XML file:

<?xml version="1.0" encoding="UTF-8"?>
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:noNamespaceSchemaLocation="schema.xsd">
  <!-- Shall pass: -->
  <elem value="1" />
  <elem ref="something else" />

  <!-- Passes for xs:unique, fails for xs:key: -->
  <elem />

  <!-- Shall fail: -->
  <elem value="1" ref="something else" />
</container>

For readers coming to this later, note that the problem can be solved in XSD 1.1 using either "conditional type assignment" or assertions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top