2010-04-02 26 views
16

Mi tarea actual consiste en escribir una biblioteca de clase para procesar archivos HL7 CDA.
Estos archivos HL7 CDA son archivos XML con un esquema XML definido, así que usé xsd.exe para generar clases .NET para la serialización y deserialización de XML.Corregir serialización XML y deserialización de tipos "mixtos" en .NET

El esquema XML contiene varios tipos que contienen el atributo mixed = "true", que especifica que un nodo XML de este tipo puede contener texto normal mezclado con otros nodos XML.
La parte pertinente del esquema XML para uno de estos tipos tiene el siguiente aspecto:

<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> 

El genera el código de este tipo se parece a esto:

/// <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... 
} 

Si deserializar un elemento XML donde el nodo del párrafo se ve así:

<paragraph>first line<br /><br />third line</paragraph> 

El resultado es que las matrices de elementos y de texto se leen así:

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

De esto no hay manera posible de determinar el orden exacto del texto y los demás nodos.
Si serializar esto de nuevo, el resultado se ve exactamente como esto:

<paragraph> 
    <br /> 
    <br />first linethird line 
</paragraph> 

El serializador predeterminado simplemente serializa los artículos primero y luego el texto.

Intenté implementar IXmlSerializable en la clase StrucDocParagraph para poder controlar la deserialización y serialización del contenido, pero es bastante complejo ya que hay tantas clases involucradas y no he llegado a una solución porque no lo hago Saber si el esfuerzo vale la pena.

¿Existe algún tipo de solución fácil a este problema, o incluso es posible mediante la serialización personalizada a través de IXmlSerializable? ¿O debería usar XmlDocument o XmlReader/XmlWriter para procesar estos documentos?

Respuesta

20

Para resolver este problema tuve que modificar las clases generadas:

  1. mover el XmlTextAttribute de la propiedad Text a la propiedad Items y agregar el parámetro Type = typeof(string)
  2. Retire la propiedad Text
  3. Retire la textField campo

Como resultado, el código generado (modificado) se ve así:

/// <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... 
} 

Ahora si Deserialize un elemento XML en el que el nodo de párrafo es el siguiente:

<paragraph>first line<br /><br />third line</paragraph> 

El resultado es que el elemento array se lee así:

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

Esto es exactamente lo que necesito, el orden de los elementos y su contenido es correcto.
Y si serializar esto de nuevo, el resultado es más correcta:

<paragraph>first line<br /><br />third line</paragraph> 

Lo que me apunta en la dirección correcta fue la respuesta de Guillaume, también pensé que debe ser posible de esta manera. Y también estaba presente en el MSDN documentation to XmlTextAttribute:

Puede aplicar el XmlTextAttribute a un campo o una propiedad que devuelve una matriz de cadenas . También puede aplicar el atributo a una matriz del tipo Objeto, pero debe establecer la propiedad Tipo en la cadena. En ese caso, cualquier cadena insertada en la matriz es serializada como texto XML.

Así que la serialización y la deserialización funcionan ahora, pero no sé si hay otros efectos secundarios. Tal vez ya no sea posible generar un esquema de estas clases con xsd.exe, pero no lo necesito de todos modos.

+0

Esto parece no funcionar más (mi versión de System.Xml es 4.0.0) El problema es que rastrea los nombres de los elementos en la matriz Elementos a través de una matriz de cadena ItemsElementName y los elementos deben coincidir 1-a-1 . Este requisito genera un error si está trabajando desde un modelo de objeto que ha rellenado al deserializar un documento XML, porque el XMLSerializer no coloca entradas representativas en la matriz ItemsElementName para ellos. Por lo tanto, un nodo de texto seguido de un elemento xml seguido de un nodo de texto genera 3 entradas en su matriz Elementos, pero solo 1 en ItemsElementName. – shahzbot

1

¿Qué hay de

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

?

+0

Esto da como resultado una InvalidOperationException cuando trato de serializar el objeto (debido a las cadenas dentro de los elementos Field, la matriz itemsField solo puede contener objetos de esos tipos especificados por los atributos [XmlElement] para la propiedad pública 'Items') – Stefan

+0

Puede encontrar ayuda aquí: http://msdn.microsoft.com/en-us/library/kz8z99ds.aspx ¿Alguna advertencia de validación de esquema? – Guillaume

+0

Ya encontré esa página durante mis búsquedas, se trata de otro problema. El esquema de mis documentos xml es correcto, lo valido antes de la deserialización y después de la serialización. Pero encontré la respuesta a mi problema en este momento, su sugerencia con la matriz itemsField ya estaba cerca, solo necesitaba algunas modificaciones adicionales en el código generado. Lo publicaré en unos minutos. – Stefan

3

Tuve el mismo problema que este, y encontré esta solución de alterar los archivos .cs generados por xsd.exe. Aunque funcionó, no me sentía cómodo alterando el código generado, ya que tendría que recordar hacerlo cada vez que regenerase las clases. También condujo a algún código incómodo que tuvo que probar y emitir a XmlNode [] para los elementos de mailto.

Mi solución fue replantear el xsd. Me deshice del uso del tipo mixto, y esencialmente definí mi propio tipo mixto.

Tenía esta

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> 

y cambiaron a

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> 

Mi código generado ahora me da un myText clase:

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; 
     } 
    } 
} 

el orden de los elementos que ahora se conserva en la serilización/deserialización, pero tengo que probar/lanzar/programar contra los tipos myTextTextMailto y myTextText.

Sólo pensé que lo lanzaría como un enfoque alternativo que funcionó para mí.

+1

Acepto que su enfoque es la solución preferida para este problema para alguien que define y usa su propio esquema XML. Mi problema era que no tenía la opción de alterar el XSD porque estaba controlado por un tercero. Así que tuve que modificar las clases generadas, lo cual es, por las razones que usted indicó, algo que solo debería hacerse si no hay otra forma. – Stefan

Cuestiones relacionadas