Правильная XML-сериализация и десериализация «смешанных» типов в .NET - PullRequest
18 голосов
/ 02 апреля 2010

Моя текущая задача заключается в написании библиотеки классов для обработки файлов HL7 CDA.
Эти файлы HL7 CDA являются файлами XML с определенной XML-схемой, поэтому я использовал xsd.exe для генерации классов .NET для сериализации и десериализации XML.

Схема XML содержит различные типы, которые содержат атрибут mixed = "true" , указывающий, что узел XML этого типа может содержать обычный текст, смешанный с другими узлами 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, где узел абзаца выглядит следующим образом:

<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 для обработки этих документов?

Ответы [ 3 ]

22 голосов
/ 06 апреля 2010

Для решения этой проблемы мне пришлось изменить сгенерированные классы:

  1. Переместите XmlTextAttribute из свойства Text в свойство Items и добавьте параметр Type = typeof(string)
  2. Удалить свойство Text
  3. Удалите поле 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, где узел абзаца выглядит следующим образом:

<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 к поле или свойство, которое возвращает массив строк. Вы также можете подать заявку атрибут к массиву типа Объект, но вы должны установить тип свойство строки. В этом случае любой строки, вставленные в массив Сериализуется как текст XML.

Так что сериализация и десериализация теперь работают правильно, но я не знаю, есть ли другие побочные эффекты. Может быть, невозможно создать схему из этих классов с помощью xsd.exe, но в любом случае мне это не нужно.

3 голосов
/ 24 марта 2011

У меня была такая же проблема, как и я, и наткнулся на это решение изменения .cs, сгенерированного xsd.exe. Хотя это сработало, мне было неудобно изменять сгенерированный код, так как я должен был помнить, чтобы делать это каждый раз, когда я обновлял классы. Это также привело к некоторому неудобному коду, который нужно было проверить и привести к XmlNode [] для элементов mailto.

Моим решением было переосмыслить xsd. Я отказался от использования смешанного типа и по сути определил свой собственный смешанный тип.

У меня было это

XML: <text>some text <mailto>me@email.com</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>me@email.com</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;
        }
    }
}

порядок элементов теперь сохраняется при сериализации / десериализации, но мне нужно проверить / привести к / программирование для типов myTextTextMailto и myTextText.

Просто подумал, что я добавлю это как альтернативный подход, который работал для меня.

0 голосов
/ 02 апреля 2010

А как же

itemsField = new object[] 
{ 
    "first line", 
    new StrucDocBr(), 
    new StrucDocBr(), 
    "third line", 
};

...