4

I have problems with deserializing/serializing some xsd schemas, specially with substitution group elements (substitutiongroup) within. What I want to do is to generate C# classes from xsd schemas, then handle with object and later serialize them into a valid XML format. There are 4 xsd files which I deserialize and serialize with xsd2code or xsd.exe. Both tools generate similar unsatisfactory results. They ignore "substitutiongroup" elements and don't generate class members properly. When i run xsd.exe or xsd2code the generated c# class for BPMNPlane for example don't contain a member BPMNShape(however BPMNDiagram class contain BPMNPlane). I tried to change the generated C# classes( e.g . add members/properties), but the generated XML output wasn't correct. I suppose one could master this with linq-to-xml, but they are too much different elements, approximately 70, with additional properties attributes.

<xsd:import namespace="http://www.omg.org/spec/DD/20100524/DC" schemaLocation="DC.xsd" />
<xsd:import namespace="http://www.omg.org/spec/DD/20100524/DI" schemaLocation="DI.xsd" />

<xsd:element name="BPMNDiagram" type="bpmndi:BPMNDiagram" />
<xsd:element name="BPMNPlane" type="bpmndi:BPMNPlane" />
<xsd:element name="BPMNLabelStyle" type="bpmndi:BPMNLabelStyle" />
<xsd:element name="BPMNShape" type="bpmndi:BPMNShape" substitutionGroup="di:DiagramElement" />
<xsd:element name="BPMNLabel" type="bpmndi:BPMNLabel" />
<xsd:element name="BPMNEdge" type="bpmndi:BPMNEdge" substitutionGroup="di:DiagramElement" />

<xsd:complexType name="BPMNDiagram">
    <xsd:complexContent>
        <xsd:extension base="di:Diagram">
            <xsd:sequence>
                <xsd:element ref="bpmndi:BPMNPlane" />
                <xsd:element ref="bpmndi:BPMNLabelStyle" maxOccurs="unbounded" minOccurs="0" />
            </xsd:sequence>
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>


<xsd:complexType name="BPMNPlane">
    <xsd:complexContent>
        <xsd:extension base="di:Plane">    
    <xsd:attribute name="bpmnElement" type="xsd:QName" />       
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="BPMNEdge">
    <xsd:complexContent>
        <xsd:extension base="di:LabeledEdge">
            <xsd:sequence>
                <xsd:element ref="bpmndi:BPMNLabel" minOccurs="0" />
            </xsd:sequence>
            <xsd:attribute name="bpmnElement" type="xsd:QName" />
            <xsd:attribute name="sourceElement" type="xsd:QName" />
            <xsd:attribute name="targetElement" type="xsd:QName" />
            <xsd:attribute name="messageVisibleKind" type="bpmndi:MessageVisibleKind" />
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>



<xsd:complexType name="BPMNShape">
    <xsd:complexContent>
        <xsd:extension base="di:LabeledShape">
            <xsd:sequence>
                <xsd:element ref="bpmndi:BPMNLabel" minOccurs="0" />
            </xsd:sequence>
            <xsd:attribute name="bpmnElement" type="xsd:QName" />
            <xsd:attribute name="isHorizontal" type="xsd:boolean" />
            <xsd:attribute name="isExpanded" type="xsd:boolean" />
            <xsd:attribute name="isMarkerVisible" type="xsd:boolean" />
            <xsd:attribute name="isMessageVisible" type="xsd:boolean" />
            <xsd:attribute name="participantBandKind" type="bpmndi:ParticipantBandKind" />
            <xsd:attribute name="choreographyActivityShape" type="xsd:QName"/>
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="BPMNLabel">
    <xsd:complexContent>
        <xsd:extension base="di:Label">
            <xsd:attribute name="labelStyle" type="xsd:QName" />
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="BPMNLabelStyle">
    <xsd:complexContent>
        <xsd:extension base="di:Style">
            <xsd:sequence>
                <xsd:element ref="dc:Font" />
            </xsd:sequence>
        </xsd:extension>
    </xsd:complexContent>
</xsd:complexType>

<xsd:simpleType name="ParticipantBandKind">
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="top_initiating" />
        <xsd:enumeration value="middle_initiating" />
        <xsd:enumeration value="bottom_initiating" />
        <xsd:enumeration value="top_non_initiating" />
        <xsd:enumeration value="middle_non_initiating" />
        <xsd:enumeration value="bottom_non_initiating" />
    </xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="MessageVisibleKind">
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="initiating" />
        <xsd:enumeration value="non_initiating" />
    </xsd:restriction>
</xsd:simpleType>

I'm newbie and have not experience with xsd's or linq-to-xml, but I think it's the better approach to work with strongly typed data/objects?

user615993
  • 185
  • 3
  • 15

3 Answers3

6

Firstly, I've upvoted your question since it really brings up a rare scenario - it also had a hard time being answered, judging by how many people pass it on... Which also means you'll have to do some reading :)...

The short answer is: xsd.exe creates usable code; it may not be what you've expected, and I'll explain why, but it works (at least with my tests); if you don't have problems exchanging that XML, than just go with it the way it is generated. If not then, Linq will work for sure.

So, the main issue starts with how the XML Schema was authored; considering where it is coming from I was surprised to see this (perceived) ambiguity in the authoring style, which ultimately is also responsible to why xsd.exe doesn't seem to yield your expected result.

Please start by reading this paper, with a focus on the section named "Abstract Attribute" and "SubstitutionGroup Attribute".

Normally, the head of a substitution group is supposed to be an abstract element. While this is not enforced by the spec, I suspect that many people make this assumption in their tooling (xsd.exe being one) since otherwise there's a risk of ambiguity with @xsi:type.

