I have the following XML I am trying to produce:
<Tags>
<Tag name="Motor" path="_types_" type="UDT_DEF">
<Property name="Value"/>
<Property name="DataType">2</Property>
<Parameters>
</Parameters>
<Tag name="Alm" path="" type="Folder"/>
<Tag name="Sts" path="" type="Folder"/>
<Tag name="Comm" path="Alm" type="OPC">
<Property name="Value"/>
<Property name="DataType">6</Property>
<Property name="OPCServer">Tag Server</Property>
<Property name="OPCItemPath">ns=1;s=Alm.Comm</Property>
<Alarms>
<Alarm name="Alarm">
<Property name="setpointA">1.0</Property>
</Alarm>
</Alarms>
</Tag>
<Tag name="Current" path="Sts" type="OPC">
<Property name="Value"/>
<Property name="DataType">6</Property>
<Property name="OPCServer">Tag Server</Property>
<Property name="OPCItemPath">ns=1;s=Sts.Current</Property>
</Tag>
</Tag>
</Tags>
I setup classes so I have a class for Tags which contain a Tag and I also have class for Alarms, etc. It serialized fine until I got to the tag element "Comm", then it throws an error:
An unhandled exception of type 'System.InvalidOperationException'
occurred in System.Xml.dll
Additional information: There was an error
reflecting type 'TagData.Tags'
How do I serialize my classes into XML when I have multiple element with the same name but different child elements? My thought was to handle the serialization myself but I figured there was a better, easier solution.
Here is my C# classes:
/// <summary>
/// Contains all the tags.
/// </summary>
public class Tags
{
[XmlElement("Tags")]
public List<Tag> ignTagList { get; set; }
}
/// <summary>
/// The tag itself.
/// </summary>
[Serializable()]
public class Tag
{
[XmlAttribute("type")]
public string Type;
[XmlAttribute("path")]
public string Path;
[XmlAttribute("name")]
public string Name;
[XmlElement("Property")]
public List<Property> props { get; set; }
[XmlElement("Tag")]
public List<Folder> folders { get; set; }
[XmlElement("Tag")] // #### ERROR OCCURS HERE. IF I RENAME THIS IT WILL SERIALIZE!! ####
public List<OPCTag> opcTags { get; set; }
public Tag()
{
this.props = new List<Property>();
this.folders = new List<Folder>();
this.opcTags = new List<OPCTag>();
}
public Tag(string Type, string Path, string Name)
{
this.props = new List<Property>();
this.folders = new List<Folder>();
this.opcTags = new List<OPCTag>();
this.Type = Type;
this.Path = Path;
this.Name = Name;
}
}
/// <summary>
/// Property data structure.
/// </summary>
[Serializable()]
public class Property
{
[XmlAttribute("name")]
public string Name;
[XmlText]
public string DataTypeValue;
public Property()
{
}
public Property(string Name, string DataTypeValue)
{
this.Name = Name;
this.DataTypeValue = DataTypeValue;
}
}
/// <summary>
/// Folder data structure.
/// </summary>
[Serializable()]
public class Folder
{
[XmlAttribute("type")]
public string Type;
[XmlAttribute("path")]
public string Path;
[XmlAttribute("name")]
public string Name;
public Folder()
{
}
public Folder(string Type, string Path, string Name)
{
this.Type = Type;
this.Path = Path;
this.Name = Name;
}
public Folder(string Path, string Name)
{
this.Type = "Folder";
this.Path = Path;
this.Name = Name;
}
}
/// <summary>
/// OPC tag data structure.
/// </summary>
[Serializable()]
public class OPCTag
{
[XmlAttribute("type")]
public string Type;
[XmlAttribute("path")]
public string Path;
[XmlAttribute("name")]
public string Name;
[XmlIgnore]
public IgnDataType DataType;
[XmlElement("Property")]
public List<Property> props { get; set; }
public OPCTag()
{
this.props = new List<Property>();
}
public OPCTag(string Type, string Path, string Name)
{
this.Type = Type;
this.Path = Path;
this.Name = Name;
this.props = new List<Property>();
}
public OPCTag(string Path, string Name, UDTDataType DataType)
{
this.Type = "OPC";
this.Path = Path;
this.Name = Name;
this.props = new List<Property>();
this.props.Add(new Property("Value", ""));
this.props.Add(new Property("DataType", ((int)DataType).ToString()));
}
}
Related
Is it possible via an attribute of some sort to serialize a string as CDATA using the .Net XmlSerializer?
[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 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;
}
}
[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 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);
}
}
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>
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
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;
}
}
}
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;
}
I have the following XML document
<?xml version = "1.0" encoding = "utf-8"?>
<flights_for_sale>
<ad id="0001" createdon ="11/02/20" expireson="12/02/20">
<aircraft id="A10">
<year> 1977 </year>
<make> <![CDATA[&c;]]> </make>
<model> Skyhawk </model>
<color> Light blue and white </color>
<description>
New paint, nearly new interior,
685 hours SMOH, full IFR King avionics
</description>
<price> 23,495 </price>
</aircraft>
<seller id = "s001" phone="123-123-123"> Skyway Aircraft </seller>
<seller id = "s002" phone="123-123-222"> Boeing </seller>
<seller id = "s003" phone="123-123-233"> McDouglas </seller>
<membership id="1000" from="12/03/16" to="12/03/18" no="M0001">Silver</membership>
<membership id="1000" from="12/03/16" to="12/03/18" no="M0002">Gold</membership>
<membership id="1000" from="12/03/16" to="12/03/18" no="M0003">Platinum</membership>
<location>
<city> Rapid City, </city>
<state> South Dakota </state>
</location>
</ad>
<ad id="002" createdon ="11/05/20" expireson="12/05/20">
<aircraft>
<year> 1965 </year>
<make> &p; </make>
<model> Cherokee </model>
<color> Gold </color>
<description>
240 hours SMOH, dual NAVCOMs, DME,
new Cleveland brakes, great shape
</description>
</aircraft>
<seller phone="555-333-2222" email="jseller#www.axl.com" id="s004">John Seller</seller>
<membership id="1000" from="12/03/16" to="12/03/18" no="M0020">State Membership</membership>
<membership id="1000" from="12/03/16" to="12/03/18" no="M0002">Gold</membership>
<location>
<city> St. Joseph, </city>
<state> Missouri </state>
</location>
</ad>
</flights_for_sale>
I have the following C# classes
class Advert2
{
public int? Id { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? ExpiresOn { get; set; }
public Aircraft MyAircraft { get; set; }
public List<Seller2> MySellers { get; set; }
public List<Membership> MyMemberships { get; set; }
public Advert2()
{
MySellers = new List<Seller2>();
MyMemberships = new List<Membership>();
MyAircraft = new Aircraft();
}
}
class Seller2
{
public int? Id { get; set; }
public string SellerName { get; set; }
public string Phone { get; set; }
}
class Membership
{
public int? Id { get; set; }
public string MembershipNumber { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public String MemberType { get; set; }
}
class Aircraft {
public string Make { get; set; }
public string Model { get; set; }
public decimal? Price { get; set; }
public string Description { get; set; }
}
Then i have used the following two methods to parse XML elements and attributes ( check for NULLs )
public static class XMLCommons
{
public static string TryGetElementValue(this XElement parentEl, string elementName, string defaultValue = null)
{
var foundEl = parentEl.Element(elementName);
if (foundEl != null)
{
return foundEl.Value;
}
return defaultValue;
}
public static string TryGetAttribtueValue(this XElement parentEl, string elementName, string attrName, string defaultValue = null)
{
var foundEl = parentEl.Element(elementName);
if (foundEl != null) {
//check attribute exists
var foundAttr = foundEl.Attribute(attrName);
if (foundAttr != null)
{
return foundAttr.Value;
}
}
return defaultValue;
}
}
Then i have written the following code to read element/attributes on the XML, and populate data to the Advert2 object structure
var xmlPath2 = System.IO.Path.Combine("../../../data/" + "XMLFile2.xml");
var xml2 = XDocument.Load(xmlPath2);
var query2 = xml2.Root.Descendants("ad").Select(n => new Advert2 {
Id = Convert.ToInt32(n.Parent.TryGetAttribtueValue("ad", "id")),
CreatedOn = Convert.ToDateTime( n.Parent.TryGetAttribtueValue("ad", "createdon") ),
ExpiresOn = Convert.ToDateTime(n.Parent.TryGetAttribtueValue("ad", "expireson")),
MyAircraft = new Aircraft {
Make = n.TryGetElementValue("make"),
Model = n.TryGetElementValue("model"),
Description = n.TryGetElementValue("description"),
Price = Convert.ToDecimal( n.TryGetElementValue("price") ) },
MySellers = new List<Seller2>().Add( new Seller2 {
Id = Convert.ToInt32( n.TryGetAttribtueValue("seller","id") ),
SellerName = n.TryGetElementValue("seller"),
Phone = n.TryGetAttribtueValue("seller","phone")
} )
}).ToList();
but the issues is i get syntax errors when i tried to create objects to populate MySellers List.
Error:
Error CS0029
Cannot implicitly convert type 'void' to System.Collections.Generic.List<XMLParsing.Seller2>'
So it seems like i don't know how to populate those two collections MySellers and MyMemberships.
is there away to populate those two collections so i can create the Averts2 Collection?
I know this is not quite in the spirit of the question. However, it should be as simple as
var xmlStream = new StreamReader(#"D:\something.xml");
var serializer = new XmlSerializer(typeof(flights_for_sale));
var result = (flights_for_sale)serializer.Deserialize(xmlStream);
The below was obtained by pasting the xml into visual studio
Edit 🡆 Paste Special 🡆 Paste XML as Classes
// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0.
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class flights_for_sale
{
private flights_for_saleAD[] adField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("ad")]
public flights_for_saleAD[] ad
{
get
{
return this.adField;
}
set
{
this.adField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class flights_for_saleAD
{
private flights_for_saleADAircraft aircraftField;
private flights_for_saleADSeller[] sellerField;
private flights_for_saleADMembership[] membershipField;
private flights_for_saleADLocation locationField;
private byte idField;
private string createdonField;
private string expiresonField;
/// <remarks/>
public flights_for_saleADAircraft aircraft
{
get
{
return this.aircraftField;
}
set
{
this.aircraftField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("seller")]
public flights_for_saleADSeller[] seller
{
get
{
return this.sellerField;
}
set
{
this.sellerField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("membership")]
public flights_for_saleADMembership[] membership
{
get
{
return this.membershipField;
}
set
{
this.membershipField = value;
}
}
/// <remarks/>
public flights_for_saleADLocation location
{
get
{
return this.locationField;
}
set
{
this.locationField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string createdon
{
get
{
return this.createdonField;
}
set
{
this.createdonField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string expireson
{
get
{
return this.expiresonField;
}
set
{
this.expiresonField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class flights_for_saleADAircraft
{
private ushort yearField;
private string makeField;
private string modelField;
private string colorField;
private string descriptionField;
private string priceField;
private string idField;
/// <remarks/>
public ushort year
{
get
{
return this.yearField;
}
set
{
this.yearField = value;
}
}
/// <remarks/>
public string make
{
get
{
return this.makeField;
}
set
{
this.makeField = value;
}
}
/// <remarks/>
public string model
{
get
{
return this.modelField;
}
set
{
this.modelField = value;
}
}
/// <remarks/>
public string color
{
get
{
return this.colorField;
}
set
{
this.colorField = value;
}
}
/// <remarks/>
public string description
{
get
{
return this.descriptionField;
}
set
{
this.descriptionField = value;
}
}
/// <remarks/>
public string price
{
get
{
return this.priceField;
}
set
{
this.priceField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class flights_for_saleADSeller
{
private string idField;
private string phoneField;
private string emailField;
private string valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string phone
{
get
{
return this.phoneField;
}
set
{
this.phoneField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string email
{
get
{
return this.emailField;
}
set
{
this.emailField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public string Value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class flights_for_saleADMembership
{
private ushort idField;
private string fromField;
private string toField;
private string noField;
private string valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public ushort id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string from
{
get
{
return this.fromField;
}
set
{
this.fromField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string to
{
get
{
return this.toField;
}
set
{
this.toField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string no
{
get
{
return this.noField;
}
set
{
this.noField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public string Value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class flights_for_saleADLocation
{
private string cityField;
private string stateField;
/// <remarks/>
public string city
{
get
{
return this.cityField;
}
set
{
this.cityField = value;
}
}
/// <remarks/>
public string state
{
get
{
return this.stateField;
}
set
{
this.stateField = value;
}
}
}
Is it possible via an attribute of some sort to serialize a string as CDATA using the .Net XmlSerializer?
[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 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;
}
}
[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 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);
}
}
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>
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
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;
}
}
}
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;
}
The XML is generated and loaded by the same .NET with c# desktop application using XMLSerialize serialization / deserialization.
The serializable class structur is quiet complex, so I just made a selection of the two relevant classes.
Now, when I deserialize, everything is loaded except the Mapping Messages (or Messages as how the object list is called in the Organization.
Does anyone have an explanation for this behaviour?
Any tips or hints for improving what has already been made are also always appreciated.
Thank you.
I have the following XML:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xsd="Company.Test3.Crm.Crm2Queue.Config.xsd">
<organizations>
<organization name="Vanilla">
<settings>
<ignoreemptyfields>true</ignoreemptyfields>
<throwerroronmissingconfiguration>true</throwerroronmissingconfiguration>
</settings>
<endpoints>
<endpoint>
<serviceUri>http://www.webservicex.net/usaddressverification.asmx</serviceUri>
</endpoint>
</endpoints>
<messages>
<message name="account">
<field name="accountnumber" mappedname="State" />
<field name="address1_county" mappedname="Zip" />
<field name="address1_latitude" mappedname="City" />
</message>
</messages>
<entities>
<entity name="account" messageschema="/XSD/.xsd" identifier="accountid">
<events>
<event name="create" message="" />
<event name="update" message="" />
<event name="delete" message="" />
</events>
</entity>
</entities>
</organization>
</organizations>
</configuration>
Now the serializable class looks as following:
[Serializable()]
public class Organization
{
#region XmlIgnore
[XmlIgnore()]
public string Id { get; set; }
[XmlIgnore()]
public bool Checked { get; set; }
[XmlIgnore()]
public List<MappingMessage> mappingMessages { get; set; }
#endregion
#region Attributes
[XmlAttribute("name")]
public string Name { get; set; }
#endregion
#region Properties
[XmlElement("settings")]
public Settings Settings { get; set; }
public bool ShouldSerializeSettings() { return (Settings != null && (Settings.IgnoreEmptyFields.HasValue || Settings.ThrowErrorOnMissingConfiguration.HasValue)); }
[XmlArray("endpoints")]
[XmlArrayItem("endpoint")]
public List<Endpoint> Endpoints { get; set; }
public bool ShouldSerializeignoreEndpoints() { return (Endpoints.Count > 0); }
[XmlArray("messages")]
[XmlArrayItem("message")]
public List<MappingMessage> Messages
{
get { return mappingMessages.Where(mm => (mm.Fields.Where(fi => !string.IsNullOrEmpty(fi.MappedName)).ToList().Count > 0)).ToList(); }
set { mappingMessages = value; }
}
public bool ShouldSerializeFilledMappingMessages() { return (mappingMessages.Where(mm => (mm.Fields.Where(fi => !string.IsNullOrEmpty(fi.MappedName)).ToList().Count > 0)).ToList().Count > 0); }
//public bool ShouldSerializeMappingMessages() { return (MappingMessages.Where(mm=> (mm.Fields.Where(fi=> !string.IsNullOrEmpty(fi.MappedName)).ToList().Count > 0)).ToList().Count > 0); }
[XmlArray("entities")]
[XmlArrayItem("entity")]
public List<Entity> Entities { get; set; }
public bool ShouldSerializeEntities() { return (Entities.Count > 0); }
#endregion
#region Constructors
public Organization()
{
Settings = new Settings();
Endpoints = new List<Endpoint>();
mappingMessages = new List<MappingMessage>();
Entities = new List<Entity>();
Checked = false;
}
public Organization(string name)
: this()
{
Name = name;
}
public Organization(string id, string name)
: this(name)
{
Id = id;
}
#endregion
}
[Serializable()]
public class MappingMessage
{
#region XmlIgnore
[XmlIgnore()]
public string EntityId { get; set; }
[XmlIgnore()]
public bool Checked { get; set; }
[XmlIgnore()]
public List<Field> Fields { get; set; }
#endregion
#region Attributes
[XmlAttribute("id")]
public string Id { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
#endregion
#region Properties
[XmlElement("field")]
public List<Field> SelectedFields
{
get
{
return Fields.Where(fi=> !string.IsNullOrEmpty(fi.MappedName)).ToList();
}
set
{
Fields = value;
}
}
public bool ShouldSerializeSelectedFields() { return (SelectedFields.Count > 0); }
[XmlElement("subentity")]
public List<SubEntity> SubEntities { get; set; }
public bool ShouldSerializeSubEntities() { return (SubEntities.Count > 0); }
[XmlElement("parententity")]
public List<ParentEntity> ParentEntities { get; set; }
public bool ShouldSerializeParentEntities() { return (ParentEntities.Count > 0); }
#endregion
#region Constructors
public MappingMessage()
{
Checked = false;
Fields = new List<Field>();
SubEntities = new List<SubEntity>();
ParentEntities = new List<ParentEntity>();
}
public MappingMessage(string entityId)
: this()
{
EntityId = entityId;
}
public MappingMessage(string entityId, string name)
: this(entityId)
{
Name = name;
}
#endregion
}
And I use deserialization as shown below:
foreach (ZipEntry zipEntry in zipFile)
{
using (MemoryStream memoryStream = new MemoryStream())
{
if (zipEntry.FileName.ToLower().EndsWith(".crm.crm2queue.config.xml"))
{
using (StreamReader streamReader = new StreamReader(memoryStream, Encoding.UTF8))
{
zipEntry.Extract(memoryStream);
memoryStream.Position = 0;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Ciber.Crm.MappingCRMTo.Data.Configuration));
configuration = (Configuration)xmlSerializer.Deserialize(streamReader);
}
}
}
}
The deserializer tries to fill the list returnepublic List<MappingMessage> Messages. In order to the serializer invoke the setter, you must change the property type to an immutable collection type, say MappingMessage[].
Edit : to see that, you can replace the Entities auto-property by a property with backing field, and set a breakpoint in both getter and setter. You should not break in the setter, only in the getter.
Is it possible via an attribute of some sort to serialize a string as CDATA using the .Net XmlSerializer?
[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 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;
}
}
[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 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);
}
}
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>
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
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;
}
}
}
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;
}