tags:

views:

57

answers:

4

I want to define a xml schema where the element Connectors have 0-* child elements. Either Sequence, Association or Message in any order and 0 to many times. I.e.

<Connectors>
    <Sequence />
    <Association />
    <Message />
    <Sequence />
    <Sequence />
    <Message />
    <Message />
    <Association />
</Connectors>

I tried to define the following schema but it seems like the order is fixed.

<xs:element name="Connectors">
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="Association" minOccurs="0" maxOccurs="unbounded" />
            <xs:element ref="Message" minOccurs="0" maxOccurs="unbounded" />
            <xs:element ref="Sequence" minOccurs="0" maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
</xs:element>
A: 

Unfortunately there is no practical way in XML Schema to define "this set of child elements in any order but each occurs at least once".

You are stuck with either a defined order, or one of a set (possibly repeated).

EDIT: No practical way, manually generating every possible sequence is possible and would work, but the combinatorial explosion will rapidly get out of hand.

Richard
@Richard see my other answer on how this in fact is possible although not practically implementable on larger element sets.
jasso
@Jasso: manually declaring every possible sequence ... *interesting* :-)
Richard
@Richard: Yes, in a way this is like walking from Florida to New York. If you really really need to, you can do it. But seriously... it's just easier to use some other way to travel (or in this case, other language to define a schema) ;-)
jasso
+2  A: 

I solved it by setting to choice and setting minOccurs and maxOccurs attributes.

<xs:element name="Connectors">
    <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
            <xs:element ref="Association" />
            <xs:element ref="Message" />
            <xs:element ref="Sequence" />
        </xs:choice>
    </xs:complexType>
</xs:element>
Torbjörn Hansson
Clear and logical answer (+1). Although I added an answer of my own, I actually prefer yours. My answer is here only to show that only one more attribute would have made your original code to do what you wanted.
jasso
+1  A: 

@Torbjörn's own answer is more clear than this one, but only a one small change in your original attempt would have resulted in the same outcome: adding maxOccurs="unbounded" on the <xs:sequence> element.

<xs:element name="Connectors">
    <xs:complexType>
        <xs:sequence maxOccurs="unbounded">
            <xs:element minOccurs="0" name="Association" />
            <xs:element minOccurs="0" name="Message" />
            <xs:element minOccurs="0" name="Sequence" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

Random order is assured because every element has minOccurs="0" and the sequence can repeat itself over and over again.

jasso
Thank you very much jasso!
Torbjörn Hansson
+1  A: 

(This is not exactly an answer to OP, but an answer to another suggested answer to OP's question. As a quite new memeber, I'm not sure if this breaks some rules or conventions here on stackoverflow.com. Sorry.)

As user @Richard wrote in his answer:

Unfortunately there is no way in XML Schema to define "this set of child elements in any order but each occurs at least once".

You are stuck with either a defined order, or one of a set (possibly repeated).

It is correct that there is no tags or attributes that especially allows such structures, however the quoted text is theoretically not true, although the solution becomes quickly huge as the amount of elements grows. Writing a schema for such structure with several elements is laborious, error-prone and difficult to maintain. So even though a solution exists, practically implementing it is not possible or worth it.

