Get node value of Parent two level up in XML in C# - c#

I have XML as shown in sample below -
<?xml version="1.0" encoding="UTF-8"?>
<Model>
<Part Id="0">
<Specs>
<Spec Id = "100" name="SpecNode">
</Spec>
</Specs>
</Part>
</Model>
This is an sample illustration of XML I have , so pls ignore any typing errors.
So in this XML I was able to retrieve Spec as XPathNodeIterator object. So now I would like to go for Part node and get Id attribute value of it. for what I have is XPathNodeIterator object which points to Spec and Part node lies two level above.
So please guide me in code changes I shall do to achieve this requirement.
Her is my code snippet..
System.Xml.XPath.XPathDocument xmlDoc = new System.Xml.XPath.XPathDocument("my.xml");
XPathNavigator xmlNav = xmlDoc.CreateNavigator();
XPathNodeIterator nodes = xmlNav.Select(XPathExpression.Compile(#"//Part/Specs/Spec[#name="SpecNode"]))
Above code sample gives me nodes object. SO my requirement now is to get Id value of Part node which two level above it.

You could use the following LINQ to retrieve the Id-attribute of the Part-node:
const string file = #"D:\Temp\file.xml";
// Load the file.
XDocument doc = XDocument.Load(file);
// Retrieve the attribute selecting the Part-Element and then its Id-attribute.
XAttribute partAttribute = doc.Root.Descendants("Part").Select(element => element.Attribute("Id")).FirstOrDefault();
// Call partAttribute.Value to retrieve its' value.
Console.WriteLine(partAttribute.Value);
Console.Read();
Output: 0.
Do note that if the Part-Element does not exist, a NullReferenceException will be thrown on the partAttribute.Value call. The same happens if the Part-Element exists but the Id does not. Use if(partAttribute != null) to make sure if the value exists.
If you will be getting alot of Part-nodes, you could sort them on ID using KeyValuePairs:
List<KeyValuePair<int, XElement>> partNodesWithIDValue = new List<KeyValuePair<int, XElement>>();
// Get a list of all Part-nodes where an ID exists.
List<XElement> partNodes = doc.Root.Descendants("Part").Where(element => element.Attribute("Id") != null).ToList();
// Sort the XElements according to their ID-value.
foreach (XElement partNode in partNodes)
{
KeyValuePair<int, XElement> elementWithAttribID = new KeyValuePair<int, XElement>(int.Parse(partNode.Attribute("Id").Value), partNode);
partNodesWithIDValue.Add(elementWithAttribID);
}
// Get a list of all Part-elements where the ID = 1.
List<XElement> partNodesWithID1 = partNodesWithIDValue.Where(kvp => kvp.Key == 1).Select(kvp => kvp.Value).ToList();
Seeing as you want to keep your original code, a simple change of XPath does the trick:
XPathNodeIterator nodes = xmlNav.Select(XPathExpression.Compile(#"//Part[#Id]"));
//Part[#Id]: //Part selects all nodes that are named Part. Adding the [#Id] makes it only select those Part-nodes that have an Id-attribute.

Deserialize the XML to an object, it'll be easier to work with and produces cleaner code:
Model class:
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Model
{
private ModelPart[] partField;
[System.Xml.Serialization.XmlElementAttribute("Part")]
public ModelPart[] Part
{
get
{
return this.partField;
}
set
{
this.partField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPart
{
private ModelPartSpecs specsField;
private byte idField;
public ModelPartSpecs Specs
{
get
{
return this.specsField;
}
set
{
this.specsField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte Id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPartSpecs
{
private ModelPartSpecsSpec specField;
public ModelPartSpecsSpec Spec
{
get
{
return this.specField;
}
set
{
this.specField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPartSpecsSpec
{
private byte idField;
private string nameField;
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte Id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
Deserialization:
XmlSerializer serializer = new XmlSerializer(typeof(Model));
Model model = null;
using (XmlReader reader = XmlReader.Create("data.xml"))
{
model = (Model)serializer.Deserialize(reader);
}
Now you'll be able to access model as an object and accessing the Part node is as simple as doing:
model.Part

Related

C# XML parsing thru EPPlus

I am parsing XML, I have generated Class from XSD file, I am able to access simple values from XML, but I don't know how to get value from this. I need it as String.
public partial class AccountIdentification4Choice {
private object itemField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("IBAN", typeof(string))]
[System.Xml.Serialization.XmlElementAttribute("Othr", typeof(GenericAccountIdentification1))]
public object Item {
get {
return this.itemField;
}
set {
this.itemField = value;
}
}
}
In XML value can be like this:
<CdtrAcct><Id><Othr><Id>BBAN</Id></Othr></Id></CdtrAcct>
or
<CdtrAcct><Id><IBAN>BBAN</IBAN></Id></CdtrAcct>
Update - all resource files:
XML: Priklad_multi.XML
XSD: camt.053.001.02.xsd
Then I parse XML with EPPlus package:
XmlSerializer ser = new XmlSerializer(typeof(Document));
Document resp = ser.Deserialize(new FileStream(file, FileMode.Open)) as Document;
And then I can access XML values like this (for example Stmt-Acct)
resp.BkToCstmrStmt.Stmt[ind].Acct.Id.Item
But I don't know how to access this CdtrAcct section in same way.
Your question is not clear, but as I understand you want to get IBAN tag, right? If so, you could do that easily with Linq To XML. ie:
void Main()
{
string xml1 = "<CdtrAcct><Id><Othr><Id>BBAN</Id></Othr></Id></CdtrAcct>";
string xml2 = "<CdtrAcct><Id><IBAN>BBAN</IBAN></Id></CdtrAcct>";
Console.WriteLine(CheckIban(xml1));
Console.WriteLine(CheckIban(xml2));
}
private string CheckIban(string xml)
{
var iban = XElement.Parse(xml).DescendantsAndSelf("IBAN").FirstOrDefault();
if (iban != null)
{
return (string)iban;
}
iban = XElement.Parse(xml).DescendantsAndSelf("Othr")
.FirstOrDefault();
if (iban != null)
{
return iban.Element("Id").Value;
}
return null;
}

Choose one of two system attributes

I need to create configurator which read/write xml configs.
Part of config is in the following form:
<camera>
<id>1</id>
<name>Camera 1</name>
<address>http://192.168.1.100</address>
<roi>
<rect>
<x>100</x>
<y>200</y>
<width>300</width>
<height>150</height>
</rect>
<rect>
<x>350</x>
<y>400</y>
<width>200</width>
<height>250</height>
</rect>
</roi>
</camera>
But an output I need in form with xml attributes:
<camera id="1" name="Camera 1" address="http://192.168.1.100">
<roi>
<rect x="100" y="200" width="300" height="150 />
<rect x="350" y="400" width="200" height="250 />
</roi>
</camera>
I create a class for every main node, but I was wondering how to choose if property for deserialization should be XmlElement and for serialization should be XmlAttribute. Or have I create two separate classes for first form of xml and for the second one? I am beginner in C# and .NET so also any points and suggestions to do it in other way?
C# code:
[System.Serializable()]
public class CamerasConfigAttrib
{
private int id;
private string name;
private string address;
private Collection<Rectangle> roi;
[XmlAttribute("id", Form = XmlSchemaForm.Unqualified)]
public int Id
{
get { return id; }
set { id = value; }
}
[XmlAttribute("name", Form = XmlSchemaForm.Unqualified)]
public string Name
{
get { return name; }
set { name = value; }
}
[XmlAttribute("address", Form = XmlSchemaForm.Unqualified)]
public string Address
{
get { return address; }
set { address = value; }
}
[XmlArray("roi", Form = XmlSchemaForm.Unqualified)]
[XmlArrayItem("rect", typeof(Rectangle), Form = XmlSchemaForm.Unqualified]
public Collection<Rectangle> Roi
{
get { return roi; }
set
{
foreach (var rect in value)
roi.Add(rect);
}
}
}
Use XmlAttributeOverrides to serialize you object to another structure (you can use it to deserialize also). Here Is a short sample how you can use it in your case:
[Serializable]
[XmlRoot("camera")]
public class CamerasConfigAttrib : IXmlSerializable
{
[XmlElement("id")]
public int Id { get; set; }
[XmlElement("name")]
public string Name { get; set; }
}
Process code:
// sample data
var xml = #"<camera><id>1</id><name>Camera 1</name></camera>";
// deserialization according to a native attributes
var camera = (CamerasConfigAttrib)new XmlSerializer(typeof(CamerasConfigAttrib))
.Deserialize(new StringReader(xml));
// prepare overridings
var overrides = new XmlAttributeOverrides();
overrides.Add(typeof (CamerasConfigAttrib), "Id",
new XmlAttributes
{
XmlAttribute = new XmlAttributeAttribute("Id")
});
overrides.Add(typeof(CamerasConfigAttrib), "Name",
new XmlAttributes
{
XmlAttribute = new XmlAttributeAttribute("name")
});
// serializer initiated with overridings
var s = new XmlSerializer(typeof(CamerasConfigAttrib), overrides);
var sb = new StringBuilder();
s.Serialize(new StringWriter(sb), camera);
Result
<?xml version="1.0" encoding="utf-16"?>
<camera xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="1" name="Camera 1" />
Seems to me this way is more natural, and doesn't require additional classes or files.
As an alternative you can implement interface IXmlSerializable, it gives more flexibility but it's a bit more complex

Read from XML to an object using LINQ

I am trying to read from an XML and enter into an object list.
My XML is
<User>
<Name>John</Name>
<Role>Admin</Role>
<QAList>
<QA>
<Question> Question 1 </Question>
<Answers>
<Answer> Answer 1 </Answer>
<Answer> Answer 2 </Answer>
<Answer> Answer 3 </Answer>
</Answers>
</QA>
</QAList>
</User>
This is my object class:
public class ListQuestionAnswers: INotifyPropertyChanged
{
private List<QuestionAnswer> _questionAnswer;
public List<QuestionAnswer> QuestionAnswerList
{
get { return _questionAnswer; }
set { _questionAnswer = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _role;
public string Role
{
get { return _role; }
set { _role = value; }
}
}
public class QuestionAnswer
{
public string _question;
public List<AnswerList> _answers;
public string Question
{
get { return _question; }
set { _question = value; }
}
public List<AnswerList> Answers
{
get { return _answers; }
set { _answers = value; }
}
}
public class AnswerList
{
private string _answer;
public string Answer
{
get { return _answer; }
set {_answer = value; }
}
}
I am able to get Name and Role, but how can I get QA List? I tried writing a select within the query but that didnt seem to work.
XDocument xmlDoc = XDocument.Load(#"C:\Test\Sample.xml");
var files = (from x in xmlDoc.Elements("User")
select new TestCasesViewModel
{
Name = (string)x.Element("Name").Value ?? string.Empty,
Role = (string)x.Element("Role").Value ?? string.Empty
}).ToList();
You can do the following:
var files = (from x in xmlDoc.Elements("User")
select
new TestCasesViewModel
{
Name = (string)x.Element("Name") ?? string.Empty,
Role = (string)x.Element("Role") ?? string.Empty,
QuestionAnswerList =
x.Descendants("QA")
.Select(q => new QuestionAnswer
{
Question = (string)q.Element("Question"),
Answers = q.Descendants("Answer").Select(a => new AnswerList { Answer = (string)a}).ToList()
}).ToList()
}).ToList();
Note that you don't need to access Value property if you are using explicit cast, whole point of using explicit cast is to avoid possible exceptions, if you use the Value property then explicit cast become redundant because if the element wasn't found it will throw the exception anyway...
In situations like this, I find it to be extremely helpful to split-up a complex LINQ expression into multiple sub-expressions, if only to help with development and debugging.
Without knowing more about the structure of your real xml data, here are some ideas:
XDocument xmlDoc = XDocument.Load(#"C:\Test\Sample.xml");
var users = xmlDoc.Elements("User");
foreach(var user in users)
{
var qaList = user.Element("QAList");
foreach(var qa in qaList.Elements("QA"))
{
//at this point, you should have access the <Question> node e.g.
string question = qa.Element("Question").Value;
//and the list of answers would be similar to before
var answers = qa.Element("Answers");
foreach(var answer in answers.Elements("Answer"))
{
//now answer.Value should be the text of the answer
}
}
}
Again, this isn't the "cleanest" approach, and certainly there are more concise ways to express this in LINQ. The point is, you have quite a few more options for break-points/debugging, allowing you explore the objects you are working with.

Class for serialization into XML with different node structure in list

I want to create a serializable C# class that would serialize into:
<metadata>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
<entry key="">false</entry>
</metadata>
Can you help me? I can't handle different entry node structure:/ Too hard for me:P
Let's begin from the lowest class in the hierarchy:
[Serializable]
public class DimensionInfo
{
[XmlElement("enabled")]
public Boolean Enabled { get; set; }
public DimensionInfo()
{
}
}
As you see, there's nothing special here. Then, proceed with the next:
[Serializable]
public class Entry
{
private DimensionInfo _dimensionInfo = default(DimensionInfo);
private Boolean _containsDimensionInfo = true;
[XmlAttribute("key")]
public String Key { get; set; }
[XmlText(typeof(String))]
public String ContainsDimensionInfo
{
get
{
return CheckDimensionContaining().ToString().ToLower();
}
set
{
_containsDimensionInfo = Boolean.Parse(value);
}
}
[XmlIgnore]
public Boolean ContainsDimensionInfoSpecified
{
get { return !CheckDimensionContaining(); }
}
[XmlElement("dimensionInfo")]
public DimensionInfo DimensionInfo
{
get { return _dimensionInfo; }
set { _dimensionInfo = value; }
}
[XmlIgnore]
public Boolean DimensionInfoSpecified
{
get { return CheckDimensionContaining(); }
}
public Entry()
{
Key = String.Empty;
CheckDimensionContaining();
}
private Boolean CheckDimensionContaining()
{
return _containsDimensionInfo = _dimensionInfo != default(DimensionInfo);
}
}
Here the magic begins. With using selective XmlIgnoreAttribute we can decide the ways the object serializes. The main class only wraps a list of Entries:
[Serializable]
[XmlRoot("metadata")]
public class Metadata
{
[XmlElement("entry")]
public List<Entry> Entries { get; set; }
public Metadata()
{
Entries = new List<Entry>();
}
}
How it is made: entry contains dimensionInfo if it exists, and false if it does not. Try out the code sample in console:
Metadata metadata = new Metadata();
metadata.Entries.Add(new Entry()); //Adding empty entry
//Adding entry with info
metadata.Entries.Add(new Entry() { DimensionInfo = new DimensionInfo() });
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Metadata));
using (FileStream fileStream = new FileStream("info.txt", FileMode.Create))
{
xmlSerializer.Serialize(fileStream, metadata);
}
It gave me the following output:
<metadata>
<entry key="">false</entry>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
</metadata>
However, this code is not perfect and there's a lot of things to improve, but now you have an idea of how it works
If you ever encounter XML files that you need to consume via .Net there is a tool XML Schema Definition Tool (Xsd.exe) that does the conversion automatically.
Its a command-line tool and to open it you use the Start > All
Programs > Visual Studio 2008/10/12/13 > Visual Studio Tools > Visual
Studio Command Prompt
The syntax to create a class from the XML file is described in the MSDN article. I'll give you an overview of the process to get you started.
Save the xml file to the temp dirtectory:
<metadata>
<entry key="">
<dimensionInfo>
<enabled>false</enabled>
</dimensionInfo>
</entry>
<entry key="">false</entry>
</metadata>
Fire up the Visual Studio Command Prompt
To find out all the options enter xsd /?
Now you need to convert the XML file to a XSD file, this is the command:
xsd "C:\temp\File.xml" /c /outputdir:c:\temp
This will create an XSD file in the temp directory.
Then to convert the XSD file to a Serializable C# class this is the command:
xsd "C:\temp\File.xsd" /c /outputdir:c:\temp
This is the resulting C# Serialized class file:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public partial class metadata {
private metadataEntry[] itemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("entry", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public metadataEntry[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class metadataEntry {
private metadataEntryDimensionInfo[] dimensionInfoField;
private string keyField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("dimensionInfo", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public metadataEntryDimensionInfo[] dimensionInfo {
get {
return this.dimensionInfoField;
}
set {
this.dimensionInfoField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string key {
get {
return this.keyField;
}
set {
this.keyField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
public partial class metadataEntryDimensionInfo {
private string enabledField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string enabled {
get {
return this.enabledField;
}
set {
this.enabledField = value;
}
}
}
Sounds like;
public class Metadata
{
private Dictionary<string, object> entry;
}
Where the dictionary has two item; "false" and another object;
public class dimensionInfo
{
public bool enabled = false;
}
However, you have 2 keys that are empty string. Are you sure the keys shouldn't have a value?
Actually you can create your own custom serialization for any class, so you don't have to stick to the schema when implementing your class.
All you have to do is to create a class which implements the IXmlSerializable interface
You can read more on the topic here:
Custom XML serialization
Otherwise you can use Attributes to control XML serialization.
Using [XmlAttribute] results in serialization as attribute.
Using [XmlText] results in serialization as inner text in the node. (Like the false string inside the other element in your example XML.)
Example:
public class Program
{
static void Main(string[] args)
{
Metadata meta = new Metadata();
meta.entry = new List<Entry>();
var dim = new dimensionInfo();
meta.entry.Add(
new Entry()
{
key = "",
O = dim
}
);
meta.entry.Add(
new Entry()
{
key = "",
text = "false",
O = null
}
);
XmlWriterSettings set = new XmlWriterSettings();
set.NamespaceHandling = NamespaceHandling.OmitDuplicates;
set.OmitXmlDeclaration = true;
set.DoNotEscapeUriAttributes = false;
set.Indent = true;
set.NewLineChars = "\n\r";
set.IndentChars = "\t";
XmlWriter writer = XmlWriter.Create(Console.Out, set);
XmlSerializer ser = new XmlSerializer(typeof(Metadata), "");
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
ser.Serialize(writer, meta, namespaces);
}
[XmlRoot("metadata")]
public class Metadata
{
[XmlElement]
public List<Entry> entry;
}
public class dimensionInfo
{
[XmlElement]
public bool enabled = false;
}
public class Entry
{
[XmlAttribute] // serialized as attribute
public string key = "";
[XmlText] // serialized as text node
public string text = "";
[XmlElement("dimensionInfo")] // serialized as an element
public dimensionInfo O = null;
}
}
This resulted the following XML:
<metadata>
<entry key=""><dimensionInfo><enabled>false</enabled></dimensionInfo></entry>
<entry key="">false</entry>
</metadata>
You can use XSD.exe to autoGenerate C# class in which you can serialize.
A straight forward answer:
Having your class to implement IXmlSerializable interface and using an XmlSerializer to defining the behavior for all the cases you desire.
private DimensionInfo _value;
public void WriteXml(XmlWriter writer)
{
var valueSerializer = new XmlSerializer(typeof (DimensionInfo));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
writer.WriteStartElement("entry");
writer.WriteAttributeString("key", string.Empty, string.Empty);
// Here you define how you want your XML structure to look like
// E.g. write an empty XML node in case of a null value
if (_value != null)
{
valueSerializer.Serialize(writer, value, ns);
}
writer.WriteEndElement();
}
Would produce this in XML
<entry key="">
<dimensionInfo>
<enabled>true</enabled>
</dimensionInfo>
</entry>
Or in case of a null value
<entry key="" />
A more detailed example with a XmlSerializableDictionary:
I used an XmlSerializableDictionary approach to produce the XML you provided. Again, you can specify precisely how the produced XML should look like in the WriteXml method.
[XmlRoot("metadata")]
public class XmlSerializableDictionary<TValue> : Dictionary<string, TValue>, IXmlSerializable where TValue : class
{
private const string XmlKeyName = "key";
private const string XmlValueName = "entry";
public void WriteXml(XmlWriter writer)
{
var valueSerializer = new XmlSerializer(typeof (TValue));
var ns = new XmlSerializerNamespaces(); ns.Add("", "");
foreach (var key in Keys)
{
writer.WriteStartElement(XmlValueName);
writer.WriteAttributeString(XmlKeyName, string.Empty, key);
var value = this[key];
// Only serialize the value if value is not null, otherwise write the
// empty XML element.
if (value != null)
{
valueSerializer.Serialize(writer, value, ns);
}
writer.WriteEndElement();
}
}
public void ReadXml(XmlReader reader) { /* left out */ }
public XmlSchema GetSchema() { return null; }
}
The DimensionInfo type for reference
[XmlRoot("dimensionInfo")]
public class DimensionInfo
{
[XmlElement("enabled")]
public Boolean Enabled { get; set; }
}
The following code serializes the dictionary to XML
var xmlSerializableDictionary = new XmlSerializableDictionary<DimensionInfo>
{
{"justakey", new DimensionInfo {Enabled = true}},
{"anotherkey", null}
};
var xmlSerializer = new XmlSerializer(typeof (SerializableDictionary<DimensionInfo>));
xmlSerializer.Serialize(File.Open(#"D:\xmlSerializedDictionary.xml", FileMode.Create), serializableDictionary);
Produced XML file:
<?xml version="1.0"?>
<metadata>
<entry key="justakey">
<dimensionInfo>
<enabled>true</enabled>
</dimensionInfo>
</entry>
<entry key="anotherkey" />
</metadata>

XML Serialization - XmlCDataSection as Serialization.XmlText

I am having problems serializing a cdata section using c#
I need to serialize XmlCDataSection object property as the innertext of the element.
The result I am looking for is this:
<Test value2="Another Test">
<![CDATA[<p>hello world</p>]]>
</Test>
To produce this, I am using this object:
public class Test
{
[System.Xml.Serialization.XmlText()]
public XmlCDataSection value { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string value2 { get; set; }
}
When using the xmltext annotation on the value property the following error is thrown.
System.InvalidOperationException:
There was an error reflecting property
'value'. --->
System.InvalidOperationException:
Cannot serialize member 'value' of
type System.Xml.XmlCDataSection.
XmlAttribute/XmlText cannot be used to
encode complex types
If I comment out the annotation, the serialization will work but the cdata section is placed into a value element which is no good for what I am trying to do:
<Test value2="Another Test">
<value><![CDATA[<p>hello world</p>]]></value>
</Test>
Can anybody point me in the right direction to getting this to work.
Thanks, Adam
Thanks Richard, only now had chance to get back to this. I think I have resolved the problem by using your suggestion. I have created a CDataField object using the following:
public class CDataField : IXmlSerializable
{
private string elementName;
private string elementValue;
public CDataField(string elementName, string elementValue)
{
this.elementName = elementName;
this.elementValue = elementValue;
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
w.WriteStartElement(this.elementName);
w.WriteCData(this.elementValue);
w.WriteEndElement();
}
public void ReadXml(XmlReader r)
{
throw new NotImplementedException("This method has not been implemented");
}
}
The way Test is defined, your data is a CData object. So the serialisation system is trying to preserve the CData object.
But you want to serialise some text data as a CData section.
So first, the type of Test.value should be String.
You then need to control how that field is serialised, but there does not appear to be any inbuilt method or attribute to control how strings are serialised (as string, maybe with entities for reserved characters, or as CDATA). (Since, from an XML infoset perspective all of these are the same, this is not surprising.)
You can of course implemented IXmlSerializable and just code the serialisation of the Test type yourself which gives you complete control.
This basically shorter version of Jack answer with better error messages:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] ContentAsCData
{
get => new[] { new XmlDocument().CreateCDataSection(Content) };
set => Content = value?.Cast<XmlCDataSection>()?.Single()?.Data;
}
Just found an alternative from here:
[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));
}
var node0 = value[0];
var cdata = node0 as XmlCDataSection;
if (cdata == null)
{
throw new InvalidOperationException(
String.Format(
"Invalid node type {0}", node0.NodeType));
}
Content = cdata.Data;
}
}
}
I had very same problem as Adam. However this Answer does not helped me at 100% :) but gives me a clue. So I'va created a code like below. It generates XML like this:
<Actions>
<Action Type="reset">
<![CDATA[
<dbname>longcall</dbname>
<ontimeout>
<url>http://[IPPS_ADDRESS]/</url>
<timeout>10</timeout>
</ontimeout>
]]>
</Action>
<Action Type="load">
<![CDATA[
<dbname>longcall</dbname>
]]>
</Action>
</Actions>
Code:
public class ActionsCDataField : IXmlSerializable
{
public List<Action> Actions { get; set; }
public ActionsCDataField()
{
Actions = new List<Action>();
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
foreach (var item in Actions)
{
w.WriteStartElement("Action");
w.WriteAttributeString("Type", item.Type);
w.WriteCData(item.InnerText);
w.WriteEndElement();
w.WriteString("\r\n");
}
}
public void ReadXml(XmlReader r)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(r);
XmlNodeList nodes = xDoc.GetElementsByTagName("Action");
if (nodes != null && nodes.Count > 0)
{
foreach (XmlElement node in nodes)
{
Action a = new Action();
a.Type = node.GetAttribute("Type");
a.InnerText = node.InnerXml;
if (a.InnerText != null && a.InnerText.StartsWith("<![CDATA[") && a.InnerText.EndsWith("]]>"))
a.InnerText = a.InnerText.Substring("<![CDATA[".Length, a.InnerText.Length - "<![CDATA[]]>".Length);
Actions.Add(a);
}
}
}
}
public class Action
{
public String Type { get; set; }
public String InnerText { get; set; }
}

Categories

Resources