views:

197

answers:

2

I'm trying to create a tool that can generate XSDs for the XAML produced by serializing a type in .NET, but this is not specifically .NET related.

Let's say I have a type. This type has properties and is a collection.

public class MyType : Collection<CollectedType>
{
  public PType1 PropertyOne {get;set;}
  public PType2 PropertyTwo {get;set;}
}

This serializes to the following (omitting the object graph construction):

<MyType xmlns="clr-namespace:blahblahblah">
  <CollectedType name="First instance in the collection"/>
  <CollectedType name="Second instance in the collection"/>
  <MyType.PropertyOne>
    <PType1 Value = "Serialized object in PropertyOne" />
  </MyType.PropertyOne>
  <MyType.PropertyTwo>
    <PType2 Value = "Serialized object in PropertyTwo" />
  </MyType. PropertyTwo >
</MyType>

In generating the XSD for this type, I can say the following:

  • MyType is a complexType
  • MyType will contain a reference to a group called CollectedTypeGroup
    • CollectedTypeGroup contains CollectedType's element and elements for types that extend CollectedType
  • MyType will contain an element called MyType.PropertyOne
    • PType1 is another complexType
  • MyType will contain an element called MyType.PropertyTwo
    • PType2 is another complexType

This is all relatively easy to do. Here's a chunk of the generated xsd:

  <xs:complexType name="MyType">
    <xs:sequence>
      <xs:element name="MyType.PropertyOne" type="PType1"/>
      <xs:element name="MyType.PropertyOne" type="PType1"/>
      <xs:group ref="CollectedTypeGroup"/>
    </xs:sequence>
  </xs:complexType>

Now comes the hard part. Because the XML will be mapped back to an object graph, I have a list of restrictions on how elements are added to MyType that MUST BE enforced by the schema. If these three requirements and only these three are not enforced, I face issues with users attempting to use my schema:

1) Elements added to MyType must not be restricted to a particular order
2) Elements that represent a property of my object can only appear once or not at all
3) Group elements must be unbounded; they can appear anywhere in the parent element and 0...* times

This is where I am having a horrendous time. I cannot find a satisfactory combination of choices, sequences, alls etc. to satisfy these three requirements. I have also tried placing the elements in a separate group, complexTypes, etc. Nothing seems to work.

How can I combine my elements and my groups into a single complexType and meet my three requirements?

+1  A: 

Does this meet your needs?

  <xs:element name="MyType" type="MyType.Type"/>
    <xs:complexType name="MyType.Type">
      <xs:sequence>
        <xs:group ref="CollectedTypeGroup" minOccurs="0" maxOccurs="unbounded"/>
        <xs:choice minOccurs="0">
          <xs:sequence>
            <xs:element name="MyType.PropertyOne" type="PType1.Type"/>
            <xs:group ref="CollectedTypeGroup" minOccurs="0" maxOccurs="unbounded"/>
            <xs:sequence minOccurs="0">
              <xs:element name="MyType.PropertyTwo" type="PType2.Type"/>
              <xs:group ref="CollectedTypeGroup" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:sequence>
          <xs:sequence>
            <xs:element name="MyType.PropertyTwo" type="PType2.Type"/>
            <xs:group ref="CollectedTypeGroup" minOccurs="0" maxOccurs="unbounded"/>
            <xs:sequence minOccurs="0">
              <xs:element name="MyType.PropertyOne" type="PType1.Type"/>
              <xs:group ref="CollectedTypeGroup" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:sequence>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
    <xs:group name="CollectedTypeGroup">
      <xs:choice>
        <xs:element name="CollectedType" type="CollectedType.Type"/>
        <xs:element name="DerivedCollectedType" type="CollectedType.Type"/>
        <xs:element name="DerivedCollectedType2" type="CollectedType.Type"/>
      </xs:choice>
    </xs:group>
    <xs:complexType name="CollectedType.Type">
      <xs:attribute name="name" type="xs:string"/>
    </xs:complexType>
    <xs:complexType name="PType1.Type">
      <xs:sequence>
        <xs:element name="PType1">
          <xs:complexType>
            <xs:attribute name="Value" type="xs:string"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
    <xs:complexType name="PType2.Type">
      <xs:sequence>
        <xs:element name="PType2">
          <xs:complexType>
            <xs:attribute name="Value" type="xs:string"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
Alohci
Wow, that was a bunch of work... however it violates #1--Elements added must not be restricted to a particular order. Members of CollectedTypeGroup must be added before the property elements. This restriction has no valid meaning for what I'm trying to do...
Will
I'm not clear what restriction is violated here. Could you post an actual example that doesn't validate?
Alohci
I can add MyType.PropertyOne more than once.
Will
Also, because of the sequence, you can't add MyType.PropertyTwo before MyType.PropertyOne.
Will
A: 

This has puzzled people for years, but it is not possible to define this content model in XML Schema.

The only schema particle that guarantees that your elements appears once or not at all, and in any order is xsd:all. However, as you will have probably discovered already, the maximum bound for elements in xsd:all is 1, not unbounded.

If you had a container element for your collection elements, you could get away with this:

<xs:complexType name="MyType">
    <xs:sequence>
      <xs:element name="MyType.PropertyOne" type="PType1" minOccurs="0"/>
      <xs:element name="MyType.PropertyTwo" type="PType2" minOccurs="0"/>
      <xs:element name="collection" type="CollectionType" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>
<xs:complexType name="CollectionType">
    <xs:sequence>
       <xs:group ref="CollectedTypeGroup"/>
    </xs:sequence>
</xs:complexType>

Extracting things into groups, complex types, and so on, will not help you work around this problem as the Schema processor will flatten it to perform its validation.

FYI, Relax NG does not have this limitation. You can use its "interleave" construct to define what you are looking for (it's the same as schema 'all' but allows unbounded children).

Hope this helps.

xcut
Well, it doesn't really help, because XSD is too limited. But I curse the W3C for that, not you. I eventually bailed on the whole XML thing and dove deep into XAML. A much better platform for serializing CLR objects which understands the limitations and limits of markup representations of object graphs.
Will