Wrap Properties with Cdata Section - Xml Serialization C#

Wrap properties with CData Section - XML Serialization C#

With some effort and customization, it possible to get close to what you want, however XmlSerializer will always place the CData nodes at the end of the container element. Your example XML shows the CData nodes between specific nodes of the container element. As long as you don't need this precise control, you can use How do you serialize a string as CDATA using XmlSerializer? to do nested serializations, like so:

public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }

[DataMember]
[JsonProperty]
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public List<Option> ListB { get; set; }

[DataMember]
public List<string> ListC { get; set; }

[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public Product Product { get; set; }

[XmlText] // NECESSARY TO EMIT CDATA NODES
[IgnoreDataMember]
[JsonIgnore]
public XmlNode[] CDataContent
{
get
{
return XmlWrapCDataHelper.GetCDataContent(this);
}
set
{
XmlWrapCDataHelper.SetCDataContent(this, value);
}
}
}

public class Product
{
public string ProductId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}

public class Option
{
public string OptionValue { get; set; }
public string OptionName { get; set; }
}

Using the following extension methods and custom attribute:

[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class XmlWrapCDataAttribute : Attribute
{
public XmlWrapCDataAttribute() { this.Namespace = string.Empty; }
public XmlWrapCDataAttribute(string name) : this() { this.Name = name; }

public string Name { get; set; }

public string Namespace { get; set; }
}

public static class XmlWrapCDataHelper
{
static Tuple<PropertyInfo, XmlWrapCDataAttribute> [] XmlWrapCDataProperties(Type type)
{
return type.GetProperties()
.Where(p => p.GetGetMethod() != null && p.GetSetMethod() != null)
.Select(p => Tuple.Create(p, p.GetCustomAttribute<XmlWrapCDataAttribute>()))
.Where(p => p.Item2 != null)
.ToArray();
}

public static XmlNode[] GetCDataContent(object obj)
{
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType());
return properties.Select(p => (XmlNode)p.Item1.GetValue(obj, index).GetCData(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace)).ToArray();
}

public static void SetCDataContent(object obj, XmlNode [] nodes)
{
if (nodes == null || nodes.Length < 1)
return;
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType()).ToDictionary(p => XName.Get(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace), p => p);
var xml = "<Root>" + String.Concat(nodes.Select(c => c.Value)) + "</Root>";
foreach (var element in XElement.Parse(xml).Elements())
{
Tuple<PropertyInfo, XmlWrapCDataAttribute> pair;
if (properties.TryGetValue(element.Name, out pair))
{
var value = element.Deserialize(pair.Item1.PropertyType, element.Name.LocalName, element.Name.Namespace.NamespaceName);
pair.Item1.SetValue(obj, value, index);
}
}
}
}

public static class XmlSerializationHelper
{
public static XmlCDataSection GetCData(this object obj, string rootName, string rootNamespace)
{
return obj == null ? null : new System.Xml.XmlDocument().CreateCDataSection(obj.GetXml(XmlSerializerFactory.Create(obj.GetType(), rootName, rootNamespace)));
}

public static XCData GetCData(this object obj, XmlSerializer serializer = null)
{
return obj == null ? null : new XCData(obj.GetXml(serializer));
}

public static string GetXml(this object obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}

public static object Deserialize(this XContainer element, Type type, string rootName = null, string rootNamespace = null)
{
return element.Deserialize(type, XmlSerializerFactory.Create(type, rootName, rootNamespace));
}

public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer = null)
{
using (var reader = element.CreateReader())
{
return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
}
}

public static T DeserializeXML<T>(this string xmlString, XmlSerializer serializer = null)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
}
}

public static class XmlSerializerFactory
{
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;

static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}

public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}

This will parse your provided XML successfully, and in return generate XML that looks like:

<Order>
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T05:10:49.6031106-05:00</Date>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC><![CDATA[<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>]]><![CDATA[<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>]]></Order>

XmlSerialize Class to CDATA

One of the ways to get the output you are expecting is to separate the creation of the module data and generating the CDATA part. For example:

To create the module data -

  1. Create a class to hold module details as below -

    public class Module
    {
    public string title { get; set; }
    public string code { get; set; }
    public string level { get; set; }
    public string summary { get; set; }
    }
  2. Create a method to fetch these datails -

     public static string CreateXMLString()
    {
    List<Module> modules = new List<Module>();
    modules = new List<Module>() { new Module() { code = "test",
    summary="Test3", title="Test", level = "tests1" },
    new Module() { code = "test3",
    summary="Test3", title="Test3", level = "tests3" } };

    // Create XML to return the string in the format of
    // <module code="test">
    // < level > tests1 </ level >
    // < summary > Test3 </ summary >
    // < title > Test </ title >
    //</ module >< module code = "test3" >
    // < level > tests3 </ level >
    // < summary > Test3 </ summary >
    // < title > Test3 </ title >
    // </ module >

    var modulesXml =
    from mod in modules
    select new XElement("module",
    new XAttribute("code", mod.code),
    new XElement("level", mod.level),
    new XElement("summary", mod.summary),
    new XElement("title", mod.title)
    );

    return String.Concat(modulesXml);
    }

