.NET XML serializer and XHTML string in object property - c#

I have a class
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.7.3081.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://test/v1")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://test/v1", IsNullable=false)]
public partial class Data
{
...
public object Comment { get; set; }
...
}
The Comment property is of type object because it is declared as type any in the xml schema. It's declared as any to allow both text and xhtml data. I can't change the schema - it's related to an international standard.
Single-line content (string):
<Comment>This is a single line text</Comment>
Multi-line content (xhtml):
<Comment>
<div xmlns="http://www.w3.org/1999/xhtml">This is text<br />with line breaks<br />multiple times<div>
</Comment>
The XmlSerializer won't allow me to plug an XmlElement into the object Comment property of the auto-generated Data class. I've also tried to create a custom IXmlSerializer implementation for XHtml, but then the XSD-generated Comment property needs to be declared as that exact type (instead of object).
The custom XHtml type I'm trying to set on the Comment property looks like this;
[XmlRoot]
public class XHtmlText : IXmlSerializable
{
[XmlIgnore]
public string Content { get; set; }
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader) { } // Only used for serializing to XML
public void WriteXml(XmlWriter writer)
{
if (Content.IsEmpty()) return;
writer.WriteStartElement("div", "http://www.w3.org/1999/xhtml");
var lines = Content.Split('\n');
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
writer.WriteRaw(line);
if (i < lines.Length - 1) writer.WriteRaw("<br />");
}
writer.WriteFullEndElement();
}
}
Exception from the XmlSerializer:
InvalidOperationException: The type Lib.Xml.XHtmlText may not be used
in this context. To use Lib.Xml.XHtmlText as a parameter, return type,
or member of a class or struct, the parameter, return type, or member
must be declared as type Lib.Xml.XHtmlText (it cannot be object).
Objects of type Lib.Xml.XHtmlText may not be used in un-typed
collections, such as ArrayLists
The serialization code:
var data = new Lib.Xml.Data { Content = "test\ntest\ntest\n" };
var settings = new XmlWriterSettings()
{
NamespaceHandling = NamespaceHandling.OmitDuplicates,
Indent = false,
OmitXmlDeclaration = omitDeclaration,
};
using (var stream = new MemoryStream())
using (var xmlWriter = XmlWriter.Create(stream, settings))
{
var serializer = new XmlSerializer(data.GetType(), new[] { typeof(Lib.Xml.XHtmlText) });
serializer.Serialize(xmlWriter, data);
return stream.ToArray();
}

Looks like I've found the solution.
I can't set the comment to an instance of XmlElement
data.Comment = commentAsXhtmlXmlElement;
but I can (somehow) assign an array of XmlNode
data.Comment = new XmlNode[] { commentAsXhtmlXmlElement };
Found out when I deserialized an inbound xml-instance of the data POCO.
Strange.

Related

Remove certain attributes (xmlns) using XmlSerializer class

