如何使用 XmlSerializer 将字符串序列化为 CDATA?

是否可以通过某种属性使用.Net XmlSerializer 将字符串序列化为 CDATA?

91982 次浏览
[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
}

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

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

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

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

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>

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