How do you serialize a string as CDATA using XmlSerializer?

C#.NetXml Serialization

C# Problem Overview


Is it possible via an attribute of some sort to serialize a string as CDATA using the .Net XmlSerializer?

C# Solutions


Solution 1 - C#

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

Output:

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

Solution 2 - C#

In addition to the way posted by John Saunders, you can use an XmlCDataSection as the type directly, although it boils down to nearly the same thing:

private string _message;
[XmlElement("CDataElement")]
public XmlCDataSection Message
{  
    get 
    { 
        XmlDocument doc = new XmlDocument();
        return doc.CreateCDataSection( _message);
    }
    set
    {
        _message = value.Value;
    }
}

Solution 3 - C#

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

Solution 4 - C#

In the class to be serialized:

public CData Content { get; set; }

And the CData class:

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

Solution 5 - C#

I had a similar need but required a different output format - I wanted an attribute on the node that contains the CDATA. I took some inspiration from the above solutions to create my own. Maybe it will help someone in the future...

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

    }
}

In the parent object to be serialised, I have the following property:

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

I get the following output:

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

Solution 6 - C#

In my case I'm using mixed fields, some CDATA some not, at least for me the following solution is working....

By always reading the Value field, I'm getting the contents, regardless whether CDATA or just plain text.

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

Better late than never.

Cheers

Solution 7 - C#

This implementation has the ability to process nested CDATA within the string you're encoding (based on John Saunders original answer).

For example, suppose you wanted to encode the following literal string into CDATA:

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

You would want the resultant output to look something like this:

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

The following implementation will loop over the string, split up instances of ...]]>... into ...]] and >... and create separate CDATA sections for each.

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

Solution 8 - C#

This works pretty well

using System.Collections.ObjectModel;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;

public class CDataContent
{
    public CDataContent()
    {
    }

    public CDataContent(string content)
    {
        this.Content = content;
    }

    [XmlIgnore]
    public string Content
    {
        get => this.CData.FirstOrDefault()?.Value;
        set
        {
            this.CData.Clear();
            this.CData.Add(new XmlDocument().CreateCDataSection(value));
        }
    }

    [XmlText]
    public Collection<XmlNode> CData { get; } = new();

    public static implicit operator CDataContent(string value) => new(value);

    public static implicit operator string(CDataContent value) => value.Content;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjamesaharveyView Question on Stackoverflow
Solution 1 - C#pr0gg3rView Answer on Stackoverflow
Solution 2 - C#Philip RieckView Answer on Stackoverflow
Solution 3 - C#John SaundersView Answer on Stackoverflow
Solution 4 - C#sagisView Answer on Stackoverflow
Solution 5 - C#Adam HeyView Answer on Stackoverflow
Solution 6 - C#CoderookieView Answer on Stackoverflow
Solution 7 - C#Iain FraserView Answer on Stackoverflow
Solution 8 - C#satnhakView Answer on Stackoverflow