¿Cómo se serializa una cadena como CDATA usando XmlSerializer?

91

¿Es posible a través de un atributo de algún tipo serializar una cadena como CDATA usando el .Net XmlSerializer?

jamesaharvey
fuente
2
Una cosa que vale la pena señalar acerca de las dos respuestas es que no las necesita CDataContentsi solo está leyendo XML. XmlSerializer.Deserializelo convertirá automáticamente en texto para usted.
Chris S

Respuestas:

62
[XmlRoot("root")]
public class Sample1Xml
{
    internal Sample1Xml()
    {
    }

    [XmlElement("node")]
    public NodeType Node { get; set; }

    #region Nested type: NodeType

    public class NodeType
    {
        [XmlAttribute("attr1")]
        public string Attr1 { get; set; }

        [XmlAttribute("attr2")]
        public string Attr2 { get; set; }

        [XmlIgnore]
        public string Content { get; set; }

        [XmlText]
        public XmlNode[] CDataContent
        {
            get
            {
                var dummy = new XmlDocument();
                return new XmlNode[] {dummy.CreateCDataSection(Content)};
            }
            set
            {
                if (value == null)
                {
                    Content = null;
                    return;
                }

                if (value.Length != 1)
                {
                    throw new InvalidOperationException(
                        String.Format(
                            "Invalid array length {0}", value.Length));
                }

                Content = value[0].Value;
            }
        }
    }

    #endregion
}
John Saunders
fuente
9
Para mí, esta no parece la solución más elegante. ¿Es esta la única forma posible de hacer esto?
jamesaharvey
1
Creo que esta es la única forma de lograrlo, he visto este tema en otros lugares y siempre la misma respuesta. El ejemplo de Philip es un poco más limpio pero el mismo concepto. La única otra forma que conozco es implementar su propio <a href=" msdn.microsoft.com/en-us/library/…> en una clase que represente el contenido CDATA.
csharptest.net
Quería hacer lo mismo porque parece que almacenar cadenas como CDATA parece implicar menos tiempo de procesamiento, ya que con él podríamos "solo" leer / escribir cadenas "tal cual". ¿Qué tan caro es involucrar instancias de XmlDocument / XmlCDataSection?
tishma
Y todo el asunto de los Atributos está ahí, por lo que podemos mantener las clases del modelo de dominio limpias de los detalles de la lógica de serialización. Es tan triste si el camino sucio es el único.
tishma
2
La solución de Philip un poco más abajo en la página es algo más ordenado.
Karl
99
[Serializable]
public class MyClass
{
    public MyClass() { }

    [XmlIgnore]
    public string MyString { get; set; }
    [XmlElement("MyString")]
    public System.Xml.XmlCDataSection MyStringCDATA
    {
        get
        {
            return new System.Xml.XmlDocument().CreateCDataSection(MyString);
        }
        set
        {
            MyString = value.Value;
        }
    }
}

Uso:

MyClass mc = new MyClass();
mc.MyString = "<test>Hello World</test>";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
StringWriter writer = new StringWriter();
serializer.Serialize(writer, mc);
Console.WriteLine(writer.ToString());

Salida:

<?xml version="1.0" encoding="utf-16"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <MyString><![CDATA[<test>Hello World</test>]]></MyString>
</MyClass>
pr0gg3r
fuente
Esto acaba de salvarme el día. Gracias.
Robert
4
// En caso de que necesite un CDATA vacío, puede establecer el valor predeterminado si el valor de la fuente es nulo para evitar una excepción. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r, ¿esto también permite deserializar al mismo objeto? Tengo problemas con eso
Martin
¿Cómo crear CDATA como un valor de texto (y no como un elemento) como <MyClass> <! [CDATA [<test> Hello World </test>]]> </MyClass>?
mko
1
Solo necesita poder manejar valores vacíos / nulos que generar <emptyfield><![CDATA[]]> </emptyfield>
bluee
91

Además de la forma publicada por John Saunders, puede usar un XmlCDataSection como tipo directamente, aunque se reduce a casi lo mismo:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
fuente
1
@Philip, ¿esto funciona para la deserialización? He estado viendo notas que dicen que el establecedor recibirá un valor XmlText.
John Saunders
1
@John Saunders: en realidad, recibe un valor XmlCharacterData en el configurador durante la deserialización, que es para lo que se llama a .Value en el configurador (originalmente lo tenía como ToString () de la memoria, pero eso era incorrecto).
Philip Rieck
1
@PhilipRieck ¿Qué pasa si necesitamos envolver un objeto personalizado alrededor de una sección CData? Create CDataSection acepta cadenas.
zepelín
¡Gracias! Solución más sencilla. Funciona bien para mi.
Antonio Rodríguez
43

En la clase que se serializará:

public CData Content { get; set; }

Y la clase CData:

public class CData : IXmlSerializable
{
    private string _value;

    /// <summary>
    /// Allow direct assignment from string:
    /// CData cdata = "abc";
    /// </summary>
    /// <param name="value">The string being cast to CData.</param>
    /// <returns>A CData object</returns>
    public static implicit operator CData(string value)
    {
        return new CData(value);
    }

    /// <summary>
    /// Allow direct assignment to string:
    /// string str = cdata;
    /// </summary>
    /// <param name="cdata">The CData being cast to a string</param>
    /// <returns>A string representation of the CData object</returns>
    public static implicit operator string(CData cdata)
    {
        return cdata._value;
    }

