.Net Xml whitespace deserialization - c#

Just tried to serialize and deserialize several whitespace with additional xml attribute to preserve them as described in msdn (see remarks). Here is simple C# example.
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace XmlTextTest
{
[Serializable]
public class WhitespaceTest
{
[XmlAttribute("space", Namespace = "http://www.w3.org/XML/1998/namespace")]
public string Space { get; set; } = "preserve";
[XmlText]
public string Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
var testValue = " ";
var test = new WhitespaceTest { Value = testValue };
var serializer = new XmlSerializer(typeof(WhitespaceTest));
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, test);
stream.Seek(0, SeekOrigin.Begin);
Console.WriteLine(new StreamReader(stream).ReadToEnd());
stream.Seek(0, SeekOrigin.Begin);
var deserialized = serializer.Deserialize(stream) as WhitespaceTest;
if (deserialized.Value != testValue)
throw new Exception();
}
}
}
}
Result xml
<WhitespaceTest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:space="preserve"> </WhitespaceTest>
May be it's my mistake but I expect that whitespaces will be deserialized as is (according to standard), not "null". How to mark original Type property to prevent replacing whitespaces with "null" into XmlText region.
Modified example to use IgnoreWhiteSpace property:
var str = "<WhitespaceTest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xml:space='preserve'> </WhitespaceTest>";
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = false;
var reader = XmlReader.Create(new StringReader(str), settings);
var deserialized2 = serializer.Deserialize(reader) as WhitespaceTest;
if (deserialized2.Value != testValue)
throw new Exception();
Still no effect.
Updated:
Created implementation of IXmlSerializable and added "xml:space=preserve" attribute manually. See example:
public class Sample : IXmlSerializable
{
#region Properties
[XmlText]
public string Text { get; set; }
#endregion
#region IXmlSerializable members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
ReadXml(this, reader);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
WriteXml(this, writer);
}
#endregion IXmlSerializable members
internal static void ReadXml(ILocalizedMappable serializable, System.Xml.XmlReader reader)
{
var node = new XmlDocument().ReadNode(reader);
if (node != null)
{
serializable.Text = node.InnerText;
}
}
internal static void WriteXml(ILocalizedMappable serializable, System.Xml.XmlWriter writer)
{
writer.WriteAttributeString("xml", "space", string.Empty, "preserve");
writer.WriteString(serializable.Text);
}
}

Related

How to generate this XML sample, with custom 2nd line, from within C#?

I need to generate the following XML, but have thus far been unsuccessful in injecting my custom header data:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>
How can i generate the above XML, from within C#?
Currently we have :
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
StringWriter sww = new StringWriter();
XmlWriter writer = XmlWriter.Create(sww);
serializer.Serialize(writer, employee);
Employee Class:
public class Employee
{
public string personnel_id { get; set; }
public string dob { get; set; }
}
Making some reasonable guesses about what your Employee class looks like, you need to do the following:
Decorate your Employee class with [XmlRoot("MT_Get_Name_Req", Namespace = "http://hsd.sd.com")]. This will give your XML root element the correct name and namespace.
While the root is in the http://hsd.sd.com namespace, its child elements are not in any namespace. Thus their namespace will need to be overridden using an XmlElement attribute.
Setting the XmlRoot namespace will use the specified namespace as the default namespace. If you must use a prefixed namespace, you need to pass a XmlSerializerNamespaces with all desired namespaces to XmlSerializer.Serialize().
Thus:
[XmlRoot("MT_Get_Name_Req", Namespace = Employee.XmlNamespace)]
public class Employee
{
public const string XmlNamespace = "http://hsd.sd.com";
public const string XmlNamespacePrefix = "ns0";
const string xsi = "http://www.w3.org/2001/XMLSchema-instance";
const string xsd = "http://www.w3.org/2001/XMLSchema";
public static XmlSerializerNamespaces XmlSerializerNamespaces
{
get
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(XmlNamespacePrefix, XmlNamespace);
// namespaces.Add("xsi", xsi); Uncomment to add standard namespaces.
// namespaces.Add("xsd", xsd);
return namespaces;
}
}
[XmlElement("personnel_id", Namespace="")]
public string personnel_id { get; set; }
[XmlElement("dob", Namespace = "")]
public string dob { get; set; }
}
And use it like
var xml = XmlSerializationHelper.GetXml(employee, Employee.XmlSerializerNamespaces, Encoding.UTF8);
Using the extension methods:
public static class XmlSerializationHelper
{
public sealed class StringWriterWithEncoding : StringWriter
{
private readonly Encoding encoding;
public StringWriterWithEncoding(Encoding encoding)
{
this.encoding = encoding;
}
public override Encoding Encoding
{
get { return encoding; }
}
}
public static string GetXml<T>(T obj, XmlSerializerNamespaces ns, Encoding encoding)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), ns, encoding);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns, Encoding encoding)
{
using (var textWriter = new StringWriterWithEncoding(encoding))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // For cosmetic purposes.
settings.IndentChars = " "; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
if (ns != null)
serializer.Serialize(xmlWriter, obj, ns);
else
serializer.Serialize(xmlWriter, obj);
}
return textWriter.ToString();
}
}
}
To get
<?xml version="1.0" encoding="utf-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>
I did a small example for you. I included an XmlRoot Attribute to the Employee.class and added a namespace object when serializing the employee.
see Add prefixes and namespaces to xml Serialization
and SO Question How to set xml prefixes
Employee.cs
[XmlRoot("MT_Get_Name_Req", Namespace = "http://hsd.sd.com")]
public class Employee
{
[XmlElement(Namespace="")]
public string personnel_id { get; set; }
[XmlElement(Namespace = "")]
public string dob { get; set; }
}
demoFile.cs
private static void customXmlSerialization()
{
Employee employee = new Employee() { personnel_id = "1202", dob = "19470906" };
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("ns0", "http://hsd.sd.com");
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
string path = #"e:\temp\data.xml";
XmlTextWriter writer = new XmlTextWriter(path, Encoding.UTF8);
serializer.Serialize(writer, employee,ns);
}
output
<?xml version="1.0" encoding="utf-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>

