Populating and XML template with LINQ to XML? - c#

I have multiple XML templates and I would like to populate them using LINQ to XML, but I wasn't sure how to get started, meaning how to correctly read in the XML file (Best method) and populate attributes and Nodes (InnerText). Also, how do create new elements and attributes in the existing template as well remove/update existing ones? Here is an example of a template:
<Person age="age">
<FirstName></FirstName>
<LastName></LastName>
<Children>
<Name></Name>
</Children>
</Person>
In the abvoe, the age attribute is a placeholder, FirstName, LastName and Name under Children are placeholders. Let's also assume that I either want to remove Names under children or add names as well. Also lets assume I want to add a DOB element inside Person with day, month, year attributes?

Here's an example. You build a class for each template. I'm using this library extension class (XElementExtensions.cs) from: https://github.com/ChuckSavage/XmlLib/
public class Person
{
XElement self;
public Person(XElement person) { self = person; }
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); } // true set as attribute
}
public string FirstName
{
get { return self.Get("FirstName", string.Empty); }
set { self.Set("FirstName", value, false); } // false set as child node
}
public string LastName
{
get { return self.Get("LastName", string.Empty); }
set { self.Set("LastName", value, false); }
}
public Children Children
{
get { return new Children(self.GetElement("Children")); }
}
}
public class Children
{
XElement self;
public Children(XElement children) { self = children; }
public Child this[string name]
{
get
{
return self.Elements("Name")
.Select(x => new Child(x))
.FirstOrDefault(c => c.Name == name);
}
}
public Child this[int index]
{
get { return new Child(self.Elements("Name").ElementAt(index)); }
}
public void Add(string name)
{
Child child = this[name];
if(null == child)
{
child = new Child(new XElement("Name"));
child.Name = name;
self.Add(child.self);
}
else
throw new ArgumentException("Child with name: "+ name +" already exists!");
}
}
public class Child
{
internal XElement self;
public Child(XElement child) { self = child; }
// You can have an Age/DOB for the child as well, or remove these two properties.
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
// DOB is an attribute as self.Value in Name erases all child nodes when set.
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); }
}
public string Name
{
get { return self.Value }
set { self.Value = value; }
}
}
If you want to specify null as the default for any Get() then you need to specify the type to Get with Get<Type>("node", null)

I would suggest to take a look at the LINQ to XML and its XDocument class. It makes navigation and editing of an xml document very easy.

Look at this site for samples of linq to xml

Related

Automapper to XML serialization producing unwanted parent XML elements

I'm using automapper to map out part of my game to an XML file so that I can export it:
cfg.CreateMap()
.ForMember(d => d.RoomType, opt => opt.MapFrom(src => src.GetRoomType()))
For some reason that I cannot figure out, it is adding a parent element to the XML like this:
<RoomType>
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
</RoomType>
I don't want the XML structure to look like that, I just want the elements like this:
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
Here is the method GetRoomType:
public static IEnumerable<RoomType> GetRoomType(this DungeonRoom room)
{
var customType = room.Monsters
.Where(e => e.Treasure.Category.IsActive);
if (room.RoomType?.MonsterType == null) yield break;
if (room.RoomType.MonsterType.StartsWith("X_Y"))
{
yield return new RoomType(room.RoomType.MonsterType);
}
foreach (PageTreasure pe in customType)
{
if (customType == null) yield break;
if (RoomLookupByTypeId.ContainsKey(pe.Treasure.Id))
yield return RoomLookupByTypeId[pe.Treasure.Id];
}
}
}
And here is the RoomLookupByTypeId() method:
public static readonly IDictionary<Int32, RoomType> RoomLookupByTypeId =
new Dictionary<Int32, RoomType> {
{373, new RoomType("X_Y001")},
{488, new RoomType("X_Y002")},
{467, new RoomType("X_Y003")},
{238, new RoomType("X_Y004")},
{756, new RoomType("X_Y005")}
};
And here is the class that is being mapped out to XML:
public class RoomType
{
[XmlText]
public String TypeId { get; set; }
[XmlAttribute("monster")]
public String Monster
{
get { return "Random"; }
set { throw new NotSupportedException("Computed property, setter only exists for XmlSerializer"); }
}
public RoomType() { }
public RoomType(String Id)
{
TypeId = Id;
}
}
And this is an example result:
<RoomType>
<RoomType monster="Random">X_Y001</RoomType>
<RoomType monster="Random">X_Y003</RoomType>
<RoomType monster="Random">X_Y005</RoomType>
</RoomType>
So the data is correct, but it's adding that opening and closing <RoomType> which it does not need.
I'm not sure why it's doing that.
At first, I thought it was because GetRoomType() was returning something other than 'null', but I added 'yield break' to stop it from returning anything if it finds a null.
So I'm at a loss. I have no idea why it's doing this.
Does anyone see anything that could be causing it?
Thanks!

