Корректная сериализация XML и десериализация "смешанных" типов в .NET.
Моя текущая задача заключается в написании библиотеки классов для обработки CDA файлов HL7.
Эти файлы CDA HL7 являются XML файлами с определенной схемой XML, поэтому я использовал xsd.exe для создания классов .NET для сериализации и десериализации XML.
XML-схема содержит различные типы, которые содержат атрибут mixed = "true" , указывая, что XML node этого типа может содержать обычный текст, смешанный с другими узлами XML.
Соответствующая часть схемы XML для одного из этих типов выглядит следующим образом:
<xs:complexType name="StrucDoc.Paragraph" mixed="true">
<xs:sequence>
<xs:element name="caption" type="StrucDoc.Caption" minOccurs="0"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="br" type="StrucDoc.Br"/>
<xs:element name="sub" type="StrucDoc.Sub"/>
<xs:element name="sup" type="StrucDoc.Sup"/>
<!-- ...other possible nodes... -->
</xs:choice>
</xs:sequence>
<xs:attribute name="ID" type="xs:ID"/>
<!-- ...other attributes... -->
</xs:complexType>
сгенерированный код для этого типа выглядит следующим образом:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(TypeName="StrucDoc.Paragraph", Namespace="urn:hl7-org:v3")]
public partial class StrucDocParagraph {
private StrucDocCaption captionField;
private object[] itemsField;
private string[] textField;
private string idField;
// ...fields for other attributes...
/// <remarks/>
public StrucDocCaption caption {
get {
return this.captionField;
}
set {
this.captionField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("br", typeof(StrucDocBr))]
[System.Xml.Serialization.XmlElementAttribute("sub", typeof(StrucDocSub))]
[System.Xml.Serialization.XmlElementAttribute("sup", typeof(StrucDocSup))]
// ...other possible nodes...
public object[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public string[] Text {
get {
return this.textField;
}
set {
this.textField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute(DataType="ID")]
public string ID {
get {
return this.idField;
}
set {
this.idField = value;
}
}
// ...properties for other attributes...
}
Если я десериализую XML-элемент, где абзац node выглядит следующим образом:
<paragraph>first line<br /><br />third line</paragraph>
Результат заключается в том, что элементы и текстовые массивы читаются следующим образом:
itemsField = new object[]
{
new StrucDocBr(),
new StrucDocBr(),
};
textField = new string[]
{
"first line",
"third line",
};
Из этого нет никакого способа определить точный порядок текста и других узлов.
Если я сериализовать снова, результат будет выглядеть так:
<paragraph>
<br />
<br />first linethird line
</paragraph>
Сериализатор по умолчанию просто сериализует элементы сначала, а затем текст.
Я попытался реализовать IXmlSerializable
в классе StrucDocParagraph, чтобы я мог контролировать десериализацию и сериализацию содержимого, но он довольно сложный, так как существует так много классов, и я еще не пришел к решению, потому что я "Не знаю, рассчитывается ли это усилие.
Есть ли какой-то легкий обходной путь к этой проблеме, или это даже возможно, выполняя пользовательскую сериализацию через IXmlSerializable
?
Или я должен просто использовать XmlDocument
или XmlReader
/XmlWriter
для обработки этих документов?
Ответы
Ответ 1
Чтобы решить эту проблему, мне пришлось модифицировать сгенерированные классы:
- Переместите
XmlTextAttribute
из свойства Text
в свойство Items
и добавьте параметр Type = typeof(string)
- Удалите свойство
Text
- Удалите поле
textField
В результате сгенерированный код (измененный) выглядит следующим образом:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(TypeName="StrucDoc.Paragraph", Namespace="urn:hl7-org:v3")]
public partial class StrucDocParagraph {
private StrucDocCaption captionField;
private object[] itemsField;
private string idField;
// ...fields for other attributes...
/// <remarks/>
public StrucDocCaption caption {
get {
return this.captionField;
}
set {
this.captionField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("br", typeof(StrucDocBr))]
[System.Xml.Serialization.XmlElementAttribute("sub", typeof(StrucDocSub))]
[System.Xml.Serialization.XmlElementAttribute("sup", typeof(StrucDocSup))]
// ...other possible nodes...
[System.Xml.Serialization.XmlTextAttribute(typeof(string))]
public object[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute(DataType="ID")]
public string ID {
get {
return this.idField;
}
set {
this.idField = value;
}
}
// ...properties for other attributes...
}
Теперь, если я десериализую XML-элемент, где абзац node выглядит следующим образом:
<paragraph>first line<br /><br />third line</paragraph>
Результат заключается в том, что массив элементов считывается следующим образом:
itemsField = new object[]
{
"first line",
new StrucDocBr(),
new StrucDocBr(),
"third line",
};
Это именно то, что мне нужно, порядок элементов и их содержимого правильный.
И если я снова сериализую, результат снова верен:
<paragraph>first line<br /><br />third line</paragraph>
То, что указывало мне в правильном направлении, было ответом Гийома, я также подумал, что так должно быть возможно. И затем это было в документации MSDN до XmlTextAttribute
:
Вы можете применить XmlTextAttribute к поле или свойство, которое возвращает массив строк. Вы также можете применить атрибут массива типа Объект, но вы должны установить тип свойство string. В этом случае любой строки, вставленные в массив, являются сериализован как XML-текст.
Итак, сериализация и десериализация работают исправно, но я не знаю, есть ли другие побочные эффекты. Возможно, теперь невозможно создать схему из этих классов с помощью xsd.exe, но мне это все равно не нужно.
Ответ 2
У меня была такая же проблема, как и это, и натолкнулся на это решение изменения .cs, сгенерированного xsd.exe. Хотя это действительно сработало, мне было неудобно изменять сгенерированный код, так как мне нужно было бы это делать, когда я восстанавливал классы. Это также привело к некоторому неудобному коду, который должен был протестировать и применить к XmlNode [] для элементов mailto.
Моим решением было переосмыслить xsd. Я отбросил использование смешанного типа и по существу определил свой собственный смешанный тип.
У меня было это
XML: <text>some text <mailto>[email protected]</mailto>some more text</text>
<xs:complexType name="text" mixed="true">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="mailto" type="xs:string" />
</xs:sequence>
</xs:complexType>
и изменен на
XML: <mytext><text>some text </text><mailto>[email protected]</mailto><text>some more text</text></mytext>
<xs:complexType name="mytext">
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="text">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string" />
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="mailto">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string" />
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:sequence>
</xs:complexType>
Мой сгенерированный код теперь дает мне класс myText:
public partial class myText{
private object[] itemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("mailto", typeof(myTextTextMailto))]
[System.Xml.Serialization.XmlElementAttribute("text", typeof(myTextText))]
public object[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
}
порядок элементов теперь сохраняется в сериализации/десериализации, но мне приходится проверять /cast to/program на типы myTextTextMailto
и myTextText
.
Просто подумал, что я брошу это как альтернативный подход, который работал у меня.
Ответ 3
Что насчет
itemsField = new object[]
{
"first line",
new StrucDocBr(),
new StrucDocBr(),
"third line",
};
?