C# DataContractSerializer - Deserialize list of list

I have problem deserializing list of list in my xml file:
<?xml version="1.0" encoding="UTF-8"?>
<RootLevel><!--Container-->
<ListOfOne><!--List of One -->
<One>
<ListOfTwo> <!--List of Two -->
<Two></Two>
</ListOfTwo>
</One>
</ListOfOne>
</RootLevel>
RootLevel has List of One.
One has List of Two
The first level (ListOfOne) is working file with out any problem, the problem is that the ListOfTwo is not being deserialized
[KnownType(typeof(List<One>))]
[DataContract(Name = "RootLevel", Namespace = "")]
public sealed class RootLevel
{
[DataMember()]
public List<One> ListOfOne { get; set; }
public RootLevel()
{
ListOfOne = new List<One>();
}
}
[DataContract(Name = "One", Namespace = "")]
[KnownType(typeof(List<Two>))]
public sealed class One
{
public One()
{
ListOfTwo = new List<Two>();
}
[OnDeserialized]
internal void OnSerializingMethod(StreamingContext context)
{
ListOfTwo = new List<Two>();
}
[DataMember]
public List<Two> ListOfTwo { get; set; }
}
[DataContract(Name = "Two", Namespace = "")]
public sealed class Two
{}
This is the operation:
using (var fs = new FileStream("path to file", FileMode.Open))
{
using (var reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
{
DataContractSerializer ser = new DataContractSerializer(typeof(RootLevel));
var deserializedPerson = (RootLevel)ser.ReadObject(reader, true);
Assert.IsTrue(deserializedPerson.ListOfOne[0].ListOfTwo.Count > 0);
reader.Close();
fs.Close();
}
}
If you drop this part of the code everything works as expected:
[OnDeserialized]
internal void OnSerializingMethod(StreamingContext context)
ListOfTwo = new List<Two>();
}
If you wish to make sure you always have an empty ListOfTwo change it to:
[OnDeserialized]
internal void OnSerializingMethod(StreamingContext context)
{
if(ListOfTwo == null) {
ListOfTwo = new List<Two>();
}
}
I ran the code with a small modification (not reading from file)
string xml = #"<?xml version=""1.0"" encoding=""UTF-8""?>
<RootLevel> <!--Container-->
<ListOfOne> <!--List of One -->
<One>
<ListOfTwo> <!--List of Two -->
<Two></Two>
</ListOfTwo>
</One>
</ListOfOne>
</RootLevel>";
var stream = new MemoryStream(Encoding.Default.GetBytes(xml));
using (var reader = XmlDictionaryReader
.CreateTextReader(stream,
new XmlDictionaryReaderQuotas()))
{
DataContractSerializer ser = new DataContractSerializer(typeof(RootLevel));
var deserializedPerson = (RootLevel)ser.ReadObject(reader, true);
Assert.IsTrue(deserializedPerson.ListOfOne[0].ListOfTwo.Count > 0);
reader.Close();
}
with this contract change
[DataContract(Name = "One", Namespace = "")]
[KnownType(typeof(List<Two>))]
public sealed class One
{
public One()
{
ListOfTwo = new List<Two>();
}
[OnDeserialized]
internal void OnSerializingMethod(StreamingContext context)
{
if (ListOfTwo == null)
{
ListOfTwo = new List<Two>();
}
}
[DataMember]
public List<Two> ListOfTwo { get; set; }
}
And the Assert is fine, ListOfTwo has one object as expected.