Xml List Serialization and Node Type Names

Ive come across multiple questions and answers on here but none specific to my situation.
I have a class 'Entity' with multiple classes that extend off of it. I want the serialization to hit the list and understand and use the type of each item for the node name.
Now, I can use what is commented out (define each array item in the main class and define the name of such by using [XmlArrayItem("Subclass1", typeof(subclass1)] but I want to keep all definitions in their subclass and I will be having too many subclasses to define everything in the main entity class...Is there anyway to achieve this?
I have tried using [XmlType(TypeName="...")] for the subclasses and so on but that did not work.
[Serializable]
[XmlInclude(typeof(Subclass1))]
[XmlRoot("Entity")]
public class Entity{
[XmlArray("CausedBy")]
//[XmlArrayItem("Subclass1", typeof(subclass1))]
//[XmlArrayItem("Sublcass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
[XmlInclude(typeof(Subclass2))]
public class Subclass1:Entity{
//Code...
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2:Subclass1{
//Code...
}
Serializing the above code after creating an entity and adding a Subclass1 and Subclass2 to the list 'CausedBy' class results in the following:
<Entity>
<CausedBy>
<Entity ... xsi:type="SubClass1" />
<Entity ... xsi:type="SubClass2" />
</CausedBy>
<Entity>
I would like the output to show:
<Entity>
<CausedBy>
<SubClass1 .../>
<SubClass2 .../>
</CausedBy>
<Entity>
Since I totally failed to read the question to begin with, here's a new answer (it's a bit of a tl;dr, so you can always skip to the end and follow the link):
It isn't possible to get the built in serializer class to work because you don't wish to add the attributes that it needs to be able to operate. Your only option is to seralize the class yourself, however, this need not be as tedious as it sounds; I had a similar issue a few years ago with DataGridView in virtual mode and produced a generic virtualizer that could be used to virtualize the data for display; it used a custom attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class showColumnAttribute : System.Attribute
{
///<summary>Optional display format for column</summary>
public string Format;
///<summary>Optional Header string for column<para>Defaults to propety name</para></summary>
public string Title;
///<summary>Optional column edit flag - defaults to false</summary>
public bool ReadOnly;
///<summary>Optional column width</summary>
public int Width;
///<summary>
///Marks public properties that are to be displayed in columns
///</summary>
public showColumnAttribute()
{
Format = String.Empty;
Title = String.Empty;
ReadOnly = false;
Width = 0;
}
}
And a constructor:
///<summary>
///Extracts the properties of the supplied type that are to be displayed
///<para>The type must be a class or an InvalidOperationException will be thrown</para>
///</summary>
public Virtualiser(Type t)
{
if (!t.IsClass)
throw new InvalidOperationException("Supplied type is not a class");
List<VirtualColumnInfo> definedColumns = new List<VirtualColumnInfo>();
PropertyInfo[] ps = t.GetProperties();
MethodInfo mg, ms;
for (int i = 0; i < ps.Length; i++)
{
Object[] attr = ps[i].GetCustomAttributes(true);
if (attr.Length > 0)
{
foreach (var a in attr)
{
showColumnAttribute ca = a as showColumnAttribute;
if (ca != null)
{
mg = ps[i].GetGetMethod();
if (mg != null)
{
ms = ps[i].GetSetMethod();
definedColumns.Add
(
new VirtualColumnInfo
(
ps[i].Name, ca.Width, ca.ReadOnly, ca.Title == String.Empty ? ps[i].Name : ca.Title,
ca.Format, mg, ms
)
);
}
break;
}
}
}
}
if (definedColumns.Count > 0)
columns = definedColumns.ToArray();
}
This extracts the public properties of the class and supplies marked items to the DataGridView as columns together with a header, format, etc.
The effect of all of this (and the rest of the missing code) was that any type could be virtualized in a dataGridView simply by tagging public properties and calling the virtualizer once for a given type:
#region Virtualisation
static readonly Virtualiser Virtual = new Virtualiser(typeof(UserRecord));
[XmlIgnore] // just in case!
public static int ColumnCount { get { return Virtual.ColumnCount; } }
public static VirtualColumnInfo ColumnInfo(int column)
{
return Virtual.ColumnInfo(column);
}
public Object GetItem(int column)
{
return Virtual.GetItem(column, this);
}
/*
** The supplied item should be a string - it is up to this method to supply a valid value to the property
** setter (this is the simplest place to determine what this is and how it can be derived from a string).
*/
public void SetItem(int column, Object item)
{
String v = item as String;
int t = 0;
if (v == null)
return;
switch (Virtual.GetColumnPropertyName(column))
{
case "DisplayNumber":
if (!int.TryParse(v, out t))
t = 0;
item = t;
break;
}
try
{
Virtual.SetItem(column, this, item);
}
catch { }
}
#endregion
The number of columns, their properties and order can be specified automatically by creating a number of public properties derived from the class data:
#region Display columns
[showColumn(ReadOnly = true, Width = 100, Title = "Identification")]
public String DisplayIdent
{
get
{
return ident;
}
set
{
ident = value;
}
}
[showColumn(Width = 70, Title = "Number on Roll")]
public int DisplayNumber
{
get
{
return number;
}
set
{
number = value;
}
}
[showColumn(Width = -100, Title = "Name")]
public string DisplayName
{
get
{
return name == String.Empty ? "??" : name;
}
set
{
name = value;
}
}
#endregion
This would virtualize any class for dataGridView to display and edit data and I used it many times over the years and the extraction of properties to display is exactly what is required for XML serialization, indeed, it has a lot of the same characteristics.
I was going to adapt this method to do the same job for XML serialization but someone has already done it at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=474453, I hope you can make use of this method to solve your problem.
This works for me:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Entity entity = new Entity();
entity.CausedBy = new List<Entity>();
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass2());
entity.CausedBy.Add(new Subclass1());
entity.CausedBy.Add(new Subclass1());
entity.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test.txt"));
}
}
[Serializable]
[XmlRoot("Entity")]
public class Entity
{
[XmlArray("CausedBy")]
[XmlArrayItem("SubClass1", typeof(Subclass1))]
[XmlArrayItem("SubClass2", typeof(Subclass2))]
public List<Entity> CausedBy { get; set; }
}
[Serializable]
[XmlRoot("Subclass1")]
public class Subclass1 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToShortDateString();
public String SubClass1Item { get { return "Test1 " + t; } set { } }
}
[Serializable]
[XmlRoot("Subclass2")]
public class Subclass2 : Entity
{
[XmlIgnore]
String t = DateTime.Now.ToString();
public String SubClass2Item { get { return "Test2 " + t; } set { } }
}
It produces:
<Entity xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CausedBy>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass2>
<SubClass2Item>Test2 20/09/2017 01:06:55</SubClass2Item>
</SubClass2>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
<SubClass1>
<SubClass1Item>Test1 20/09/2017</SubClass1Item>
</SubClass1>
</CausedBy>
</Entity>

