Creating and using custom XML special characters with XmlSerializer - c#

I have a ColorFormat class that stores basic information about a color format. The aim is to be able to serialize and deserialize to and from XML. To represent Red, Green and Blue I use special color string identifiers:
public const string RedColorIdentifier = "&red;";
public const string GreenColorIdentifier = "&green;";
public const string BlueColorIdentifier = "&blue;";
For a format like "#RGB", the class format string is as such:
colorFormat.Format = "#" + ColorFormat.RedColorIdentifier +
ColorFormat.GreenColorIdentifier +
ColorFormat.BlueColorIdentifier;
Ideally, the serialized XML should be:
<ColorFormat Name="HexFmt" ColorBase="Hex">#&red;&green;&blue;</ColorFormat>
The actual serialization is:
<ColorFormat Name="HexFmt" ColorBase="Hex">#&red;&green;&blue;</ColorFormat>
I was wondering if there is a way of "serializing and deserializing" your own custom special XML character

You can use CData to wrap special characters.
From MSDN CDATA Section
For example class below will be serialized witt color values wrapped with CData
[XmlType("ColorFormat")]
public class ColorFormat
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string ColorBase { get; set; }
[XmlIgnore]
public string Format { get; set; }
[XmlText]
public XmlNode[] SerializableFormat
{
get
{
var doc = new XmlDocument();
return new XmlNode[] { doc.CreateCDataSection(this.Format) };
}
set
{
this.Format = value[0].Value;
}
}
}
Using of ColorFormat class
const string FORMAT = "&red;&green;&blue;";
var format = new ColorFormat
{
Name = "HexFormat",
ColorBase = "Hex",
Format = FORMAT
};
var serializer = new XmlSerializer(typeof(ColorFormat));
using (var writer = new StringWriter())
{
serializer.Serialize(writer, format);
Console.WriteLine(writer.ToString());
}

Finally found it, gotta implement IXmlSerializable as such:
public class ColorFormat : IXmlSerializable
{
...
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Name = reader.GetAttribute(nameof(Name));
ColorBase = CommonUtil.ParseStringToEnum<NumberBase>(reader.GetAttribute(nameof(ColorBase)));
Format = reader.ReadInnerXml();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString(nameof(Name), Name);
writer.WriteAttributeString(nameof(ColorBase), ColorBase.ToString());
writer.WriteRaw(Format);
}
}

Related

XML Attribute on Plain String

I'd like to generate some XML like the following with C# code.
<card>
<name>Cool Card</name>
<set rarity="common">S1</set>
</card>
I have something like this.
public class card
{
public string name = "Cool Card";
[XmlAttribute]
public string rarity = "common";
public string set = "S1";
}
public static void WriteXml()
{
var serializer = new XmlSerializer(typeof(card));
var myCard = new();
using var sw = new StringWriter();
serializer.Serialize(sw, myCard);
XDocument doc = XDocument.Parse(sw.ToString());
XmlWriterSettings xws = new();
xws.OmitXmlDeclaration = true;
xws.Indent = true;
using var xw = XmlWriter.Create(path, xws);
doc.Save(xw);
}
The problem is that this doesn't add the "rarity" attribute to the "set" value. Trying to add [XmlAttribute] adds it to the parent element rather than the next sibling element and I can't figure out how to get it on a plain string element, so at present my output looks like.
<card rarity="common">
<name>Cool Card</name>
<set>S1</set>
</card>
The XML example doc shows an example of how to set the attribute on an element, but only one with nested fields and not one that's a plain string. Is it possible to add an attribute to a plain old string element in XML like my first posted example demonstrates?
Try this:
public class card
{
public string name = "Cool Card";
public Set set = new();
}
public class Set
{
[XmlText]
public string value = "S1";
[XmlAttribute]
public string rarity = "common";
}
If you think about it there is no other way the xml attribute can be applied only to the element it is declared in. So you need to move it into another class. When you do this new property for value is required and that one needs to be flattened as value node is not required for that you need XmlText attribute.
The cleanest option in this case is implement IXmlSerializable:
public class card : IXmlSerializable
{
public string name = "Cool Card";
public string rarity = "common";
public string set = "S1";
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement(nameof(card));
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
if (reader.Name == nameof(name))
{
this.name = reader.ReadElementContentAsString();
}
else if (reader.Name == nameof(set))
{
this.rarity = reader.GetAttribute(nameof(rarity));
this.set = reader.ReadElementContentAsString();
}
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString(nameof(name), this.name);
writer.WriteStartElement(nameof(set));
writer.WriteAttributeString(nameof(rarity), this.rarity);
writer.WriteString(this.set);
writer.WriteEndElement();
}
}
If your class is big and you only need do a bit change in the XML, sometimes implement IXmlSerializable is a mess (you must save all the properties and only in one or two, do a bit change). In these cases, you can use attributes and some small helpers classes to get same results. The idea is tell to XML serializer that don't serialize some property and use another fake property to do the serialization.
For example, create a Set class with your desired XML structure:
public class XmlSet
{
private readonly card _card;
public XmlSet()
{
this._card = new card();
}
public XmlSet(card card)
{
this._card = card;
}
[XmlText]
public string set
{
get { return this._card.set; }
set { this._card.set = value; }
}
[XmlAttribute]
public string rarity
{
get { return this._card.rarity; }
set { this._card.rarity = value; }
}
}
It's only a wrapper, with the XML sttributes that you want. You get/set the values from/to your card object.
Then, in your card class, Ignore the set property and serialize the fake property:
public class card
{
public string name = "Cool Card";
[XmlIgnore]
public string rarity = "common";
[XmlIgnore]
public string set = "S1";
// Added to serialization
private XmlSet _xmlSetNode;
[XmlElement("set")]
public XmlSet XmlSet
{
get
{
this._xmlSetNode = this._xmlSetNode ?? new XmlSet(this);
return this._xmlSetNode;
}
set { this._xmlSetNode = value; }
}
}