Removing Parent XML element from XML Serialization in C#?

I have a class named Node and inside that i have Property of Type Document Class.
When I serialize it into XML, I get the output as
<Node>
<DocumentType>
<File></File>
</DoumentType>
<Node>
But I want the output as
<Node>
<File></File>
<Node>
Object Code
public class Document
{
[XmlElement(ElementName = "file")]
public string File { get; set; }
}
public class Node
{
public Document NodeDocument
{
get;
set;
}
}
How can I do that using C# xml Serialization?
Following Kami's suggestion, here is the code for your reference. All credit goes to Kami.
public class Node : IXmlSerializable {
public Node() {
NodeDocument = new Document();
}
public Document NodeDocument { get; set; }
public System.Xml.Schema.XmlSchema GetSchema() {
return null;
}
public void ReadXml(XmlReader reader) {
reader.ReadStartElement();
NodeDocument.File = reader.ReadString();
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer) {
writer.WriteStartElement("file");
writer.WriteString(NodeDocument.File);
writer.WriteEndElement();
}
}
public class Document {
public String File { get; set; }
}
class Program {
static void Main(string[] args) {
var node = new Node();
node.NodeDocument.File = "bbb.txt";
Serialize<Node>("a.xml", node);
node = Deserialize<Node>("a.xml");
Console.WriteLine(node.NodeDocument.File);
Console.Read();
}
static T Deserialize<T>(String xmlFilePath) where T : class {
using (var textReader = File.OpenText(xmlFilePath)) {
using (var xmlTextReader = new XmlTextReader(textReader)) {
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(xmlTextReader);
}
}
}
static void Serialize<T>(String xmlFilePath, T obj) where T : class {
using (var textWriter = File.CreateText(xmlFilePath)) {
using (var xmlTextWriter = new XmlTextWriter(textWriter)) {
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(xmlTextWriter, obj);
}
}
}
}
Have you considered implementing IXmlSerializable interface - http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx.
You should then be able to write custom serialization/deserialization to facilitate the above.

OmitXmlDeclaration in XmlWriter and implementing IXmlSerializable

I want to create custom xml serialization by implementing IXmlSerializable.
I've got this test class that implements IXmlSerializable interface:
[Serializable]
public class Employee : IXmlSerializable
{
public Employee()
{
Name = "Vyacheslav";
Age = 23;
}
public string Name{get; set;}
public int Age { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
this.Name = reader["Name"].ToString();
this.Age = Int32.Parse(reader["Age"].ToString());
}
public void WriteXml(XmlWriter writer)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.ConformanceLevel = ConformanceLevel.Fragment;
XmlWriter newWriter = XmlWriter.Create(writer, settings);
newWriter.WriteAttributeString("Name", this.Name);
newWriter.WriteAttributeString("Age", this.Age.ToString());
}
}
What I want to do is to omit xml declaration. For that I create proper instance of XmlWriterSettings and pass it as second parameter to create new XmlWriter.
But when I debug this piece of code, I see that newWriter.Settings.OmitXmlDeclaration is set to false and serialized data contains tag. What am I doing wrong?
The actual serialization looks like this:
var me = new Employee();
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
TextWriter writer = new StreamWriter(#"D:\file.txt");
serializer.Serialize(writer, me);
writer.Close();
And the second question is - if I want to serialize type Employee that has cutom type ContactInfo at field to be serialized, do I need to implement IXmlSerializable on ContactInfo too?
The writer-settings is a function of the outermost writer; you should be applying that to the code that creates the file, i.e.
using(var file = File.Create("file.txt"))
using(var writer = XmlWriter.Create(file, settings))
{
serializer.Serialize(writer, me);
}
additionally, then, you don't need to implement IXmlSerializable. You cannot do this at the inner level - it is too late.
For example:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class Employee
{
[XmlAttribute] public string Name { get; set; }
[XmlAttribute] public int Age { get; set; }
}
class Program
{
static void Main()
{
var settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
var me = new Employee {
Name = "Vyacheslav", Age = 23
};
var serializer = new XmlSerializer(typeof (Employee));
using (var file = File.Create("file.txt"))
using (var writer = XmlWriter.Create(file, settings))
{
serializer.Serialize(writer, me);
}
}
}
and if you don't want the extra namespaces, then:
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(writer, me, ns);
which generates the file:
<Employee Name="Vyacheslav" Age="23" />

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