I need to remove all of the xmlns attributes from every element. I am using the XmlSerializer class in c#.
Here is a sample of the XML I am getting from serializing an object returned by a webservice.
Code for serialization
public static string ToXML<T>(object obj,string nameSpace)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
var xml = "";
using (var stringwriter = new StringWriter())
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stringwriter, obj,ns);
xml = xml + stringwriter;
}
return xml;
}
Calling Code
var unitInfo = RC.GetUnitInfo(txtUnitNum.Text);
var x = XML.DocumentExtensions.ToXML<Vehicles>(unitInfo, "");
Result
<Vehicles>
<Unit xmlns="http://Entities/2006/09">430160</Unit>
<VinNumber xmlns="http://Entities/2006/09">1FUJGP9337</VinNumber>
<CustName xmlns="http://Entities/2006/09">Ryder : N/A</CustName>
<CustCode xmlns="http://Entities/2006/09">4199</CustCode>
<NationalAccountFVM xmlns="http://Entities/2006/09">0</NationalAccountFVM>
<VehMake xmlns="http://Entities/2006/09">FREIGHTLINER/MERCEDES</VehMake>
<VehModel xmlns="http://Entities/2006/09">PX12564ST CASCADIA</VehModel>
<VehYear xmlns="http://Entities/2006/09">2012</VehYear>
<VehDescrip xmlns="http://Entities/2006/09">TRACTOR</VehDescrip>
<InService xmlns="http://Entities/2006/09">10/28/2011 12:00:00 AM</InService>
<EngMake xmlns="http://Entities/2006/09">CUMMINS ENGINE CO.</EngMake>
<EngModel xmlns="http://Entities/2006/09">ISX'10 14.9 450/1800</EngModel>
<EngSize xmlns="http://Entities/2006/09">450</EngSize>
<EngSerial xmlns="http://Entities/2006/09">79502</EngSerial>
<TransMake xmlns="http://Entities/2006/09">FULLER TRANS., DIV. EATON</TransMake>
</Vehicles
I need to serialize the object without getting the xmlns attributes.
Vehicle Object
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://Entities/2006/09")]
public partial class Vehicles {
private string unitField;
private string vinNumberField;
/// <remarks/>
public string Unit {
get {
return this.unitField;
}
set {
this.unitField = value;
}
}
/// <remarks/>
public string VinNumber {
get {
return this.vinNumberField;
}
set {
this.vinNumberField = value;
}
}
}
I modified your ToXML() method below to always correctly exclude a namespace.
The main difference is how the XmlSerializerNamespaces is instantiated.
Generating an empty namespace disables the inserting of namespaces by the XmlWriter into the output XML.
I also change how you build the output XML since string concatenation is more resource intensive than utilizing a StringBuilder in conjuction with a StringWriter.
public static string ToXML<T>(object obj)
{
StringBuilder outputXml = new StringBuilder();
using (var stringwriter = new StringWriter(outputXml))
{
// Define a blank/empty Namespace that will allow the generated
// XML to contain no Namespace declarations.
XmlSerializerNamespaces emptyNS = new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") });
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stringwriter, obj, emptyNS);
}
return outputXml.ToString();
}
Passing in a sample object, I get the output XML of
<?xml version="1.0" encoding="utf-16"?>
<Vehicles>
<Unit>430160</Unit>
<VinNumber>1FUJGP9337</VinNumber>
<CustName>Ryder : N/A</CustName>
<CustCode>4199</CustCode>
<NationalAccountFVM>0</NationalAccountFVM>
<VehMake>FREIGHTLINER/MERCEDES</VehMake>
<VehModel>PX12564ST CASCADIA</VehModel>
<VehYear>2012</VehYear>
<VehDescrip>TRACTOR</VehDescrip>
<InService>2011-10-28T00:00:00</InService>
<EngMake>CUMMINS ENGINE CO.</EngMake>
<EngModel>ISX'10 14.9 450/1800</EngModel>
<EngSize>450</EngSize>
<EngSerial>79502</EngSerial>
<TransMake>FULLER TRANS., DIV. EATON</TransMake>
</Vehicles>

Serialize c# object in XML using variable content as attribute name

