Десериализация сложных схем xsd (содержащих элементы группы замещения для наследования) с помощью xsd.exe или xsd2code
У меня проблемы с десериализацией / сериализацией некоторых схем xsd, особенно с элементами группы замещения (substitutiongroup) внутри. Я хочу создать классы C# из схем xsd, затем обработать их объектом и затем сериализовать их в допустимый формат XML. Есть 4 xsd-файла, которые я десериализирую и сериализую с помощью xsd2code или xsd.exe. Оба инструмента дают одинаковые неудовлетворительные результаты. Они игнорируют элементы группы замещения и не генерируют членов класса должным образом. Когда я запускаю xsd.exe или xsd2code, сгенерированный класс C# для BPMNPlane, например, не содержит члена BPMNShape(однако класс BPMNDiagram содержит BPMNPlane). Я пытался изменить сгенерированные классы C# (например, добавить члены / свойства), но сгенерированный вывод XML был неправильным. Я полагаю, что можно справиться с этим с помощью linq-to-xml, но это слишком много разных элементов, примерно 70, с дополнительными атрибутами свойств.
<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>
Я новичок и не имею опыта работы с xsd или linq-to-xml, но я думаю, что это лучший подход для работы со строго типизированными данными / объектами?
2 ответа
Во-первых, я поддержал ваш вопрос, так как он действительно поднимает редкий сценарий - на него также было трудно ответить, судя по тому, сколько людей передают его... Что также означает, что вам придется немного почитать:)...
Краткий ответ: xsd.exe создает полезный код; это может быть не то, что вы ожидали, и я объясню почему, но это работает (по крайней мере, с моими тестами); если у вас нет проблем с обменом этого XML, то просто следуйте тому, как он генерируется. Если нет, то Linq будет работать точно.
Итак, основная проблема начинается с того, как была создана XML-схема; учитывая, откуда это исходит, я был удивлен, увидев эту (кажущуюся) двусмысленность в стиле авторского дизайна, которая в конечном итоге также отвечает за то, почему xsd.exe, похоже, не дает ожидаемого результата.
Пожалуйста, начните с прочтения этой статьи с акцентом на разделы "Абстрактные атрибуты" и "Атрибут группы замещения".
Обычно глава группы замещения считается абстрактным элементом. Хотя спецификация этого не требует, я подозреваю, что многие люди делают это предположение в своих инструментах (xsd.exe - один из них), так как в противном случае существует риск двусмысленности с @xsi:type.
В схеме BPMN глава групп замещения не является абстрактным (тот, на который я смотрел); более того, элементы, используемые в качестве заголовка группы замещения, имеют абстрактный тип - это звучит как xsi: type. Короче говоря, если вы посмотрите на сгенерированный код, xsd.exe создает совершенно правильный код, делая выбор между использованием или не использованием xsi: type; это пошло с первым.
Этот код ссылается на сгенерированный код xsd.exe для создания простого XML.
BPMNEdge edge = new BPMNEdge();
edge.id = "B2";
// more code here for waypoint
plane.DiagramElement1 = new DiagramElement[] { edge };
Свойство DiagramElement1 будет в основном принимать любой тип, производный от типа DiagramElement, в основном полностью заполняя контракт (и предоставляя вам тип @xsi: для DiagramElement в сгенерированном XML).
XML ниже действителен; Я не мог понять, решит ли ваше резюме DiagramElement вашу проблему... Я не думаю, что это может быть так просто, но я оставлю это на ваше усмотрение.
<?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>
(Также действительный) XML ниже был сгенерирован инструментом (не кодом, сгенерированным xsd.exe); он показывает совершенно допустимую альтернативу XML, приведенному выше, с использованием членов группы замещения, чего вы и хотели. Все, что вам нужно сделать, это выяснить, что еще нужно поставить вместо DiagramElement. Я использовал этот график, чтобы изобразить это:
<?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>
Я думаю, что эта схема является идеальным примером, который показывает, как можно использовать ее в обоих направлениях (со стилем авторизации xsi: type или без него), используя только одну схему. Отличным тестом может быть выяснение того, можно ли десериализовать этот последний XML-код с использованием кода, сгенерированного xsd.exe, и какие изменения необходимо внести, чтобы он работал.
Я нашел решение для этого.
Насколько я понял, проблема заключается не в абстрактном определении, потому что класс DiagramElement определен как абстрактный в спецификациях, а в том факте, что класс BPMNShape находится в другом пространстве имен, чем класс DiagramElement. В этой ситуации кажется, что группы замещения не работают.
В спецификации BPMN есть другая аналогичная ситуация, но в которой группы подстановки определены для классов в одном пространстве имен (см., Например, tUserTask и tFlowElement), и в этом случае это работает.
Я обнаружил, что проблема находится в определении DiagramElement1 в классе Plane, где класс, созданный xsd, выглядит следующим образом:
[System.Xml.Serialization.XmlElementAttribute("DiagramElement")]
public DiagramElement[] DiagramElement1 {
get {
return this.diagramElement1Field;
}
set {
this.diagramElement1Field = value;
}
}
Я решил ничего не менять в исходном xsd, а просто обновить этот класс следующим образом:
[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;
}
}
И теперь это работает!
Очевидно, вы должны задокументировать и поддерживать изменения на случай, если вы повторно создадите классы.
Я не мог придумать лучшего способа добиться этого. если кто знает как это сделать, прокомментируйте.
Спасибо за ваше время! Это хорошее объяснение! Я также обнаружил, что занимаюсь этим:
BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;
bpmnl.DiagramElement1.Add(new BPMNShape());
работает, это дает мне следующую структуру XML:
Но то, что я действительно хочу, это:
<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 >
Итак, я могу написать:
BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;
что дает мне:
< BPMNDiagram name="bpmndiagramid">
< BPMNPlane>...
</ BPMNPlane>
</ BPMNDiagram >
Но, но не прямо это:
BPMNDiagram bpmnd = new BPMNDiagram();
BPMNPlane bpmnl = bpmnd.BPMNPlane;
BPMNShape myShape = bpmnl.BPMNShape;
Я подумал, что было бы быстрее, чем работать с LINQ 2 XML, создавать классы C# и работать с ними, но теперь я понимаю, что мне нужно углубиться в XML-схему / элементы и т. Д.