17

Given an XML layout like this, I'm trying to create a XSD schema to validate it.

<RootNode>
  <ChildA />
  <ChildC />
  <ChildB />
  <ChildB />
  <ChildA />
</RootNode>

The requirements are as follows:

  • ChildA, ChildB and ChildC may occur in any order. (<xs:sequence> unsuitable)
  • ChildA is mandatory but may occur multiple times.
  • ChildB is optional and may occur multiple times.
  • ChildC is optional and may occur once only.

The technique I usually use to create an unordered list of nodes is to use a <xs:choice maxOccurs="unbounded"> with each possible node in the list, however, I am unable to create the minOccurs="1" constraint on ChildA and the maxOccurs="1" contraint on ChildC. (The # of occurances of the choice takes precedence over those of the elements here).

<xs:element name="RootNode">
  <xs:complexType>
    <xs:choice minOccurs="1" maxOccurs="unbounded">
      <xs:element name="ChildA" minOccurs="1"/>
      <xs:element name="ChildB" />
      <xs:element name="ChildC" maxOccurs="1"/>
    </xs:choice>
  </xs:complexType>
</xs:element>
Cœur
  • 37,241
  • 25
  • 195
  • 267
Mark H
  • 13,797
  • 4
  • 31
  • 45
  • I don't think the current XML schema is capable of doing this right now. Have you checked into other validation systems, like RelaxNG (www.relaxng.org) maybe?? – marc_s Aug 01 '10 at 19:56
  • Related question with, at the time of writing this, better answers: https://stackoverflow.com/questions/2290360/xsd-how-to-allow-elements-in-any-order-any-number-of-times/12012599 – Flow Sep 23 '17 at 15:36

3 Answers3

11

Update: In XSD 1.1m some of the constraints on all-groups have been lifted. See the answers here and here.

Not a simple one but seems doable. Difficult part here is that Schema definitions must be deterministic. Approach I used was to visualize the problem by drawing a finite state automata of it and then to write a regular expression that corresponded that automata. It is not at all as complicated as it might sound. Still, using some other validation system would have likely provided simpler answer.

I have done some testing but missing out some special cases is easy. Please comment if you spot an error.

...and here is the code:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" >

    <!-- Schema for elements ChildA, ChildB and ChildC
        The requirements are as follows:
            * ChildA, ChildB and ChildC may occur in any order.
            * ChildA is mandatory but may occur multiple times.
            * ChildB is optional and may occur multiple times.
            * ChildC is optional and may occur once only.
    -->

    <xsd:element name="root">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="ABC-container" type="ABC" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>

    <xsd:complexType name="ABC">
        <xsd:sequence>
            <xsd:element name="ChildB" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
            <xsd:choice>
                <xsd:sequence maxOccurs="1">
                    <xsd:element name="ChildC" type="xsd:string"/>
                    <xsd:element name="ChildB" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
                    <xsd:element name="ChildA" type="xsd:string"/>
                    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
                        <xsd:element name="ChildA" type="xsd:string" minOccurs="0"/>
                        <xsd:element name="ChildB" type="xsd:string" minOccurs="0"/>
                    </xsd:sequence>
                </xsd:sequence>
                <xsd:sequence maxOccurs="1">
                    <xsd:element name="ChildA" type="xsd:string" minOccurs="1"/>
                    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
                        <xsd:element name="ChildA" type="xsd:string" minOccurs="0"/>
                        <xsd:element name="ChildB" type="xsd:string" minOccurs="0"/>
                    </xsd:sequence>
                    <xsd:sequence minOccurs="0" maxOccurs="1">
                        <xsd:element name="ChildC" type="xsd:string"/>
                        <xsd:sequence minOccurs="0" maxOccurs="unbounded">
                            <xsd:element name="ChildA" type="xsd:string" minOccurs="0"/>
                            <xsd:element name="ChildB" type="xsd:string" minOccurs="0"/>
                        </xsd:sequence>
                    </xsd:sequence>
                </xsd:sequence>
            </xsd:choice>
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>
Flow
  • 23,572
  • 15
  • 99
  • 156
jasso
  • 13,736
  • 2
  • 36
  • 50
  • 6
    "By combining and nesting the various groups provided by XML Schema, and by setting the values of `minOccurs and maxOccurs`, it is possible to represent any content model expressible with an XML 1.0 DTD." But they never said it would be pretty. – Will May 25 '11 at 00:28
  • Jasso. This is very clever. I am trying to modify it to provide for: no mandatory element (lose ChildA); and many 0-or-1-occurance items (like ChildC). I'm having real trouble. I've posted the question at http://stackoverflow.com/questions/14321579/how-to-make-a-schema-for-an-unordered-list-where-some-occur-once-some-many-time. You seem to be an expert at this. If you could have a look I'd appreciate it. – Dave Jan 31 '13 at 16:06
  • @Dave Thanks for appreciating my efforts. I wrote an answer to your question. It came a bit late, because I had not logged in here for few weeks. – jasso Feb 12 '13 at 23:57
  • That answer is very complex but the only option if you use XSD 1.0. If you are able to use [XSD 1.1](https://www.w3.org/TR/xmlschema11-1/) then `xs:all` does what you want, and the approach mentioned in this answer becomes unnecessarily complex. See also https://stackoverflow.com/a/10887039/194894 – Flow Sep 23 '17 at 15:43
2

This should do what you specified:

<xs:element name="RootNode">   
  <xs:complexType>     
    <xs:all>       
      <xs:element name="ChildA" minOccurs="1"/>      
      <xs:element name="ChildB" />       
      <xs:element name="ChildC" minOccurs="0" maxOccurs="1"/>     
    </xs:all>   
  </xs:complexType> 
</xs:element> 
karakfa
  • 66,216
  • 7
  • 41
  • 56
  • Thanks for trying, but this doesn't quite fit. A and B can appear multiple times, but the `` element specifies that each element in it may appear zero or one time only. It would be nice if there were a solution as simple as this, but I think jasso's solution is probably the best we'll get. – Mark H Jun 06 '12 at 09:26
  • 1
    what happens if you set minOccurs=1 and maxOccurs=1 for all child elements? – demented hedgehog Apr 28 '17 at 22:51
  • That answer is correct (AFAIKT) if you use [XSD 1.1](https://www.w3.org/TR/xmlschema11-1/). [G1.3 5.](https://www.w3.org/TR/xmlschema11-1/#changes) explains the difference between XSD 1.0 that allow for this. – Flow Sep 23 '17 at 15:41
  • `all` can not contain unbound child nodes. – ceving Dec 05 '18 at 10:35
1

I was just reading the relax-NG shortcut syntax.

I'm guessing this would be condensed to the following in relax-ng's compact syntax:

head = element root { ChildA & ChildC? & ChildB* }

That sure is pretty.

Texas
  • 11
  • 1