I have the following c# object:
class Modification {
public string Name;
public string Value;
}
I want to use the serializer to serialize my object the following way:
<name>value</name>
Example: Let's say we set those variables to
Name = "Autoroute"
Value = 53
I want the xml to look like:
<test>
<Autoroute>53</Autoroute>
</test>
I saw somewhere that this feature is not supported by the serializer, but is there a way to overload the serializer to allow this kind of behavior ?
Changing the XML structure is not an option since it is already a convention.
You can use IXmlSerializable to do this, though this doesn't give you control over the root element name - you have to set this in the serializer (which may present other challenges when you come to read it as part of a larger xml structure...).
public class Modification : IXmlSerializable
{
public string Name;
public string Value;
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Name = reader.Name;
Value = reader.ReadElementContentAsString();
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString(Name, Value);
}
}
Usage,
Modification modification = new Modification()
{
Name = "Autoroute",
Value = "53"
};
Modification andBack = null;
string rootElement = "test";
XmlSerializer s = new XmlSerializer(typeof(Modification), new XmlRootAttribute(rootElement));
using (StreamWriter writer = new StreamWriter(#"c:\temp\output.xml"))
s.Serialize(writer, modification);
using (StreamReader reader = new StreamReader(#"c:\temp\output.xml"))
andBack = s.Deserialize(reader) as Modification;
Console.WriteLine("{0}={1}", andBack.Name, andBack.Value);
The XML produced by this looks like this,
<?xml version="1.0" encoding="utf-8"?>
<test>
<Autoroute>53</Autoroute>
</test>

Xml serialization remove inner tags

I'm having a bit of trouble getting the desired xml serialization that I want. Thanks beforehand for your help.
So, the xml that I am aiming for looks something like this:
<ChangeSet>
<Change class="object" key="foo">
bar
</Change>
<Change class="testing" key="temp">
temp
</Change>
</ChangeSet>
What I'm actually getting is:
<ChangeSet>
<Change class="object" key="foo">
<Value> bar </Value>
</Change>
<Change class="testing" key="temp">
<Value> temp </Value>
</Change>
</ChangeSet>
Note that the value inside of the Value tags need to be able to be any object. (Collection, object, generic type... etc)
How can I get rid of the Value tags?
C# code:
[Serializable]
[XmlRoot("ChangeSet")]
public class ChangeSet
{
[XmlElement("Change", typeof(Change))]
public List<Change> Changes;
}
public class Change
{
[XmlAttribute("Class")]
public string Class;
[XmlAttribute("Description")]
public string Key;
public object Value;
}
StringBuilder xml = new StringBuilder();
XmlSerializer serializer = new XmlSerializer(objToSerialize.GetType());
XmlWriterSettings settings = new XmlWriterSettings()
{
OmitXmlDeclaration = true,
Indent = true
};
using (StringWriter writer = new StringWriter(xml))
{
using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings))
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Removes the xsd & xsi namespace declarations from the xml
serializer.Serialize(xmlWriter, objToSerialize, ns);
}
}
Use [XmlText] attribute over Value of type string
[XMLText]
public string Value;
or use another string property and ignore the Value property
[XMLIgnore]
public object Value;
[XMLText]
public string ValueString
{
get{ return this.Value.ToString(); }
}

Xml Serialization without XML Declaration [duplicate]

How do I serialize an XML-serializable object to an XML fragment (no XML declaration nor namespace references in the root element)?
Here is a hack-ish way to do it without having to load the entire output string into an XmlDocument:
using System;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
public class Example
{
public String Name { get; set; }
static void Main()
{
Example example = new Example { Name = "Foo" };
XmlSerializer serializer = new XmlSerializer(typeof(Example));
XmlSerializerNamespaces emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
StringBuilder output = new StringBuilder();
XmlWriter writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, example, emptyNamespace);
Console.WriteLine(output.ToString());
}
}
You should be able to just serialize like you usually do, and then use the Root property from the resulting document.
You may need to clear the attributes of the element first.
By the way this is awesome.
I implemented this code to make it easy to work with xml fragments as classes quickly and then you can just replace the node when finished. This makes the transition between code and xml ultra-easy.
First create some extension methods.
public static class SerializableFragmentExtensions
{
public static XElement ToElement(this ISerializableFragment iSerializableFragment)
{
var serializer = new XmlSerializer(iSerializableFragment.GetType());
var emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
var output = new StringBuilder();
var writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, iSerializableFragment, emptyNamespace);
return XElement.Parse(output.ToString(), LoadOptions.None);
}
public static T ToObject<T>(this XElement xElement)
{
var serializer = new XmlSerializer(typeof (T));
var reader = xElement.CreateReader();
var obj = (T) serializer.Deserialize(reader);
return obj;
}
}
Next Implement the required interface (marker interface--I know you are not supposed to but I think this is the perfect reason to it.)
public interface ISerializableFragment
{
}
Now all you have to do is decorate any Serializable class, you want to convert to an XElement Fragment, with the interface.
[Serializable]
public class SomeSerializableClass : ISerializableFragment
{
[XmlAttribute]
public string SomeData { get; set; }
}
Finally test the code.
static void Main(string[] args)
{
var someSerializableClassObj = new SomeSerializableClass() {SomeData = "Testing"};
var element = someSerializableClass.ToElement();
var backToSomeSerializableClassObj = element.ToObject<SomeSerializableClass>();
}
Thanks again for this amazingly useful code.

