axelrod
axelrod

Reputation: 3978

XSD: Choice between element and attribute

I'd like to have an element with either a content, or a certain attribute but not both. The element can look like this:

<Location ref="blah"/>

or this:

<Location> <aaaLocation>...</aaaLocation> <Location>

But not like this:

 <Location ref="blah"> <aaaLocation>...</aaaLocation> <Location>

I tried somee variations of this:

<xs:complexType name="FatherOfLocatiion">
        <xs:choice>
            <xs:element name="Location">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name ="aaaLocation" type="Alocation"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="Location">
                <xs:complexType>
                    <xs:attribute name="ref" type="xs:string" />
                </xs:complexType>
            </xs:element>
        </xs:choice>
        <xs:attribute name="name" type="xs:string" use="required"/>
    </xs:complexType>

The schema was valid in tools like XML spy, but when i tried to use jaxb to generate objects from it i got the following error:

Multiple elements with name 'Location', with different types, appear in the model group.

Is there any other way to enforce it?

Upvotes: 4

Views: 5273

Answers (3)

Petru Gardea
Petru Gardea

Reputation: 21658

There is a way to do it in XSD 1.0, which involves the use of the xsi:type attribute; the xsi:type is an XML attribute, however, an XSD-aware XML processor will treat it differently, along the lines of what you want. (For completeness, this approach is the precursor to alternative types in XSD 1.1, another way to achieve what you want).

Given the following XSD 1.0:

<?xml version="1.0" encoding="utf-8" ?>
<!-- XML Schema generated by QTAssistant/XSD Module (http://www.paschidev.com) -->
<xsd:schema elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="Location"/>
    <xsd:complexType name="LocationWithRef">
        <xsd:attribute name="ref" type="xsd:string" use="required"/>
    </xsd:complexType>
    <xsd:complexType name="LocationWithContent">
        <xsd:sequence>
            <xsd:element name="a"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema> 

The following XMLs would be valid, but no other combination; with attribute:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- Sample XML generated by QTAssistant (http://www.paschidev.com) -->
<Location xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="LocationWithRef" ref="a"/>

With element:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- Sample XML generated by QTAssistant (http://www.paschidev.com) -->
<Location xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="LocationWithContent"><a/></Location>

The above is sufficient to model what you've indicated for the XML world; however, for XSD to code binding technologies, you need to take an extra step to make it friendly, be that JAXB (or .NET). The thing is to link them with an abstract base type (below that is ALocation).

<?xml version="1.0" encoding="utf-8" ?>
<!-- XML Schema generated by QTAssistant/XSD Module (http://www.paschidev.com) -->
<xsd:schema elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="Location" type="ALocation"/>
    <xsd:complexType name="ALocation" abstract="true"/>
    <xsd:complexType name="LocationWithRef">
        <xsd:complexContent>
            <xsd:extension base="ALocation">
                <xsd:attribute name="ref" type="xsd:string" use="required"/>                
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
    <xsd:complexType name="LocationWithContent">
        <xsd:complexContent>
            <xsd:extension base="ALocation">
                <xsd:sequence>
                    <xsd:element name="a"/>
                </xsd:sequence>             
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:schema> 

The advantage of the above is that it gives what you've asked (alas, xsi:type may not be to your, or other peoples, liking) based on technologies that are supported by virtually any XSD processor that there is out there.

Unfortunately, XSD 1.1 support is very limited and if I would have to guess, it'll take a while before parts of it will be supported by JAXB (you seem to be interested in), let alone other XSD to code binding solutions...

Upvotes: 5

helderdarocha
helderdarocha

Reputation: 23637

You can't achieve this with XSD 1.0, but you can using XSD 1.1.

To tell your parser that your XSD is 1.1, add these attributes to your xs:schema element:

xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" vc:minVersion="1.1"

If your parser supports XSD 1.1, it will understand assertions, where you can use XPath to express your rule.

In this XSD document:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" vc:minVersion="1.1">
    <xs:element name="FatherOfLocation" type="FatherOfLocation"/>
    <xs:complexType name="FatherOfLocation">
        <xs:sequence>
            <xs:element name="Location" maxOccurs="unbounded">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name ="aaaLocation" type="xs:string" minOccurs="0"/>
                    </xs:sequence>
                    <xs:attribute name="ref" type="xs:string" use="optional"/>
                    <xs:assert test="(@ref and not(aaaLocation)) or (not(@ref) and aaaLocation)"></xs:assert>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

both the ref attribute and the aaaLocation element are optional, but the assert declares a rule that restricts that (using XPath in the context of the complexType):

(@ref and not(aaaLocation)) or (not(@ref) and aaaLocation)

So it will validate for:

<Location ref="blah"/>

and

<Location> <aaaLocation>...</aaaLocation> </Location>

but will not validate for

<Location ref="blah"> <aaaLocation>...</aaaLocation> </Location>

or

<Location></Location>

Upvotes: 1

Val&#233;ry
Val&#233;ry

Reputation: 4708

Alas, you want to restrict the content of an element based on one of its attributes. This is possible with other schema languages like Relax NG, but not with W3C XML Schema. Strange that XML Spy didn't raise any error.

The usual workaround is to use 2 different element names, e.g. Location and LocationWithRef.

Upvotes: 0

Related Questions