To get the CDATA you can use the below steps -


  1. Create a class Modulesand refer the documentation for usages of CreateCDataSection and for similar threads here for the details

    [XmlType("")]
    public class Modules
    {
    public Modules() { }

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

To test the output assign the string generated in step 2 during serialization you can refer the sample code below

static void Main(string[] args)
{
Modules mc = new Modules();
mc.Message = CreateXMLString();//Assign your data created in step 2
XmlSerializer serializer = new XmlSerializer(typeof(Modules));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");

StringWriter writer = new StringWriter();

//Remove unnecessary namespaces
serializer.Serialize(writer, mc,ns);
var test = XDocument.Parse(writer.ToString());

var data = test.Root.Elements();

Console.WriteLine(data.FirstOrDefault().Value);

}

Output -

<modules>
<![CDATA[<module>
<code>test</code>
<level>tests1</level>
<summary>Test3</summary>
<title>Test</title>
</module><module>
<code>test3</code>
<level>tests3</level>
<summary>Test3</summary>
<title>Test3</title>
</module>]]>
</modules>

How do you serialize a string as CDATA using XmlSerializer?

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

Deserialize XML CData with attribute

You have a good start there.. this should help you get the rest of the way.

public class Level
{
[XmlAttribute]
public string ID {get; set;}
public Selection Selection {get; set;}
}

public class Selection {
[XmlAttribute]
public string Name {get;set;}
public Content Content {get;set;}
}

public class Content {
[XmlText]
public string Data {get;set;}
}

So to access that CDATA text via your object model you would access Level.Selection.Content.Data.

Deserialize XML with XmlSerializer where XmlElement names differ but have same content

You can adopt the approach from this answer and add a surrogate XmlElement [] property, marked with [XmlAnyElement], that performs a nested (de)serialization on the key/value pairs of the Dictionary<string, GroupName> property, binding the dictionary keys to the element names.

Note that, while the documentation for XmlAnyElementAttribute states

Specifies that the member (a field that returns an array of XmlElement or XmlNode objects) contains objects that represent any XML element that has no corresponding member in the object being serialized or deserialized.

In fact the attribute can be applied to a property as well. Thus a (de)serialization callback is not required since the nested serialization can be performed inside the getter and setter for the surrogate property itself. It can also be applied to members returning an array of XElement objects instead of XmlElement if you prefer the new LINQ-to-XML API.

In this approach, your DeactivationsGroup would look like:

[Serializable()]
public class DeactivationsGroup
{
public DeactivationsGroup() { this.GroupNames = new Dictionary<string, GroupName>(); }

[XmlIgnore]
public Dictionary<string, GroupName> GroupNames { get; set; }

public int Level { get; set; }

[XmlAttribute]
public byte index { get; set; }

[XmlAnyElement]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XElement[] XmlGroupNames
{
get
{
return GroupNames.SerializeToXElements(null);
}
set
{
if (value == null || value.Length < 1)
return;
foreach (var pair in value.DeserializeFromXElements<GroupName>())
{
GroupNames.Add(pair.Key, pair.Value);
}
}
}
}

Making use of the following extension methods and classes:

public static class XmlKeyValueListHelper
{
const string RootLocalName = "Root";

public static XElement [] SerializeToXElements<T>(this IEnumerable<KeyValuePair<string, T>> dictionary, XNamespace ns)
{
if (dictionary == null)
return null;
ns = ns ?? "";
var serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
var array = dictionary
.Select(p => new { p.Key, Value = p.Value.SerializeToXElement(serializer, true) })
// Fix name and remove redundant xmlns= attributes. XmlWriter will add them back if needed.
.Select(p => new XElement(ns + p.Key, p.Value.Attributes().Where(a => !a.IsNamespaceDeclaration), p.Value.Elements()))
.ToArray();
return array;
}

public static IEnumerable<KeyValuePair<string, T>> DeserializeFromXElements<T>(this IEnumerable<XElement> elements)
{
if (elements == null)
yield break;
XmlSerializer serializer = null;
XNamespace ns = null;
foreach (var element in elements)
{
if (serializer == null || element.Name.Namespace != ns)
{
ns = element.Name.Namespace;
serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName);
}
var elementToDeserialize = new XElement(ns + RootLocalName, element.Attributes(), element.Elements());
yield return new KeyValuePair<string, T>(element.Name.LocalName, elementToDeserialize.Deserialize<T>(serializer));
}
}

public static XmlSerializerNamespaces NoStandardXmlNamespaces()
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
return ns;
}

public static XElement SerializeToXElement<T>(this T obj)
{
return obj.SerializeToXElement(null, NoStandardXmlNamespaces());
}

public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns)
{
return obj.SerializeToXElement(null, ns);
}

public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
return obj.SerializeToXElement(serializer, (omitStandardNamespaces ? NoStandardXmlNamespaces() : null));
}

public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}

public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
return (T)result;
}
}
}

public static class XmlSerializerFactory
{
// To avoid a memory leak the serializer must be cached.
// https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
// This factory taken from
// https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648

readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;

static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}

public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}

Sample fiddle. And another demonstrating a case with XML namespaces and attributes.

How to serialize an array to XML with dynamic tag names

Using XDocument :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication45
{
class Program
{

static void Main(string[] args)
{
string xmlIdent = "<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
"<GeneralInformation xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"</GeneralInformation>";

XDocument doc = XDocument.Parse(xmlIdent);

XElement generalInfo = doc.Root;
XElement infoList = new XElement("InfoList");
generalInfo.Add(infoList);

for (int i = 0; i < 10; i++)
{
infoList.Add(new XElement("Infor" + i.ToString("0##"), new XElement("InfoName", "Test" + i.ToString("0##"))));
}

}

}
}

//<?xml version="1.0" encoding="utf-16"?>
//<GeneralInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
// <InfoList>
// <Info001>
// <InfoName>Test1</InfoName>
// </Info001>
// <Info002>
// <InfoName>Test2</InfoName>
// </Info002>
// <Info003>
// <InfoName>Test3</InfoName>
// </Info003>
// </InfoList>
//</GeneralInformation>


Related Topics



Leave a reply



Submit