In BPMN schema, the head of the substitution groups aren't abstract (the one I looked at); more, the elements used as head of a substitution group are of an abstract type - this rings in xsi:type. To make a long story short, if you take a look at the generated code, xsd.exe creates a perfectly valid code, by making a choice between to use or not to use xsi:type; it went with the former.

This code references the xsd.exe generated code to create a simple XML.

BPMNEdge edge = new BPMNEdge();
edge.id = "B2";
// more code here for waypoint
plane.DiagramElement1 = new DiagramElement[] { edge };

The DiagramElement1 property will basically take in any type that derives from the DiagramElement type, basically fullfilling the contract (and giving you the @xsi:type for the DiagramElement in the generated XML).

The XML below is valid; I couldn't figure out if making the DiagramElement abstract would solve your problem... I don't think it could be that simple but I'll leave it up to you.

<?xml version="1.0" encoding="utf-16"?>
<BPMNPlane xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="A1" xmlns:q1="urn:tempuri-org:alpha" bpmnElement="q1:test" xmlns="http://www.omg.org/spec/BPMN/20100524/DI">
  <DiagramElement xmlns:q2="http://www.omg.org/spec/BPMN/20100524/DI" xsi:type="q2:BPMNEdge" id="B2" xmlns="http://www.omg.org/spec/DD/20100524/DI">
    <waypoint x="1" y="1" />
    <waypoint x="1" y="1" />
</DiagramElement>
</BPMNPlane> 

The (also valid) XML below was generated by a tool (not code generated by xsd.exe); it shows a perfectly valid alternative to the XML above, using members of the substitution group, which is what you wanted. All you have to do is figure out what else to put in place of DiagramElement. I used this graph to picture it:

enter image description here

<?xml version="1.0" encoding="utf-16"?>
<!-- Sample XML generated by QTAssistant (http://www.paschidev.com) -->
<BPMNPlane xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="A1" xmlns:q1="urn:tempuri-org:alpha" bpmnElement="q1:test" xmlns="http://www.omg.org/spec/BPMN/20100524/DI">
    <BPMNEdge xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" p4:any_Attr="anySimpleType" bpmnElement="qname1" sourceElement="qname1" targetElement="qname1" messageVisibleKind="initiating" id="ID1" xmlns:p4="otherNS" xmlns="http://www.omg.org/spec/BPMN/20100524/DI">
        <di:extension/>
        <di:waypoint x="1" y="1"/>
        <di:waypoint x="-1.7976931348623157E+308" y="-1.7976931348623157E+308"/>
        <BPMNLabel p4:any_Attr="anySimpleType" labelStyle="qname1" id="ID2">
            <di:extension/>
            <dc:Bounds x="1" y="1" width="1" height="1"/>
        </BPMNLabel>
    </BPMNEdge>
</BPMNPlane>

I think this schema is a perfect example that shows how one could have it both ways (with or without xsi:type authoring style) using one schema only. A great test might be to see if this latter XML can be deserialized using the code generated by the xsd.exe, and what changes would need to be done in order to make it work.

Petru Gardea
  • 21,373
  • 2
  • 50
  • 62
2

I have found a solution for this.

For what I have understood, the problem is not the astract definition, because the DiagramElement class is defined as abstract in the specifications, but the fact that the BPMNShape class is in another namespace than the DiagramElement class. In this situation, it seems that the substitution groups are not working.

In the BPMN specification there is another situation similar, but in which the substitution groups are defined for classes in the same namespace (see, for example the tUserTask and the tFlowElement), and, in that case, it works.

I have found that the problem is located at the definition of the DiagramElement1 in the Plane class, where the class created by xsd is as follows:

[System.Xml.Serialization.XmlElementAttribute("DiagramElement")]
    public DiagramElement[] DiagramElement1 {
        get {
            return this.diagramElement1Field;
        }
        set {
            this.diagramElement1Field = value;
        }
    }

I decided not to change anything in the original xsd, but just update this class in the following way:

    [System.Xml.Serialization.XmlElementAttribute("BPMNEdge", typeof(BPMNEdge), Namespace="http://www.omg.org/spec/BPMN/20100524/DI")]
    [System.Xml.Serialization.XmlElementAttribute("BPMNShape", typeof(BPMNShape), Namespace = "http://www.omg.org/spec/BPMN/20100524/DI")]
    [System.Xml.Serialization.XmlElementAttribute("DiagramElement")]
    public DiagramElement[] DiagramElement1 {
        get {
            return this.diagramElement1Field;
        }
        set {
            this.diagramElement1Field = value;
        }
    }

And now it works!

Obviously you must document and maintain the modification in case you regenerate the classes.

I was not able to figure out a better way to achieve this. if someone knows how to do it, please comment.

Dario
  • 31
  • 2
0

thank you for your time! Its a good explanation! I have also found, that doing this:

BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;
 bpmnl.DiagramElement1.Add(new BPMNShape());

works, it gives me the following XML structure:

But what I actually want is this:

<bpmndi: BPMNDiagram name="bpmndiagramid">
<bpmndi: BPMNPlane>
    <bpmndi:BPMNShape id="11" bpmnElement="functionsname">
        <dc:Bounds x="0" y="0" width="0" height="0"/>
    </bpmndi:BPMNShape>
</bpmndi: BPMNPlane>
</bpmndi: BPMNDiagram >

So, I can write:

BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;

which gives me:

< BPMNDiagram name="bpmndiagramid">
< BPMNPlane>...
</ BPMNPlane>
</ BPMNDiagram >

But but not directly this:

BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;
BPMNShape myShape = bpmnl.BPMNShape;

I thought, it would be faster than working with LINQ 2 XML, to generate c# classes and work with them, but I see now that I have to go deeper in XML Schema/Elements etc.

user615993
  • 185
  • 3
  • 15