Como você serializa uma string como CDATA usando XmlSerializer?

90

É possível por meio de um atributo de algum tipo serializar uma string como CDATA usando o .Net XmlSerializer?

Jamesaharvey
fonte
2
Uma coisa que vale a pena notar sobre as duas respostas é que você não precisa CDataContentse estiver apenas lendo XML. XmlSerializer.Deserializeirá transformá-lo automaticamente em texto para você.
Chris S

Respostas:

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
fonte
8
Para mim, essa não parece a solução mais elegante. Esta é a única maneira possível de fazer isso?
jamesaharvey
1
Acho que essa é a única maneira de conseguir isso, já vi esse tópico em outro lugar e sempre a mesma resposta. O exemplo de Philip é um pouco mais limpo, mas o mesmo conceito. A única outra maneira que conheço é implementar seu próprio <a href=" msdn.microsoft.com/en-us/library/…> em uma classe que representa o conteúdo CDATA.
csharptest.net
Eu queria fazer a mesma coisa porque parece que armazenar strings como CDATA parece implicar em menos tempo de processamento, já que com isso poderíamos 'apenas' ler / gravar strings 'como estão'. Quanto custa envolver instâncias de XmlDocument / XmlCDataSection?
tishma
E toda a coisa de Atributos está lá para que possamos manter as classes do modelo de domínio limpas dos detalhes da lógica de serialização. É tão triste se o caminho sujo é o único.
tishma
2
A solução de Philip um pouco mais abaixo na página é uma coisa mais organizada de se fazer.
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());

Resultado:

<?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
fonte
Isso salvou meu dia. Obrigado.
Robert,
4
// Caso você precise de um CDATA vazio, você pode definir o padrão se o valor de origem for nulo para evitar a exceção. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r isso também permite desserializar para o mesmo objeto? Estou tendo problemas com isso
Martin,
Como criar CDATA como um valor de texto (e não como um elemento), como <MyClass> <! [CDATA [<test> Hello World </test>]]> </MyClass>?
mko
1
Só precisa ser capaz de lidar com valores vazios / nulos do que a saída de <emptyfield><![CDATA[]]> </emptyfield>
bluee
91

Além da maneira postada por John Saunders, você pode usar um XmlCDataSection como o tipo diretamente, embora resulte praticamente na mesma coisa:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
fonte
1
@Philip, isso funciona para desserialização? Tenho visto notas dizendo que o setter receberá um valor XmlText.
John Saunders,
1
@John Saunders - Na verdade, ele recebe um valor XmlCharacterData no setter durante a desserialização, que é para o que serve a chamada para .Value no setter (originalmente o tinha como ToString () da memória, mas estava incorreto.)
Philip Rieck
1
@PhilipRieck E se precisarmos envolver um objeto personalizado em torno de uma CDataSection. Criar CDataSection aceita string.
zepelim
Obrigado! Solução mais fácil. Funciona bem para mim.
Antonio Rodríguez
42

Na classe a ser serializada:

public CData Content { get; set; }

E a classe 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
fonte
Funciona como um encanto. Obrigado.
Leonel Sanches da Silva
3
Essa resposta merece mais reconhecimento. Embora, o tipo CData personalizado não tenha mais os métodos internos convenientes que o tipo System.String desfruta.
Lionet Chen
boa, então primeira resposta
Hsin-Yu Chen
A resposta funciona muito bem. É uma pena que o XmlElement não funcione no campo string, então você poderia apenas adicionar um tipo cdata, mas tanto faz ...
jjxtra
Perfeito! Obrigado!
Roy
4

No meu caso estou usando campos mistos, alguns CDATA outros não, pelo menos para mim a seguinte solução está funcionando ....

Sempre lendo o campo Valor, estou obtendo o conteúdo, seja CDATA ou apenas texto simples.

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

    [XmlText]
    public string Value;

Antes tarde do que nunca.

Felicidades

Coderookie
fonte
Fantástico - tenho a sensação de que essa resposta me economizou muito tempo! Para obter informações, usei o atributo [XmlIgnore] em Value
d219
Como isso é diferente operacionalmente da resposta de pr0gg3r ?
Ruffin
4

Eu tinha uma necessidade semelhante, mas exigia um formato de saída diferente - queria um atributo no nó que contém o CDATA. Tirei alguma inspiração das soluções acima para criar a minha própria. Talvez ajude alguém no 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()
    {

    }
}

No objeto pai a ser serializado, tenho a seguinte propriedade:

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

Eu obtenho a seguinte saída:

<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 Ei
fonte
Eu precisava fazer exatamente isso. Obrigado!!
Lews Therin
2

Essa implementação tem a capacidade de processar CDATA aninhado dentro da string que você está codificando (com base na resposta original de John Saunders).

Por exemplo, suponha que você queira codificar a seguinte string literal em CDATA:

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

Você gostaria que a saída resultante se parecesse com isto:

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

O ciclo seguinte implementação vontade sobre a corda, dividida instâncias de ...]]>...em ...]]e >...e criar seções CDATA separados para cada.

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