Comment sérialiser une chaîne en tant que CDATA à l'aide de XmlSerializer?

90

Est-il possible via un attribut quelconque de sérialiser une chaîne en tant que CDATA à l'aide du .Net XmlSerializer?

Jamesaharvey
la source
2
Une chose à noter à propos des deux réponses est que vous n'en avez pas besoin CDataContentsi vous ne lisez que du XML. XmlSerializer.Deserializele transformera automatiquement en texte pour vous.
Chris S

Réponses:

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
la source
8
Cela ne me semble pas être la solution la plus élégante. Est-ce la seule façon possible de faire cela?
jamesaharvey
1
Je pense que c'est la seule façon d'y parvenir, j'ai vu ce sujet ailleurs et toujours la même réponse. L'exemple de Philip est un peu plus propre mais le même concept. Le seul autre moyen que je connaisse est d'implémenter votre propre <a href=" msdn.microsoft.com/en-us/library/…> sur une classe qui représente le contenu CDATA.
csharptest.net
Je voulais faire la même chose car il semble que stocker des chaînes comme CDATA semble impliquer moins de temps de traitement, car avec lui, nous ne pourrions «lire / écrire que des chaînes« telles quelles ». Quel est le coût des instances XmlDocument / XmlCDataSection?
tishma le
Et tout ce qui concerne les attributs est là pour que nous puissions garder les classes de modèle de domaine propres des détails de la logique de sérialisation. C'est tellement triste si le sale chemin est le seul moyen.
tishma le
2
La solution de Philip un peu plus bas sur la page est une chose plus ordonnée à faire.
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;
        }
    }
}

Usage:

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());

Production:

<?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
la source
Cela m'a sauvé la journée. Je vous remercie.
Robert
4
// Si vous avez besoin d'un CDATA vide, vous pouvez définir la valeur par défaut si la valeur source est nulle pour éviter les exceptions. XmlDocument().CreateCDataSection(MyString ?? String.Empty);
Asereware
@ pr0gg3r cela permet-il également de désérialiser vers le même objet? J'ai des problèmes avec ça
Martin
Comment créer CDATA en tant que valeur de texte (et non en tant qu'élément) comme <MyClass> <! [CDATA [<test> Hello World </test>]]> </MyClass>?
mko
1
Il suffit de pouvoir gérer les valeurs vides / nulles que de générer <emptyfield><![CDATA[[[ Often ]> </emptyfield>)
bluee
91

En plus de la manière publiée par John Saunders, vous pouvez utiliser directement une XmlCDataSection comme type, bien que cela se résume à presque la même chose:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}
Philip Rieck
la source
1
@Philip, cela fonctionne-t-il pour la désérialisation? J'ai vu des notes disant que le setter recevra une valeur XmlText.
John Saunders le
1
@John Saunders - Il reçoit en fait une valeur XmlCharacterData dans le setter pendant la désérialisation, ce à quoi sert l'appel à .Value dans le setter (je l'avais à l'origine comme ToString () de mémoire, mais c'était incorrect.)
Philip Rieck
1
@PhilipRieck Qu'en est-il si nous devons envelopper un objet personnalisé autour d'une CDataSection. Create CDataSection accepte la chaîne.
zeppelin
Je vous remercie! La solution la plus simple. Fonctionne bien pour moi.
Antonio Rodríguez
42

Dans la classe à sérialiser:

public CData Content { get; set; }

Et la 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
la source
Fonctionne comme un charme. Je vous remercie.
Leonel Sanches da Silva
3
Cette réponse mérite plus de reconnaissance. Cependant, le type CData personnalisé ne dispose plus de ces méthodes intégrées pratiques que le type System.String apprécie.
Lionet Chen
belle puis première réponse
Hsin-Yu Chen
La réponse fonctionne très bien. C'est dommage que XmlElement ne fonctionne pas sur un champ de chaîne, alors vous pouvez simplement ajouter un type cdata, mais peu importe ...
jjxtra
Parfait! Merci!
Roy
4

Dans mon cas, j'utilise des champs mixtes, certains CDATA d'autres non, du moins pour moi la solution suivante fonctionne ...

En lisant toujours le champ Valeur, j'obtiens le contenu, que ce soit CDATA ou simplement du texte brut.

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

    [XmlText]
    public string Value;

Mieux vaut tard que jamais.

À votre santé

Coderookie
la source
Fantastique - j'ai le sentiment que cette réponse m'a fait gagner du temps! Pour info, j'ai utilisé l'attribut [XmlIgnore] sur Value
d219 le
En quoi est-ce différent sur le plan opérationnel de la réponse de pr0gg3r ?
ruffin
4

J'avais un besoin similaire mais j'avais besoin d'un format de sortie différent - je voulais un attribut sur le nœud qui contient le CDATA. Je me suis inspiré des solutions ci-dessus pour créer la mienne. Peut-être que cela aidera quelqu'un à l'avenir ...

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()
    {

    }
}

Dans l'objet parent à sérialiser, j'ai la propriété suivante:

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

J'obtiens la sortie suivante:

<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
la source
J'avais besoin de faire exactement cela. Je vous remercie!!
Lews Therin
2

Cette implémentation a la capacité de traiter CDATA imbriqué dans la chaîne que vous encodez (en fonction de la réponse originale de John Saunders).

Par exemple, supposons que vous vouliez encoder la chaîne littérale suivante dans CDATA:

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

Vous voudriez que la sortie résultante ressemble à quelque chose comme ceci:

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

L'implémentation suivante bouclera sur la chaîne, divisera les instances de ...]]>...en ...]]et >...et créera des sections CDATA séparées pour chacune.

[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
la source