Original posters question had a set of only three randomly occurring elements. Correct solution was simple, because none of the elements was mandatory. But even in that kind of case, with such a small set of elements, forcing each one of the elements to appear at least once is still moderately easy to implement. Code example below

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
    <!-- Schema for 3 elements with random order.
        Each element must appear at least once. -->

    <xs:element name="Association" type="xs:string"/>
    <xs:element name="Message" type="xs:string"/>
    <xs:element name="Sequence" type="xs:string"/>

    <xs:element name="root">
        <xs:complexType>
            <xs:sequence>
                <xs:element maxOccurs="unbounded" name="Connectors" type="unordered-3-group" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="unordered-3-group">
    <!-- state level 0 -->
        <xs:group ref="Connectors-state-0"/>
    </xs:complexType>

    <xs:group name="Connectors-state-0">
        <xs:sequence>
            <!-- Empty, no previous elements
            <xs:choice minOccurs="0" maxOccurs="unbounded">
            </xs:choice> -->
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Association"/>
                    <xs:group ref="Connectors-state-1a"/>
                </xs:sequence>
                <xs:sequence>
                    <xs:element ref="Message"/>
                    <xs:group ref="Connectors-state-1b"/>
                </xs:sequence>
                <xs:sequence>
                    <xs:element ref="Sequence"/>
                    <xs:group ref="Connectors-state-1c"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-1a">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Association"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Message" />
                    <xs:group ref="Connectors-state-2a"/>
                </xs:sequence>
                <xs:sequence>
                    <xs:element ref="Sequence" />
                    <xs:group ref="Connectors-state-2b"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-1b">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Message"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Association" />
                    <xs:group ref="Connectors-state-2a"/>
                </xs:sequence>
                <xs:sequence>
                    <xs:element ref="Sequence" />
                    <xs:group ref="Connectors-state-2c"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-1c">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Sequence"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Association" />
                    <xs:group ref="Connectors-state-2b"/>
                </xs:sequence>
                <xs:sequence>
                    <xs:element ref="Message" />
                    <xs:group ref="Connectors-state-2c"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-2a">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Association"/>
                <xs:element ref="Message"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Sequence" />
                    <xs:group ref="Connectors-state-3a"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-2b">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Association"/>
                <xs:element ref="Sequence"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Message" />
                    <xs:group ref="Connectors-state-3a"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-2c">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Message"/>
                <xs:element ref="Sequence"/>
            </xs:choice>
            <xs:choice>
                <xs:sequence>
                    <xs:element ref="Association" />
                    <xs:group ref="Connectors-state-3a"/>
                </xs:sequence>
            </xs:choice>
        </xs:sequence>
    </xs:group>

    <xs:group name="Connectors-state-3a">
        <xs:sequence>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element ref="Association"/>
                <xs:element ref="Message"/>
                <xs:element ref="Sequence"/>
            </xs:choice>
            <!-- Empty, no new elements
            <xs:choice>
                <xs:sequence>
                </xs:sequence>
            </xs:choice> -->
        </xs:sequence>
    </xs:group>

</xs:schema>

Connectors is used as a container just like OP had. Separate root element allows multiple Connectors only to make testing different combinations a bit easier.

Imitating different states with group elements

I have edited this answer and updated the code. New one is longer but it is a quite clear example of making use of state-based schema design. Code follows the different phases of a finite state machine that can validate this problem. All states are represented as <xs:group> elements and each group references other groups that represent all possible following states. All the <xs:group> elements obey same rules for their structure. The groups have a sequence of two choices. The first <xs:choice> is an unbounded repetition of the already seen elements. The second <xs:choice> contains sequences of all the possible new elements following a reference to a group that represents the next state.

Complexity of the problem

Case of N elements can be validated with a deterministic finite state machine that has 2^N different states (including the initial state). States will form N different levels and amount of states on each level corresponds to binomial coefficients i.e. amount of states on different levels is "n over k" where "n" represents the number of different elements and "k" represents the depth of level. Because adding one more element doubles the amount of needed states it also doubles the number of <xs:group>s and thus roughly doubles the LOC for this complex type.

Is there any use for this?

Maybe... or not. Manually writing and especially updating such structure for large element sets is probably unreasonable. However since all the groups have similar structure it should be relatively easy to programmatically generate appropriate XML Schema code for the whole complexType structure. One way to do this that comes into my mind is a function that takes two lists as parameters, seen and unseen elements and then adds all the seen elements to the first <xs:choice> element and for each unseen element creates a transition to appropriate new state and then calls itself for every new referenced state.

Bear in mind that even if generating code takes away the pain of manually keeping track of the whole structure, on large element sets the code size will grow so big that the size is no more reasonable. A set of 14 elements requires 2^14 = 16383 groups and at depth level 7 there is a maximum of (14 over 7) = 3432 parallel state groups, each group having 39 elements (= slightly over 40 LOC). No, you don't want anything like that. In such cases you should start looking for some other suitable schema definition language.

jasso