Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML Elements in Any Order, Some Required and Others Aren't

Tags:

xml

xsd

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>
like image 934
user994165 Avatar asked Jun 05 '12 18:06

user994165


2 Answers

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
like image 51
user123444555621 Avatar answered Oct 31 '22 01:10

user123444555621


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...>
like image 5
Mark M Avatar answered Oct 31 '22 03:10

Mark M