    public CData() : this(string.Empty)
    {
    }

    public CData(string value)
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value;
    }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        _value = reader.ReadElementString();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteCData(_value);
    }
}
sagis
fuente
Funciona de maravilla. Gracias.
Leonel Sanches da Silva
3
Esta respuesta merece más reconocimiento. Aunque, el tipo CData personalizado ya no tiene esos métodos integrados convenientes que disfruta el tipo System.String.
Lionet Chen
bien, entonces primera respuesta
Hsin-Yu Chen
La respuesta funciona muy bien. Es una pena que XmlElement no funcione en el campo de cadena, entonces podría simplemente agregar un tipo cdata, pero lo que sea ...
jjxtra
¡Perfecto! ¡Gracias!
Roy
5

Tenía una necesidad similar, pero necesitaba un formato de salida diferente: quería un atributo en el nodo que contiene el CDATA. Me inspiré en las soluciones anteriores para crear la mía propia. Quizás ayude a alguien en el futuro ...

public class EmbedScript
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlText]
    public XmlNode[] Script { get; set; }

    public EmbedScript(string type, string script)
    {
        Type = type;
        Script = new XmlNode[] { new XmlDocument().CreateCDataSection(script) };
    }

    public EmbedScript()
    {

    }
}

En el objeto principal que se va a serializar, tengo la siguiente propiedad:

    [XmlArray("embedScripts")]
    [XmlArrayItem("embedScript")]
    public List<EmbedScript> EmbedScripts { get; set; }

Obtengo el siguiente resultado:

<embedScripts>
    <embedScript type="Desktop Iframe">
        <![CDATA[<div id="play_game"><iframe height="100%" src="http://www.myurl.com" width="100%"></iframe></div>]]>
    </embedScript>
    <embedScript type="JavaScript">
        <![CDATA[]]>
    </embedScript>
</embedScripts>
Adam Hey
fuente
1
Necesitaba hacer exactamente esto. ¡¡Gracias!!
Lews Therin
4

En mi caso, estoy usando campos mixtos, algunos CDATA otros no, al menos para mí, la siguiente solución está funcionando ...

Al leer siempre el campo Valor, obtengo el contenido, independientemente de si es CDATA o solo texto sin formato.

    [XmlElement("")]
    public XmlCDataSection CDataValue {
        get {
            return new XmlDocument().CreateCDataSection(this.Value);
        }
        set {
            this.Value = value.Value;
        }
    }

    [XmlText]
    public string Value;

Mejor tarde que nunca.

Salud

Coderookie
fuente
Fantástico - ¡Tengo la sensación de que esta respuesta me ahorró mucho tiempo! Para obtener información, utilicé el atributo [XmlIgnore] en Value
d219
¿En qué se diferencia operativamente esto de la respuesta de pr0gg3r ?
Ruffin
2

Esta implementación tiene la capacidad de procesar CDATA anidado dentro de la cadena que está codificando (según la respuesta original de John Saunders).

Por ejemplo, suponga que desea codificar la siguiente cadena literal en CDATA:

I am purposefully putting some <![CDATA[ cdata markers right ]]> in here!!

Querría que la salida resultante se viera así:

<![CDATA[I am purposefully putting some <![CDATA[ cdata markers right ]]]]><![CDATA[> in here!!]]>

El siguiente bucle aplicación voluntad sobre la cadena, se separan los casos de ...]]>...en ...]]y >...y crear secciones CDATA separados para cada uno.

[XmlRoot("root")]
public class Sample1Xml
{
    internal Sample1Xml()
    {
    }

    [XmlElement("node")]
    public NodeType Node { get; set; }

    #region Nested type: NodeType

    public class NodeType
    {
        [XmlAttribute("attr1")]
        public string Attr1 { get; set; }

        [XmlAttribute("attr2")]
        public string Attr2 { get; set; }

        [XmlIgnore]
        public string Content { get; set; }

        [XmlText]
        public XmlNode[] CDataContent
        {
            get
            {
                XmlDocument dummy = new XmlDocument();
                List<XmlNode> xmlNodes = new List<XmlNode>();
                int tokenCount = 0;
                int prevSplit = 0;
                for (int i = 0; i < Content.Length; i++)
                {
                    char c = Content[i];
                    //If the current character is > and it was preceded by ]] (i.e. the last 3 characters were ]]>)
                    if (c == '>' && tokenCount >= 2)
                    {
                        //Put everything up to this point in a new CData Section
                        string thisSection = Content.Substring(prevSplit, i - prevSplit);
                        xmlNodes.Add(dummy.CreateCDataSection(thisSection));
                        prevSplit = i;
                    }
                    if (c == ']')
                    {
                        tokenCount++;
                    }
                    else
                    {
                        tokenCount = 0;
                    }
                }
                //Put the final part of the string into a CData section
                string finalSection = Content.Substring(prevSplit, Content.Length - prevSplit);
                xmlNodes.Add(dummy.CreateCDataSection(finalSection));

                return xmlNodes.ToArray();
            }
            set
            {
                if (value == null)
                {
                    Content = null;
                    return;
                }

                if (value.Length != 1)
                {
                    throw new InvalidOperationException(
                        String.Format(
                            "Invalid array length {0}", value.Length));
                }

                Content = value[0].Value;
            }
        }
    }
Iain Fraser
fuente