Reputation: 9512
I'm trying to have a list of elements that are allowed in any order. Some of the elements are required (min of 1, max of 1), some are optional with a maximum of one and some are optional with any number. This is what I have and the XSD is valid, but when I go to validate an XML, the rules that I'm trying to implement aren't enforced. For example, id is not made to be required.
<xsd:complexType name="feedType">
<xsd:annotation>
<xsd:documentation>
The Atom feed construct is defined in section 4.1.1 of the format spec.
</xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="3" maxOccurs="unbounded">
<xsd:element name="author" type="atom:personType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="category" type="atom:categoryType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="contributor" type="atom:personType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="generator" type="atom:generatorType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="icon" type="atom:iconType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="id" type="atom:idType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="logo" type="atom:logoType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="rights" type="atom:textType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="subtitle" type="atom:textType" minOccurs="0" maxOccurs="1"/>
<xsd:element name="title" type="atom:textType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="updated" type="atom:dateTimeType" minOccurs="1" maxOccurs="1"/>
<xsd:element name="entry" type="atom:entryType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:attributeGroup ref="atom:commonAttributes"/>
</xsd:complexType>
Upvotes: 7
Views: 8320
Reputation: 49321
Basically, don't do that when using XSD. It is not designed for a use case like this.
You can validate with RelaxNG instead, using its unordered operation, or if you have a partner who requires an XSD then enforce an arbitrary order.
The schema for atom feeds given in RFC4287 uses RelaxNG for this reason:
atomFeed =
element atom:feed {
atomCommonAttributes,
(atomAuthor*
& atomCategory*
& atomContributor*
& atomGenerator?
& atomIcon?
& atomId
& atomLink*
& atomLogo?
& atomRights?
& atomSubtitle?
& atomTitle
& atomUpdated
& extensionElement*),
atomEntry*
}
See Principles of XML design: When the order of XML elements matters for further discussion of when to enforce order in XML
Upvotes: 2
Reputation: 153154
I just ran into the same problem and had a look at what they did in the XHTML XSD. Same situation there inside head
: title
is required, base
is optional, and then there's an arbitrary number of script
, style
, meta
, link
and object
elements allowed.
Here's what they did: First, they grouped the optional elements that may occur more than once:
<xs:group name="head.misc">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="script"/>
<xs:element ref="style"/>
<xs:element ref="meta"/>
<xs:element ref="link"/>
<xs:element ref="object"/>
</xs:choice>
</xs:sequence>
</xs:group>
Then they referenced this group in the actual head
definition:
<xs:sequence>
<xs:group ref="head.misc"/>
<xs:choice>
<xs:sequence>
<xs:element ref="title"/>
<xs:group ref="head.misc"/>
<xs:sequence minOccurs="0">
<xs:element ref="base"/>
<xs:group ref="head.misc"/>
</xs:sequence>
</xs:sequence>
<xs:sequence>
<xs:element ref="base"/>
<xs:group ref="head.misc"/>
<xs:element ref="title"/>
<xs:group ref="head.misc"/>
</xs:sequence>
</xs:choice>
</xs:sequence>
This is a bit tricky. Written as regex-like pseudo code, the above looks something like this:
misc=(script|style|meta|link|object)*
head=${misc}(title${misc}(base${misc})?|base${misc}title${misc})
So, technically it works.
However, this requires putting all possible permutations of the mandatory and optional elements (with the misc stuff in between) inside choice
. With n
elements, that's n!
child nodes. So for XHTML with n=2
they ended up with n!=2
. In your case with n=8
, it's going to be n!=40320
.
FWIW, here's the algorithm to generate your XSD:
result = '<xs:group name="misc"><xs:sequence><xs:choice minOccurs="0" maxOccurs="unbounded">'
forall(optionalArbitraryCountElements as element)
result += '<xs:element ref="' + element.name + '"/>'
result += '</xs:choice></xs:sequence></xs:group>'
result += '<xs:complexType name="feedType"><xs:sequence><xs:group ref="misc"/><xs:choice>'
permutations = getAllPermutations(mandatoryElements + optionalOnceElements)
foreach (permutations as p)
result += '<xs:sequence>'
foreach (p as element)
if (element.isOptional)
result += '<xs:sequence minOccurs="0">'
result += '<xs:element ref="' + element.name + '"/><xs:group ref="misc"/>'
if (element.isOptional)
result += '</xs:sequence>'
result += '</xs:sequence>'
result += '</xs:choice></xs:sequence></xs:complexType>'
return result
Upvotes: 13
Reputation: 976
choice
only allows one of its child elements to be present in the XML graph. It looks like you want to use sequence
if your elements are always in the same order. If the order is variable then you should use all
and wrap all elements that have maxOccurs="unbounded"
in a containing list element, because all
only allows 1 or zero occurrences of its child elements.
EDIT:
And you should remove the minOccurs & maxOccurs from the choice element. This only allows you to enforce 3 choices, but doesn't allow you specify what choices they are (including repeating the same element multiple times). I don't know what exactly you're trying to enforce, but it won't be effectively enforced that way.
EDIT 2:
You can create list wrappers as follows (using the link element as an example):
<xs:element name="linkList" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="link" type="atom:linkType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
and in the xml document you would (for example) nest your link elements in the linkList:
Old:
<other elements...>
<id>...</id>
<link>...</link>
<link>...</link>
<logo>...</logo>
<other elements...>
New:
<other elements...>
<id>...</id>
<linkList>
<link>...</link>
<link>...</link>
</linkList>
<logo>...</logo>
<other elements...>
Upvotes: 5