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>
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
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...>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With