Can I make XmlSerializer ignore the namespace on deserialization?

Can I make XmlSerializer ignore the namespace (xmlns attribute) on deserialization so that it doesn't matter if the attribute is added or not or even if the attribute is bogus? I know that the source will always be trusted so I don't care about the xmlns attribute.
Yes, you can tell the XmlSerializer to ignore namespaces during de-serialization.
Define an XmlTextReader that ignores namespaces. Like so:
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader): base(reader) { }
public override string NamespaceURI
{
get { return ""; }
}
}
// helper class to omit XML decl at start of document when serializing
public class XTWFND : XmlTextWriter {
public XTWFND (System.IO.TextWriter w) : base(w) { Formatting= System.Xml.Formatting.Indented;}
public override void WriteStartDocument () { }
}
Here's an example of how you would de-serialize using that TextReader:
public class MyType1
{
public string Label
{
set { _Label= value; }
get { return _Label; }
}
private int _Epoch;
public int Epoch
{
set { _Epoch= value; }
get { return _Epoch; }
}
}
String RawXml_WithNamespaces = #"
<MyType1 xmlns='urn:booboo-dee-doo'>
<Label>This document has namespaces on its elements</Label>
<Epoch xmlns='urn:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'>0</Epoch>
</MyType1>";
System.IO.StringReader sr;
sr= new System.IO.StringReader(RawXml_WithNamespaces);
var s1 = new XmlSerializer(typeof(MyType1));
var o1= (MyType1) s1.Deserialize(new NamespaceIgnorantXmlTextReader(sr));
System.Console.WriteLine("\n\nDe-serialized, then serialized again:\n");
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("urn", "booboo-dee-doo");
s1.Serialize(new XTWFND(System.Console.Out), o1, ns);
Console.WriteLine("\n\n");
The result is like so:
<MyType1>
<Label>This document has namespaces on its elements</Label>
<Epoch>0</Epoch>
</MyType1>
If you expect no namespace, but the input has namespaces, then you can set
Namespaces = false
on your XmlTextReader.
Exdended Wolfgang Grinfeld answer (w/o exception handling):
public static Message Convert(XmlDocument doc)
{
Message obj;
using (TextReader textReader = new StringReader(doc.OuterXml))
{
using (XmlTextReader reader = new XmlTextReader(textReader))
{
reader.Namespaces = false;
XmlSerializer serializer = new XmlSerializer(typeof(Message));
obj = (Message)serializer.Deserialize(reader);
}
}
return obj;
}
Solved this by using XmlSerializer Deserialize to read from xml instead from stream. This way before xml is Deserialized, using Regex to remove xsi:type from the xml. Was doing this is Portable Class Library for Cross Platform, so did not had many other options :(. After this the deserialization seems to work fine.
Following code can help,
public static TClass Deserialize<TClass>(string xml) where TClass : class, new()
{
var tClass = new TClass();
xml = RemoveTypeTagFromXml(xml);
var xmlSerializer = new XmlSerializer(typeof(TClass));
using (TextReader textReader = new StringReader(xml))
{
tClass = (TClass)xmlSerializer.Deserialize(textReader);
}
return tClass;
}
public static string RemoveTypeTagFromXml(string xml)
{
if (!string.IsNullOrEmpty(xml) && xml.Contains("xsi:type"))
{
xml = Regex.Replace(xml, #"\s+xsi:type=""\w+""", "");
}
return xml;
}
Why try to make the XmlSerializer forget how XML works? It's a fact of XML that two elements with the same name but different namespaces are different elements.
If you want to process XML that has no namespaces, then you should pre-process it to remove the namespaces, and then pass it to the serializer.

Categories

Resources