Recursive adding XML into TreeView

I'm trying to import an XML file of nodes into the same node structure in a TreeView using C#. I have found a lot of example that use a single node structure, but have had a lot of issues traversing the XML file and populating the TreeView with it. This is a sample of the XML file:
<?xml version="1.0"?>
<xmlRoot>
<ProductGroup>
<Group>
<GroupName>Soda</GroupName>
<Classifications>
<Classification>
<ClassificationName>Regular</ClassificationName>
<Containers>
<Container>
<ContainerType>Can</ContainerType>
<ContainerName>SmallCan</ContainerName>
</Container>
<Container>
<ContainerType>bottle</ContainerType>
<ContainerName>SmallBottle</ContainerName>
</Container>
</Containers>
</Classification>
<Classification>
<ClassificationName>Diet</ClassificationName>
<Containers>
<Container>
<ContainerType>Can</ContainerType>
<ContainerName>SmallCan</ContainerName>
</Container>
</Containers>
</Classification>
</Classifications>
</Group>
<Group>
<GroupName>Water</GroupName>
<Classifications>
<Classification>
<ClassificationName>Regular</ClassificationName>
<Containers>
<Container>
<ContainerType>Bottle</ContainerType>
<ContainerName>EcoBottle</ContainerName>
</Container>
</Containers>
</Classification>
</Classifications>
</Group>
</ProductGroup>
</xmlRoot>
I've tried using something like this:
treProducts.Nodes.Clear();
XDocument xdoc = XDocument.Load("ProductDocument.xml");
foreach (XElement groupElement in xdoc.Descendants("Group"))
{
treProducts.Nodes.Add(groupElement.Element("GroupName").Value);
treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("GroupName").Value];
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("ClassificationName").Value];
foreach (XElement ContainerElement in groupElement.Descendants("Container"))
{
treProducts.SelectedNode.Nodes.Add(ContainerElement.Element("ContainerName").Value);
}
}
}
I'm trying to get the tree to show:
Soda
Regular
SmallCan
SmallBottle
Diet
SmallCan
Water
Regular
EcoBottle
...but the tree is only showing Soda and it seems to skip the rest, except if I comment out the nested foreach statements, it will show Soda and Water.
There's something wrong with the syntax I'm using and I'm wondering if someone who understands Linq better can help see where the code is wrong.
You are using the wrong variable in your loop over the Classification Elements. Replace groupElement with ClassificationElement inside the loop.
Change:
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
// groupElement.Element("ClassificationName") is null:
treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
...
}
to
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
treProducts.SelectedNode.Nodes.Add(ClassificationElement.Element("ClassificationName").Value);
...
}
I suggest recursion
void AddNodes(XElement parentElement, TreeNode parent = null)
{
Queue<XElement> queue = new Queue<XElement>(parentElement.Elements());
while (queue.Count > 0)
{
TreeNode child = parent;
XElement element = queue.Dequeue();
if (!element.HasElements)
{
string value = element.Value;
element = (XElement)element.NextNode;
if (null != element && !element.HasElements)
value = element.Value;
if (null == parent)
treeView1.Nodes.Add(child = new TreeNode(value));
else
parent.Nodes.Add(child = new TreeNode(value));
child.Expand();
element = queue.Dequeue();
}
AddNodes(element, child);
}
}
AddNodes(XElement.Load("ProductDocument.xml"));
Note: dbc's answer is probably better if your XML structure is likely to change, but if you are given it as it currently stands, and it won't change - then this will slurp it right into the tree quickly, without a lot of overhead.
The reason this is complicated is that your tree node hierarchy does not correspond 1-1 to your XML hierarchy. In situations like this, I suggest introducing intermediate classes to provide a view into the base XML model data. In WPF these classes would be the View Model, but the windows forms TreeView doesn't support data binding. Despite this, the abstraction of a view model is useful here.
First, some basic view model interfaces and classes to tie together TreeNode and XElement hierarchies:
public interface ITreeNodeViewModel
{
string Name { get; }
string Text { get; }
object Tag { get; }
object Model { get; }
IEnumerable<ITreeNodeViewModel> Children { get; }
}
public abstract class TreeNodeViewModel<T> : ITreeNodeViewModel
{
readonly T model;
public TreeNodeViewModel(T model)
{
this.model = model;
}
public T Model { get { return model; } }
#region ITreeNodeProxy Members
public abstract string Name { get; }
public abstract string Text { get; }
public virtual object Tag { get { return this; } }
public abstract IEnumerable<ITreeNodeViewModel> Children { get; }
#endregion
#region ITreeNodeViewModel Members
object ITreeNodeViewModel.Model
{
get { return Model; }
}
#endregion
}
public abstract class XElementTreeNodeViewModel : TreeNodeViewModel<XElement>
{
public XElementTreeNodeViewModel(XElement node) : base(node) {
if (node == null)
throw new ArgumentNullException();
}
public XNamespace Namespace { get { return Model.Name.Namespace; } }
public override string Name
{
get { return Model.Name.ToString(); }
}
}
Next, a couple extension classes:
public static class TreeViewExtensions
{
public static void PopulateNodes(this TreeView treeView, IEnumerable<ITreeNodeViewModel> viewNodes)
{
treeView.BeginUpdate();
try
{
treeView.Nodes.PopulateNodes(viewNodes);
}
finally
{
treeView.EndUpdate();
}
}
public static void PopulateNodes(this TreeNodeCollection nodes, IEnumerable<ITreeNodeViewModel> viewNodes)
{
nodes.Clear();
if (viewNodes == null)
return;
foreach (var viewNode in viewNodes)
{
var name = viewNode.Name;
var text = viewNode.Text;
if (string.IsNullOrEmpty(text))
text = name;
var node = new TreeNode { Name = name, Text = text, Tag = viewNode.Tag };
nodes.Add(node);
PopulateNodes(node.Nodes, viewNode.Children);
node.Expand();
}
}
}
public static class XObjectExtensions
{
public static string TextValue(this XContainer node)
{
if (node == null)
return null;
//return string.Concat(node.Nodes().OfType<XText>().Select(tx => tx.Value)); c# 4.0
return node.Nodes().OfType<XText>().Select(tx => tx.Value).Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
}
public static IEnumerable<XElement> Elements(this IEnumerable<XElement> elements, XName name)
{
return elements.SelectMany(el => el.Elements(name));
}
}
Next, view models for the three levels of your tree:
class ContainerViewModel : XElementTreeNodeViewModel
{
public ContainerViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ContainerName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get { return Enumerable.Empty<ITreeNodeViewModel>(); }
}
}
class ClassificationViewModel : XElementTreeNodeViewModel
{
public ClassificationViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ClassificationName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get
{
return Model.Elements(Namespace + "Containers").Elements<XElement>(Namespace + "Container").Select(xn => (ITreeNodeViewModel)new ContainerViewModel(xn));
}
}
}
class GroupViewModel : XElementTreeNodeViewModel
{
public GroupViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "GroupName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get
{
return Model.Elements(Namespace + "Classifications").Elements<XElement>(Namespace + "Classification").Select(xn => (ITreeNodeViewModel)new ClassificationViewModel(xn));
}
}
}
Now, building your tree becomes quite simple:
var xdoc = XDocument.Load("ProductDocument.xml");
var ns = xdoc.Root.Name.Namespace;
treeView1.PopulateNodes(xdoc.Root.Elements(ns + "ProductGroup").Elements(ns + "Group").Select(xn => (ITreeNodeViewModel)new GroupViewModel(xn)));
And the result:
Later, if you wish to add editing functionality to your tree, you can add the appropriate methods to ITreeNodeViewModel -- for instance, a setter method for Text. Since the ITreeNodeViewModel has saved itself in the TreeNode.Tag the appropriate methods will be available.

