How to do mutually exclusive attributes in XML schema?
Asked Answered
S

6

14

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" />
Slouch answered 22/12, 2008 at 16:45 Comment(0)
A
5

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
Antagonist answered 23/12, 2008 at 8:48 Comment(0)
C
9

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>
Cannes answered 22/12, 2008 at 16:57 Comment(1)
It can be applied to attributes using XSD identity constraints, see my answer.Camporee
O
7

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...

Oleviaolfaction answered 22/12, 2008 at 16:46 Comment(5)
Is there any other schema language/style (other than XSD) where it would be possible?Excaudate
@Filip: The only other schema language I know of is relaxNG, but I know nothing about it.Cablegram
There's always xsd:annotation "Hey, don't include x and y on z".Plane
I just posted an answer with a RelaxNG solution.Antagonist
One could use post-processing (e.g, JAXB post-unmarshal listener as described in stackoverflow.com/questions/976378) to validate that there is mutual exclusion.Bedard
A
5

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
Antagonist answered 23/12, 2008 at 8:48 Comment(0)
C
3

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>
Camporee answered 8/11, 2017 at 13:57 Comment(0)
S
2

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.

Sombrous answered 26/1, 2013 at 16:56 Comment(2)
As far as it does solve the problem given I think it is – sadly! – an ugly solution. Somehow I hate it when I have to add those XSI attributes on my document just to cover up what I consider shortcomings of XSD. But maybe it is just me!Snout
I certainly wouldn't call it elegant, and I try to avoid it when I can. This is where owning an XML IDE pays off.Sombrous
G
1

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

Gregg answered 30/12, 2014 at 0:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.