My nested Class collection XMLRoot name is not being used when serializing to xml

I have a Model populated and I wish to serlise to an xml document.
Due to naming conventions I have to over ride the class names for my XML document,
This is my Model(s):
[Serializable]
[XmlRoot("preferences")]
public class PreferencesModel
{
[XmlIgnore]
public string MessageToUser { get; set; }
[XmlElement(ElementName = "sectiondivider")]
public List<SectionDivider> SectionDivider { get; set; }
}
[Serializable]
[XmlRoot(ElementName = "sectiondivider")]
public class SectionDivider
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("preference")]
public List<PreferenceModel> PreferenceModel { get; set; }
}
[Serializable]
[XmlRoot("preference")]
public class PreferenceModel
{
[XmlAttribute("type")]
public string Type { get; set; }
public string Name { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
[XmlElement("options")]
public List<Option> Options { get; set; }
}
this is how I serialize:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(ObjectToXmlString(obj, includeNameSpace, includeStartDocument, rootAttribute));
return xDoc;
public static string ObjectToXmlString(Object obj, bool includeNameSpace, bool includeStartDocument, XmlRootAttribute rootAttribute)
{
SpecialXmlWriter stWriter = null;
XmlSerializer xmlSerializer = default(XmlSerializer);
string buffer = null;
try
{
if (rootAttribute == null)
{
xmlSerializer = new XmlSerializer(obj.GetType());
}
else
{
xmlSerializer = new XmlSerializer(obj.GetType(), rootAttribute);
}
MemoryStream memStream = new MemoryStream();
StringWriter writer = new StringWriter();
stWriter = new SpecialXmlWriter(memStream, new UTF8Encoding(false), includeStartDocument);
if (!includeNameSpace)
{
System.Xml.Serialization.XmlSerializerNamespaces xs = new XmlSerializerNamespaces();
//To remove namespace and any other inline
//information tag
xs.Add("", "");
xmlSerializer.Serialize(stWriter, obj, xs);
}
else
{
xmlSerializer.Serialize(stWriter, obj);
}
buffer = Encoding.UTF8.GetString(memStream.ToArray());
}
catch (Exception e)
{
string msg = e.Message;
throw;
}
finally
{
if (stWriter != null)
stWriter.Close();
}
return buffer;
}
I call it like this:
XmlDocument preferencesxml = Codec.ObjectToXml(m.SectionDivider,false,
false, new XmlRootAttribute("preferences"));
My m value is:
and my resulting XML is this:
XmlRootAttribute, as the name suggests, only applies to the root element of the XML being serialised.
You need to use XmlTypeAttribute in this context:
[XmlType("sectiondivider")]`
public class SectionDivider
{
//...
}
As an aside, the [Serializable] attribute is not relevant to XmlSerializer - it can be removed unless you need it for some other purpose.

How to serialize List<IFilter> (Nokia Imaging SDK)?

I'm trying to save a List of IFilter(of type Interface) which are applied to an image using XML serialization, so that user can edit the same image from where he left off.
[XmlRoot]
public class ImageProperties
{
public string ImageName { get; set; }
public string ImageFilePath { get; set; }
public List<IFilter> Filters { get; set; }
}
Is this possible? Is there another alternative to do the same?
You could use IXmlSerializable to achieve this.. assuming you can change the ImageProperties class.
Upon serialization, you can get the type by looking at each filter instance and querying for it. You can store this type information in the XML so when you come to read it, you know which type it is, and you can then just invoke the default XML serializer for each filter.
Here is a possible implementation.
public class ImageProperties : IXmlSerializable
{
public string ImageName { get; set; }
public string ImageFilePath { get; set; }
public List<IFilter> Filters { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
string startEle = reader.Name;
reader.ReadStartElement();
Filters = new List<IFilter>();
do
{
switch (reader.Name)
{
case "imgName":
ImageName = reader.ReadElementContentAsString();
break;
case "imgFilePath":
ImageFilePath = reader.ReadElementContentAsString();
break;
case "filters":
reader.ReadStartElement("filters");
while (reader.Name.Equals("iFilter"))
{
XmlSerializer filterSerializer = new XmlSerializer(Type.GetType(reader.GetAttribute("type")));
reader.ReadStartElement("iFilter");
Filters.Add((IFilter)filterSerializer.Deserialize(reader));
reader.ReadEndElement();
}
reader.ReadEndElement();
break;
default:
reader.ReadOuterXml();
break;
}
} while (reader.Name != startEle);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("imgName", ImageName);
writer.WriteElementString("imgFilePath", ImageFilePath);
writer.WriteStartElement("filters");
foreach (IFilter filter in Filters)
{
writer.WriteStartElement("iFilter");
writer.WriteAttributeString("type", filter.GetType().FullName);
XmlSerializer filterSerializer = new XmlSerializer(filter.GetType());
filterSerializer.Serialize(writer, filter);
writer.WriteEndElement();
}
writer.WriteEndElement();
}
}
If you have different types of filter, the default serializer of the real type is invoked, so their unique properties will get recorded.
For example, with these filter classes available:
public interface IFilter
{
string SomeCommonProp { get; set;}
}
[XmlRoot("myFilter")]
public class MyFilter : IFilter
{
[XmlElement("somemyFilterProp")]
public string SomeMyFilterProp { get; set; }
[XmlElement("someCommonProp")]
public string SomeCommonProp { get; set;}
}
[XmlRoot("myOtherFilter")]
public class MyOtherFilter : IFilter
{
[XmlElement("someOtherFilterProp")]
public string SomeOtherFilterProp { get; set; }
[XmlElement("someCommonProp")]
public string SomeCommonProp { get; set;}
}
You can use as follows to serialise and deserialise two different types of filter in IFilters to xml.
static void Main(string[] args)
{
ImageProperties props = new ImageProperties();
props.ImageName = "img.png";
props.ImageFilePath = "c:\\temp\\img.png";
props.Filters = new List<IFilter>();
props.Filters.Add(new MyFilter() { SomeMyFilterProp = "x", SomeCommonProp ="p" });
props.Filters.Add(new MyOtherFilter() { SomeOtherFilterProp = "y", SomeCommonProp ="p" });
XmlSerializer s = new XmlSerializer(typeof(ImageProperties));
using (StreamWriter writer = new StreamWriter(#"c:\temp\imgprops.xml"))
s.Serialize(writer, props);
using (StreamReader reader = new StreamReader(#"c:\temp\imgprops.xml"))
{
object andBack = s.Deserialize(reader);
}
Console.ReadKey();
}
This produces an XML that looks like this.
<?xml version="1.0" encoding="utf-8"?>
<ImageProperties>
<imgName>img.png</imgName>
<imgFilePath>c:\temp\img.png</imgFilePath>
<filters>
<iFilter type="SomeNameSpace.Whatever.MyFilter">
<myFilter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<somemyFilterProp>x</somemyFilterProp>
<someCommonProp>p</someCommonProp>
</myFilter>
</iFilter>
<iFilter type="SomeNameSpace.Whatever.MyOtherFilter">
<myOtherFilter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<someOtherFilterProp>y</someOtherFilterProp>
<someCommonProp>p</someCommonProp>
</myOtherFilter>
</iFilter>
</filters>
</ImageProperties>
No. Interface instances cannot be serialized. It doesn't know the implementation to "deserialize" to. It will require a concrete class or custom serialization in this case.

How to serialize Name/Value pairs as Attributes

public class MyStuff {
public string Name { get; set; }
public List<Annotation> Annotations { get; set; }
}
public class Annotation {
public string Name { get; set; }
public string Value { get; set; }
}
How do I get the List of Annotations to serialize as a bunch of XML attributes?
var x = new MyStuff {
Name = "Stuff",
Annotations = new [] {
new Annotation { Name = "Note1", Value = "blah" },
new Annotation { Name = "Note2", Value = "blahblah" }
}.ToList()
};
// turns into something like:
<MyStuff Name="Stuff" ann:Note1="blah" ann:Note2="blahblah" />
ann:Note1 is only valid if ann is an xml namespace,
XNamespace ns = "Annotation";
XElement xElem = new XElement("MyStuff", new XAttribute("Name",x.Name));
xElem.Add(x.Annotations
.Select(a => new XAttribute(ns + a.Name, a.Value)));
var xml = xElem.ToString();
OUTPUT:
<MyStuff Name="Stuff" p1:Note1="blah" p1:Note2="blahblah" xmlns:p1="Annotation" />
XmlDocument doc = new XmlDocument(); // Creating an xml document
XmlElement root =doc.CreateElement("rootelement"); doc.AppendChild(root); // Creating and appending the root element
XmlElement annotation = doc.CreateElement("Name");
XmlElement value = doc.CreateElement("Value");
annotation.InnerText = "Annotation Name Here";
value.InnerText = "Value Here";
doc.AppendChild(annotation);
doc.AppendChild(value);
You can narrow all your list and do the same thing in a loop.
What you can do is add the attribute [XmlAttribute] on your properties:
public class Annotation
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
}
And the result will be like this:
<Annotations>
<Annotation Name="Note1" Value="blah" />
<Annotation Name="Note2" Value="blahblah" />
</Annotations>
The IXmlSerializable interface allows you to customize the serialization of any class.
public class MyStuff : IXmlSerializable {
public string Name { get; set; }
public List<Annotation> Annotations { get; set; }
public XmlSchema GetSchema() {
return null;
}
public void ReadXml(XmlReader reader) {
// customized deserialization
// reader.GetAttribute() or whatever
}
public void WriteXml(XmlWriter writer) {
// customized serialization
// writer.WriteAttributeString() or whatever
}
}

DataContractSerializer with Multiple Namespaces

I am using a DataContractSerializer to serialize an object to XML. The main object is SecurityHolding with the namespace "http://personaltrading.test.com/" and contains a property called Amount that's a class with the namespace "http://core.test.com". When I serialize this to XML I get the following:
<ArrayOfSecurityHolding xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<Amount xmlns:d3p1="http://core.test.com/">
<d3p1:Amount>1.05</d3p1:Amount>
<d3p1:CurrencyCode>USD</d3p1:CurrencyCode>
</Amount>
<BrokerageID>0</BrokerageID>
<BrokerageName i:nil="true" />
<RecordID>3681</RecordID>
</SecurityHolding></ArrayOfSecurityHolding>
Is there anyway I can control the d3p1 prefix? Am I doing something wrong or should I be doing something else?
Firstly, the choice of namespace alias should make no difference to a well-formed parser.
But; does it have to be DataContractSerializer? With XmlSerializer, you can use the overload of Serialize that accepts a XmlSerializerNamespaces. This allows you to pick and choose the namespaces and aliases that you use.
Ultimately; DataContractSerializer is not intended to give full xml control; that isn't its aim. If you want strict xml control, XmlSerializer is a better choice, even if it is older (and has some nuances/foibles of its own).
Full example:
using System;
using System.Xml.Serialization;
public class Amount
{
public const string CoreNamespace = "http://core.test.com/";
[XmlElement("Amount", Namespace=CoreNamespace)]
public decimal Value { get; set; }
[XmlElement("CurrencyCode", Namespace = CoreNamespace)]
public string Currency { get; set; }
}
[XmlType("SecurityHolding", Namespace = SecurityHolding.TradingNamespace)]
public class SecurityHolding
{
public const string TradingNamespace = "http://personaltrading.test.com/";
[XmlElement("Amount", Namespace = Amount.CoreNamespace)]
public Amount Amount { get; set; }
public int BrokerageId { get; set; }
public string BrokerageName { get; set; }
public int RecordId { get; set; }
}
static class Program
{
static void Main()
{
var data = new[] {
new SecurityHolding {
Amount = new Amount {
Value = 1.05M,
Currency = "USD"
},
BrokerageId = 0,
BrokerageName = null,
RecordId = 3681
}
};
var ser = new XmlSerializer(data.GetType(),
new XmlRootAttribute("ArrayOfSecurityHolding") { Namespace = SecurityHolding.TradingNamespace});
var ns = new XmlSerializerNamespaces();
ns.Add("foo", Amount.CoreNamespace);
ser.Serialize(Console.Out, data, ns);
}
}
Output:
<ArrayOfSecurityHolding xmlns:foo="http://core.test.com/" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<foo:Amount>
<foo:Amount>1.05</foo:Amount>
<foo:CurrencyCode>USD</foo:CurrencyCode>
</foo:Amount>
<BrokerageId>0</BrokerageId>
<RecordId>3681</RecordId>
</SecurityHolding>
</ArrayOfSecurityHolding>
I have solved this problem slightly differently to Marc that can be implemented in a base class.
Create a new attribute to define the additional XML namespaces that you will use in your data contract.
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public sealed class NamespaceAttribute : Attribute
{
public NamespaceAttribute()
{
}
public NamespaceAttribute(string prefix, string uri)
{
Prefix = prefix;
Uri = uri;
}
public string Prefix { get; set; }
public string Uri { get; set; }
}
Add the attribute to your data contracts.
[DataContract(Name = "SomeObject", Namespace = "http://schemas.domain.com/namespace/")]
[Namespace(Prefix = "a", Uri = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
[Namespace(Prefix = "wm", Uri = "http://schemas.datacontract.org/2004/07/System.Windows.Media")]
public class SomeObject : SerializableObject
{
private IList<Color> colors;
[DataMember]
[DisplayName("Colors")]
public IList<Colors> Colors
{
get { return colors; }
set { colours = value; }
}
}
Then in your Save method, use reflection to get the attributes and then write them to the file.
public static void Save(SerializableObject o, string filename)
{
using (Stream outputStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
if (outputStream == null)
throw new ArgumentNullException("Must have valid output stream");
if (outputStream.CanWrite == false)
throw new ArgumentException("Cannot write to output stream");
object[] attributes;
attributes = o.GetType().GetCustomAttributes(typeof(NamespaceAttribute), true);
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Indent = true;
writerSettings.NewLineOnAttributes = true;
using (XmlWriter w = XmlWriter.Create(outputStream, writerSettings))
{
DataContractSerializer s = new DataContractSerializer(o.GetType());
s.WriteStartObject(w, o);
foreach (NamespaceAttribute ns in attributes)
w.WriteAttributeString("xmlns", ns.Prefix, null, ns.Uri);
// content
s.WriteObjectContent(w, o);
s.WriteEndObject(w);
}
}
}
I have struggled with this problem also. The solution I present below is not optimal IMHO but it works. Like Marc Gravell above, I suggest using XmlSerializer.
The trick is to add a field to your class that returns a XmlSerializerNamespaces object. This field must be decorated with a XmlNamespaceDeclarations attribute. In the constructor of your class, add namespaces as shown in the example below. In the xml below note that the root element is prefixed correctly as well as the someString element.
More info on XmlSerializerNamespaces
Schemas reference
[XmlRoot(Namespace="http://STPMonitor.myDomain.com")]
public class CFMessage : IQueueMessage<CFQueueItem>
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns;
[XmlAttribute("schemaLocation", Namespace=System.Xml.Schema.XmlSchema.InstanceNamespace)]
public string schemaLocation = "http://STPMonitor.myDomain.com/schemas/CFMessage.xsd";
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("username")]
public string UserName { get; set; }
[XmlAttribute("somestring", Namespace = "http://someURI.com")]
public string SomeString = "Hello World";
public List<CFQueueItem> QueueItems { get; set; }
public CFMessage()
{
xmlns = new XmlSerializerNamespaces();
xmlns.Add("myDomain", "http://STPMonitor.myDomain.com");
xmlns.Add("xyz", "http://someURI.com");
}
}
<?xml version="1.0" encoding="utf-16"?>
<myDomain:CFMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xyz="http://someURI.com"
xsi:schemaLocation="http://STPMonitor.myDomain.com/schemas/CFMessage.xsd"
xyz:somestring="Hello World" type="JOIN" username="SJ-3-3008-1"
xmlns:myDomain="http://STPMonitor.myDomain.com" />
Add "http://www.w3.org/2001/XMLSchema" namespace by:
private static string DataContractSerialize(object obj)
{
StringWriter sw = new StringWriter();
DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
using (XmlTextWriter xw = new XmlTextWriter(sw))
{
//serializer.WriteObject(xw, obj);
//
// Insert namespace for C# types
serializer.WriteStartObject(xw, obj);
xw.WriteAttributeString("xmlns", "x", null, "http://www.w3.org/2001/XMLSchema");
serializer.WriteObjectContent(xw, obj);
serializer.WriteEndObject(xw);
}
StringBuilder buffer = sw.GetStringBuilder();
return buffer.ToString();
}

Categories

Resources