Is it possible to xml-serialize a property (in a xml serialized class) that returns a list of string objects?

I have a class, lets call it, Employee, that has a list of qualifications as property
[XmlRoot("Employee")]
public class Employee
{
private string name;
private IListManager<string> qualification = new ListManager<string>();
public Employee()
{
}
[XmlElement("Name")]
public string Name
{
get
{
return name;
}
set
{
if (value != null)
{
name = value;
}
}
}
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
}
[XmlElement("Qual")]
public string Qualifications
{
get
{
return ReturnQualifications();
}
}
private string ReturnQualifications()
{
string qual = string.Empty;
for (int i = 0; i < qualification.Count; i++)
{
qual += " " + qualification.GetElement(i);
}
return qual;
}
public override String ToString()
{
String infFormat = String.Format("{0, 1} {1, 15}", this.name, Qualifications);
return infFormat;
}
}
}
Then I have a method that serializes the above class tom XML by taking a list of Employees as patameter, the method is generic:
public static void Serialize<T>(string path, T typeOf)
{
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
TextWriter t = new StreamWriter(path);
try
{
xmlSer.Serialize(t, typeOf);
}
catch
{
throw;
}
finally
{
if (t != null)
{
t.Close();
}
}
}
This is how I call the method XMLSerialize:
controller.XMLSerialize<Employee>(opnXMLFileDialog.FileName, employeeList);
The result of the method execution returns a file and is shown below:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEmployees xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employees>
<Name>g</Name>
</Employees>
</ArrayOfEmployees>
as you can see there is only the property name included in the file. How do I proceed from here and serialize the list of qualifications too? Any suggestionswill be greatly appreciated.
Thanks in advance.
In order to serialize a property, XmlSerializer requires that the property have both a public setter and public getter. So whatever property you choose to serialize your qualifications must have a setter as well as a getter.
The obvious solution would be for your QualificationList to have a setter as well as a getter:
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
set
{
qualification = value;
}
}
If for some reason this cannot be done -- perhaps because your ListManager<string> class is not serializable -- you could serialize and deserialize it as a proxy array of strings, like so:
[XmlIgnore]
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
}
[XmlElement("Qual")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string[] QualificationArray
{
get
{
return Enumerable.Range(0, qualification.Count).Select(i => qualification.GetElement(i)).ToArray();
}
set
{
// Here I am assuming your ListManager<string> class has Clear() and Add() methods.
qualification.Clear();
if (value != null)
foreach (var str in value)
{
qualification.Add(str);
}
}
}
Then for the following employee list:
var employee = new Employee() { Name = "Mnemonics" };
employee.QualificationList.Add("posts on stackoverflow");
employee.QualificationList.Add("easy to remember");
employee.QualificationList.Add("hard to spell");
var list = new List<Employee>();
list.Add(employee);
The following XML is generated:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfEmployee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employee>
<Name>Mnemonics</Name>
<Qual>posts on stackoverflow</Qual>
<Qual>easy to remember</Qual>
<Qual>hard to spell</Qual>
</Employee>
</ArrayOfEmployee>
Is that satisfactory?

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