Choose one of two system attributes - c#

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

Related

XML parsing nested objects with attributes for Unity dialogues system purpose

I'm trying to build a basic dialogue system to use in Unity starting from a XML document that look like this:
<?xml version="1.0" encoding="utf-8"?>
<speech id="1">
<bubble id="1" isQuestion="no">
Hi!
</bubble>
<bubble id="2" isQuestion="no">
It's been a while!
</bubble>
<bubble id="3" isQuestion="no">
Have a look at my wares!
</bubble>
<bubble id="4" isQuestion="yes">
Do you want to trade?
<option id="1"> true </option>
<option id="2"> false </option>
</bubble>
<bubble id="5" isQuestion="no">
Goodbye!
</bubble>
</speech>
<speech id="2">
...
</speech>
The concept here is to store each line in a "bubble" node with the id attributes to locate the node in the speech and a Boolean variable to know if the bubble ask a question.
To read that I've tried something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
public class DialogueManager
{
public List<Speech> LoadSpeechs(XmlDocument doc)
{
doc.Load();
List<Bouble> Boubles = new List<Bouble>();
List<Bouble> Speechs = new List<Speech>();
foreach (xmlNode node in doc.DocumentElement)
{
int id = node.Attributes[0].Value;
foreach (xmlNode node in doc.DocumentElement)
{
int id = node.Attributes[0].Value;
bool isQuestion = node.Attributes[1].Value;
string content = string.Parse(node["bouble"].InnerText);
bouble = new Bouble(id, isQuestion, value);
Boubles.Add(bouble);
}
speech = new Speech(id, Boubles);
Speechs.Add(speech);
}
return Speechs;
}
public class Speech
{
public int SpeechID { get; set; }
public Speech(int m_speechID, List<Bouble> bobules)
{
SpeechID = m_speechID;
Boubles = bobules;
}
}
public class Bouble
{
public bool IsQuesion { get; set; }
public int NodeID { get; set; }
public string Content { get; set; }
public Bouble(int m_nodeID, bool m_isQuestion, string m_value)
{
NodeID = m_nodeID;
IsQuesion = m_isQuestion;
Content = m_value;
}
}
}
The problem is that I get a tons of error, which are difficult to understand alone.
So here I am. I should mention that I'm not trying to realize something in particular just learning the paradigm and Unity so I prefer some comment and explanation on how this work and where I'm mistaken rather than other ways around, but all reply will be welcome :)
I'm planning on adding more attributes, but for now I'd like to figure how to make this work properly.
Based on my test, I find the following problem and you could make some changes.
First, we should have the root node when we want to read the xml files.
Here is my tested xml:
Second, we need to use type convert if we want to convert one type to another type.
Such as the following code:
int sid = Convert.ToInt32(node.Attributes[0].Value);
string content = Lnode.InnerText;(InnerText will return a string type, there is no need to convert again)
Third, we could use the following code convert "yes" or "no" to type bool.
text = Lnode.Attributes[1].Value.ToString();
if(text=="yes")
{
isquestion = true;
}
else
{
isquestion = false;
}
Fourth, we need to put the Speechs.Add(speech); to outside the inner loop.
Finally, you could refer to the following completed code example to convert xml to list.
Code:
public class DialogueManager
{
public List<Speech> LoadSpeechs(XmlDocument doc)
{
doc.Load("test.xml");
List<Bouble> Boubles = new List<Bouble>();
List<Speech> Speechs = new List<Speech>();
string text = string.Empty;
bool isquestion = false;
Speech speech = null;
foreach (XmlNode node in doc.DocumentElement)
{
if (node.Name == "speech")
{
int sid = Convert.ToInt32(node.Attributes[0].Value);
foreach (XmlNode Lnode in node.ChildNodes)
{
int bid = Convert.ToInt32(Lnode.Attributes[0].Value);
text = Lnode.Attributes[1].Value.ToString();
if(text=="yes")
{
isquestion = true;
}
else
{
isquestion = false;
}
string content = Lnode.InnerText;
Bouble bouble = new Bouble(bid, isquestion, content);
Boubles.Add(bouble);
}
speech = new Speech(sid, Boubles);
}
Speechs.Add(speech);
}
return Speechs;
}
public class Speech
{
public int SpeechID { get; set; }
public List<Bouble> Boubles { get; set; }
public Speech(int m_speechID, List<Bouble> bobules)
{
SpeechID = m_speechID;
Boubles = bobules;
}
}
public class Bouble
{
public bool IsQuesion { get; set; }
public int NodeID { get; set; }
public string Content { get; set; }
public Bouble(int m_nodeID, bool m_isQuestion, string m_value)
{
NodeID = m_nodeID;
IsQuesion = m_isQuestion;
Content = m_value;
}
}
}
Tested code and result:(I tested in console app)
static void Main(string[] args)
{
DialogueManager manager = new DialogueManager();
XmlDocument doc = new XmlDocument();
var list=manager.LoadSpeechs(doc);
foreach (DialogueManager.Speech speech in list)
{
Console.WriteLine(speech.SpeechID);
foreach (DialogueManager.Bouble bouble in speech.Boubles)
{
Console.WriteLine(bouble.NodeID);
Console.WriteLine(bouble.IsQuesion);
Console.WriteLine(bouble.Content);
}
}
Console.ReadKey();
}
First of all there is one issue with your XML file: You need a root element in a form like e.g.
<?xml version="1.0"?>
<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<speech>
...
</speech>
<speech>
...
</speech>
</Root>
Then instead of parsing this all "manually" you could/should use XMLSerializer.
This allows you to completely flexible add more elements and attributes without having to change the parser method all the time. You rather already define the entire XML scheme structure directly in the class definitions via attributes
and do something like e.g.
// In general make your classes Serializable and use fields instead of properties
// This way you can also see them in the Unity Inspector
// See https://docs.unity3d.com/Manual/script-Serialization.html
[Serializable]
// That generates/interprets the general XML root element
[XmlRoot]
public class Root
{
// By matching this element name with the root name of the Speech class (see below)
// This is treated as array without the need for an additional wrapper tag
[XmlElement(ElementName = "speech")] public List<Speech> speeches = new List<Speech>();
}
[Serializable]
// By using the same ElementName as root for this
// It will not read/write an additional array wrapper tag but rather direct children of the Root
[XmlRoot(ElementName = "speech")]
public class Speech
{
// Makes this an attribute without the speech tag
// NOTE: I think you wouldn't really need these
// They are elements in an array so you could rely on the index itself
[XmlAttribute] public int id;
[XmlElement("bubble")] public List<Bubble> bubbles = new List<Bubble>();
}
[Serializable]
[XmlRoot(ElementName = "bubble")]
public class Bubble
{
[XmlAttribute] public int id;
// This treats the label as the actual text between the tags
[XmlText] public string label;
// NOTE: Here I thought your bool is quite redundant, you don't need it
// simply check if there are options elements -> if so it is automatically a question
// if there are no options anyway -> there is no question
public bool isQuestion => options.Count != 0;
// If there are none in your XML file the list will simply stay empty
[XmlElement(ElementName = "option")] public List<Option> options = new List<Option>();
}
[Serializable]
[XmlRoot(ElementName = "option")]
public class Option
{
[XmlAttribute] public int id;
[XmlText] public string label;
// Optionally you could use some return value like a bool or enum
// but again, you can also simply go by index
}
This requires your XML file be slightly changed and look like e.g.
<?xml version="1.0"?>
<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<speech id="1">
<bubble id="1">Hallo</bubble>
<bubble id="2">World?
<option id="1">Yes</option>
<option id="2">Nope</option>
</bubble>
</speech>
<speech id="2">
<bubble id="1">Hi</bubble>
<bubble id="2">There!</bubble>
</speech>
</Root>
Then finally you can simply have two methods like e.g.
public class Example : MonoBehaviour
{
[Header("Input")]
[Tooltip("The FULL path to your file - for the example below I cheated ;) ")]
public string xmlFileUrl = Path.Combine(Application.streamingAssetsPath, "Example.xml");
[Header("Result")]
[Tooltip("The deserialized c# classes")]
public Root root;
// This allows you to call this method via the Inspector Context Menu
[ContextMenu(nameof(LoadFile))]
public void LoadFile()
{
// Open the file as a stream
using (var stream = File.Open(xmlFileUrl, FileMode.Open, FileAccess.Read, FileShare.Read))
{
// create an XMLSerializer according to the root type
var serializer = new XmlSerializer(typeof(Root));
// Deserialize the file according to your implemented Root class structure
root = (Root) serializer.Deserialize(stream);
}
}
[ContextMenu(nameof(WriteFile))]
public void WriteFile()
{
// Delete the existing file
if (File.Exists(xmlFileUrl)) File.Delete(xmlFileUrl);
// create the StreamingAsset folder if not exists
// NOTE: Later in your app you want to use the StreamingAssets only
// if your file shall be read-only!
// otherwise use persistentDataPath
if(!Directory.Exists(Application.streamingAssetsPath)) Directory.CreateDirectory(Application.streamingAssetsPath);
// Create a new file as stream
using (var stream = File.Open(xmlFileUrl, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
var serializer = new XmlSerializer(typeof(Root));
// serialize the current Root class into the XML file
serializer.Serialize(stream, root);
}
#if UNITY_EDITOR
// in the editor refresh the AssetDataBase of Unity
// so you see the added files in the Project View
UnityEditor.AssetDatabase.Refresh();
#endif
}
}
And now you have access to the root and all its properties directly like e.g.
var isQuestion = root.speeches[0].bubbles[1].isQuestion;
and you can do the entire preparation and editing also directly via the Inspector in Unity.
Then as a final little personal touch I would rather use
[Serializable]
[XmlRoot]
public class Root
{
[XmlElement(ElementName = nameof(Speech))] public List<Speech> speeches = new List<Speech>();
}
[Serializable]
[XmlRoot(ElementName = nameof(Speech))]
public class Speech
{
...
[XmlElement(nameof(Bubble))] public List<Bubble> bubbles = new List<Bubble>();
}
[Serializable]
[XmlRoot(ElementName = nameof(Bubble))]
public class Bubble
{
...
[XmlElement(ElementName = nameof(Option))] public List<Option> options = new List<Option>();
}
[Serializable]
[XmlRoot(ElementName = nameof(Option))]
public class Option
{
...
}
and in the XML use the uppercase class names
<?xml version="1.0"?>
<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Speech id="1">
<Bubble id="1">Hallo</Bubble>
<Bubble id="2">World?
<Option id="1">Yes</Option>
<Option id="2">Nope</Option>
</Bubble>
</Speech>
<Speech id="2">
<Bubble id="1">Hi</Bubble>
<Bubble id="2">There!</Bubble>
</Speech>
</Root>

how to remove default namespaces and add custom namespace in root tag of xml using C#?

While creating xml from C# class I getting some default namespaces(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema") in root tag (Order) like below. but, I want to remove those default namespaces and I need the following namespace in the root tag (Order xmlns="http://example.com/xml/1.0").
how to remove those default namespaces and replace in c# code. thanks in advance.
<?xml version="1.0"?>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Number Type="mobile">9999999999</Number>
<TrackStartDateTime>2015-05-30 11:00 ET</TrackStartDateTime>
<Notifications>
<Notification>
<PartnerMPID>99999999</PartnerMPID>
<IDNumber>L563645</IDNumber>
<TrackDurationInHours>120</TrackDurationInHours>
<TrackIntervalInMinutes>240</TrackIntervalInMinutes>
</Notification>
</Notifications>
<Carrier>
<Dispatcher>
<DispatcherName>?</DispatcherName>
<DispatcherPhone>0</DispatcherPhone>
<DispatcherEmail>?</DispatcherEmail>
</Dispatcher>
</Carrier>
</Order>
I have used following C# classes.
[XmlRoot("Order")]
public class Order
{
[XmlElement("Number")]
public Number Number;
[XmlElement("TrackStartDateTime")]
public string TrackStartDateTime;//TODO - need to check
[XmlElement("Notifications")]
public Notifications Notifications;//TODO - notification tag should come inside Notifications tag
[XmlElement("Carrier")]
public Carrier Carrier;
public Order() {
Number = new Number();
Notifications = new Notifications();
Carrier = new Carrier();
TripSheet = new TripSheet();
}
}
public class Number
{
[XmlAttribute("Type")]
public string Type;
[XmlText]
public Int64 Value;
}
public class Notifications {
[XmlElement("Notification")]
public List<Notification> Notification;
public Notifications() {
Notification = new List<Notification>();
}
}
public class Notification
{
[XmlElement("PartnerMPID")]
public string PartnerMPID { get; set; }
[XmlElement("IDNumber")]
public string IDNumber { get; set; }
[XmlElement("TrackDurationInHours")]
public int TrackDurationInHours { get; set; }
[XmlElement("TrackIntervalInMinutes")]
public int TrackIntervalInMinutes { get; set; }
}
public class Carrier
{
[XmlElement("Name")]
public string Name;
[XmlElement("Dispatcher")]
public Dispatcher Dispatcher;
public Carrier() {
Dispatcher = new Dispatcher();
}
}
public class Dispatcher
{
[XmlElement("DispatcherName")]
public string DispatcherName;
[XmlElement("DispatcherPhone")]
public Int64 DispatcherPhone;
[XmlElement("DispatcherEmail")]
public string DispatcherEmail;//conform format for email
}
and I have taken the new instance of Order Class and for testing purpose, I have hard-coded values for the each fields and I have used the following code for the creating xml from the C# class.
public string CreateXML(Order order)
{
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Order));
// Creates a stream whose backing store is memory.
using (MemoryStream xmlStream = new MemoryStream())
{
xmlSerializer.Serialize(xmlStream, order);//,ns
xmlStream.Position = 0;
//Loads the XML document from the specified string.
xmlDoc.Load(xmlStream);
return xmlDoc.InnerXml;
}
}
I am not sure its a right approach for creating xml from C# classes. Please guide me to get the following xml output from c# class.
<?xml version="1.0"?>
<Order xmlns="http://example.com/xml/1.0" >
<Number Type="mobile">9999999999</Number>
<TrackStartDateTime>2015-05-30 11:00 ET</TrackStartDateTime>
<Notifications>
<Notification>
<PartnerMPID>99999999</PartnerMPID>
<IDNumber>L563645</IDNumber>
<TrackDurationInHours>120</TrackDurationInHours>
<TrackIntervalInMinutes>240</TrackIntervalInMinutes>
</Notification>
</Notifications>
<Carrier>
<Dispatcher>
<DispatcherName>?</DispatcherName>
<DispatcherPhone>0</DispatcherPhone>
<DispatcherEmail>?</DispatcherEmail>
</Dispatcher>
</Carrier>
</Order>
Here is a way to do it...
Just create a new XDocument and set the namespace that you want on it and transplant the original xml descendant into it, like so:
var xml = "<?xml version=\"1.0\"?><Order xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><Number Type=\"mobile\">9999999999</Number></Order>";
var xdoc = XDocument.Parse(xml);
var ns = XNamespace.Get("http://example.com/xml/1.0");
var xdoc2 = new XDocument(new XDeclaration("1.0", null, null),
new XElement(ns + "Order", xdoc.Root.Descendants()));
See here for working sample: https://dotnetfiddle.net/JYCL95
For this case it should be sufficient to simply add a null namespace to the XMLRoot decoration used on your class definition.
[XmlRoot("Order", Namespace = null)]
public class Order
{
[XmlElement("Number")]
public Number Number;
[XmlElement("TrackStartDateTime")]
public string TrackStartDateTime;
[XmlElement("Notifications")]
public Notifications Notifications;
[XmlElement("Carrier")]
public Carrier Carrier;
The serializer should do the rest.

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?

Building XML using Linq from a List<CustomClass>

I'm just trying to understand Linq and I am trying to do something that seems very simple, but I can't get it to output the way I would like. I have been stuck on this for days trying various different methods I just can't get it right.
So I have a class EarObs, it has members: eventID, icaoId, frm, sta, db.
I'm trying to build an XML document from a List. I want the XML document to look like so:
<EarObs EventId = "123456789">
<icao icaoID = "0001">
<frm frm = "01">
<sta sta = "00">
<db>87</db>
<hz>99</hz>
</sta>
<sta station = "01">
<db>79</db>
<hz>99</hz>
</sta>
</frm>
<frm frm = "02">
................
</frm>
</icao>
</EarObs>
And this would continue all the way down keeping the same order if there was more than one frame or more than one code etc.
So this is what I have been trying most recently but it still does not output they way I would like, Obs get repeated and I do not know where I am going wrong.
string eventGUID = "eventGUID";
List<EarObs> frameObsList = new List<EarObs>();
for (int frm = 2; frm > 0; frm--)
{
for (int sta = 5; sta > 0; sta--)
{
frameObsList.Add(new EarObs("KAPF", eventGUID, frm, sta, 85 + sta, 99 + sta));
cnt++;
}
}
String eventID = obsList.First().EventGUID;
List<EarObs> distinctApts =
obsList
.GroupBy(p => p.IcaoId)
.Select(g => g.First())
.ToList();
XElement xElement = new XElement("EarObs", new XAttribute("eventID", eventID),
from ea in distinctApts
orderby ea.IcaoId
select new XElement("icao", new XAttribute("code", ea.IcaoId),
from eb in obsList
where ea.IcaoId == eb.IcaoId
orderby eb.Frm
select new XElement("frm", new XAttribute("frm", eb.Frm),
from ec in obsList
where eb.Frm == ec.Frm
orderby ec.Sta
select new XElement("sta", new XAttribute("sta", ec.Sta),
new XElement("db", ec.Db),
new XElement("hz", ec.Hz)))));
Using this code I get an xml document that repeats the frame once for each station. This is not correct. I feel like this is easily done sequentially, but I'm trying to learn and this seems just so simple that I should be able to do it in Linq. I need each element in the List to only be represented in the XML document once. How do I go about this?
I would also like to expand it so that it can handle multiple eventId's as well, but that is not as important as getting the XML structure right. Any help would be much appreciated, I haven't been able to find too many example of creating an XML including the filtering of the elements using linq, most examples seem to have the List all ready structured before they create the XML.
Since you have a custom class, EarObs why not define Xml attributes to your object and serialize the object using the XmlSerlizer class? This way, you can continue use Linq on your objects, and also output your objects.
e.g. Below is a team, with players on it.
[XmlRoot("root")]
public class Team
{
private List<Player> players = new List<Player>();
[XmlElement("player")]
public List<Player> Players { get { return this.players; } set { this.players = value; } }
// serializer requires a parameterless constructor class
public Team() { }
}
public class Player
{
private List<int> verticalLeaps = new List<int>();
[XmlElement]
public string FirstName { get; set; }
[XmlElement]
public string LastName { get; set; }
[XmlElement]
public List<int> vertLeap { get { return this.verticalLeaps; } set { this.verticalLeaps = value; } }
// serializer requires a parameterless constructor class
public Player() { }
}
Once I create a team, with some players on it, I just have to do:
Team myTeamData = new Team();
// add some players on it.
XmlSerializer deserializer = new XmlSerializer(typeof(Team));
using (TextReader textReader = new StreamReader(#"C:\temp\temp.txt"))
{
myTeamData = (Team)deserializer.Deserialize(textReader);
textReader.Close();
}
The output will look like this:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<player>
<FirstName>dwight</FirstName>
<LastName>howard</LastName>
<vertLeap>1</vertLeap>
<vertLeap>2</vertLeap>
<vertLeap>3</vertLeap>
</player>
<player>
<FirstName>dwight</FirstName>
<LastName>howard</LastName>
<vertLeap>1</vertLeap>
</player>
</root>
The easiest way is to create a set of classes to handle the serialization like so;
public class sta
{
public int db { get; set; }
public int hz { get; set; }
[XmlAttribute()]
public string station { get; set; }
}
public class frm
{
[XmlAttribute("frm")]
public string frmID { get; set; }
[XmlElement("sta")]
public List<sta> stas { get; set; }
}
public class icao
{
[XmlAttribute]
public string icaoID { get; set; }
[XmlElement("frm")]
public List<frm> frms { get; set; }
}
public class EarObs
{
[XmlAttribute]
public string EventId { get; set; }
[XmlElement("icao")]
public List<icao> icaos { get; set; }
}
and you can use the xml serializer to serialize/deserialize. The following serializes to the structure identical to what you have;
XmlSerializer serializer = new XmlSerializer(typeof(EarObs));
EarObs obs = new EarObs() { EventId = "123456789" };
obs.icaos = new List<icao>();
obs.icaos.Add(new icao() { icaoID = "0001" });
obs.icaos[0].frms = new List<frm>();
obs.icaos[0].frms.Add(new frm() { frmID = "01" });
obs.icaos[0].frms[0].stas = new List<sta>();
obs.icaos[0].frms[0].stas.Add(new sta() { station = "00", db = 87, hz = 99 });
obs.icaos[0].frms[0].stas.Add(new sta() { station = "01", db = 79, hz = 99 });
obs.icaos[0].frms.Add(new frm() { frmID = "02" });
using (StringWriter s = new StringWriter())
{
serializer.Serialize(s, obs);
string test = s.ToString();
}
Outputs;
<?xml version="1.0" encoding="utf-16"?>
<EarObs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" EventId="123456789">
<icao icaoID="0001">
<frm frm="01">
<sta station="00">
<db>87</db>
<hz>99</hz>
</sta>
<sta station="01">
<db>79</db>
<hz>99</hz>
</sta>
</frm>
<frm frm="02" />
</icao>
</EarObs>
Now, while this seems like a lot of trouble to go to, it's possible to use the xsd.exe tool (comes with the framework I believe), to automatically create a set of classes that match any given xml file, although it does use an intermediary xsd file (painless though). You can find out how here; How to generate .NET 4.0 classes from xsd?

Custom XML serialization with inheritance

I have the following class structure which I want to serialize:
[XmlRoot("SomeClass")]
public class SomeClass
{
private BaseClass[] _itemArray;
public BaseClass[] ItemArray
{
get { return _itemArray; }
set { _itemArray = value; }
}
public PPSPStatReportMessage()
: base()
{
}
}
public class SomeOtherClass
{
private int _property5;
[XmlAttribute("Property5")]
public int Property5
{
get { return _property5; }
set { _property5 = value; }
}
private string _property6;
[XmlText()]
public string Property6
{
get { return _property6; }
set { _property6 = value; }
}
public SomeOtherClass()
{
}
}
[XmlInclude(typeof(DerivedClass1))]
[XmlInclude(typeof(DerivedClass2))]
[XmlRoot("BaseClass")]
[XmlType("")]
public class BaseClass
{
private string _property1;
[XmlAttribute("Property1")]
public string Property1
{
get { return _property1; }
set { _property1 = value; }
}
public SomeClass(string PropertyVal)
{
_property1 = PropertyVal;
}
}
[XmlRoot("BaseClass")]
[XmlTypeAttribute(Namespace = "")]
public class DerivedClass1 : BaseClass
{
private string _property2;
[XmlAttribute("Property2")]
public string Property2
{
get { return _property2; }
set { _property2 = value; }
}
private SomeOtherClass _property3;
[XmlElement("SomeOtherClass")]
public SomeOtherClass Property3
{
get { return _property3; }
set { _property3 = value; }
}
public DerivedClass()
: base("PropertyVal1")
{
}
}
[XmlRoot("BaseClass", Namespace = "")]
[XmlType("")]
public class DerivedClass2 : BaseClass
{
private Int64 _property4;
[XmlAttribute("Property4")]
public Int64 Property4
{
get { return _property4; }
set { _property4 = value; }
}
public DerivedClass2()
: base("PropertyVal2")
{
}
}
And this is the method I use to serialize SomeClass:
public static string SerializeXML(object Value, System.Type ObjectType)
{
XmlSerializer serializer = new XmlSerializer(ObjectType);
XmlSerializerNamespaces namespaceSerializer = new XmlSerializerNamespaces();
namespaceSerializer.Add("", "");
StringWriter ms = new StringWriter();
serializer.Serialize(ms, Value, namespaceSerializer);
return ms.ToString();
}
This method generates an XML structure that looks like the following:
<?xml version="1.0" encoding="utf-16"?>
<SomeClass>
<ItemArray>
<BaseClass d3p1:type="DerivedClass1" Property1="PropertyVal1" Property2="123" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">
<SomeOtherClass Property5="0" >STRING DATA</SomeOtherClass>
</BaseClass>
<BaseClass d3p1:type="DerivedClass2" Property="PropertyVal2" Property4="456" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
</ItemArray>
</SomeClass>
However, I need to omit the d3p1:type and xmlns:d3p1 attributes and generate an XML structure that looks like this:
<?xml version="1.0" encoding="utf-16"?>
<SomeClass>
<ItemArray>
<BaseClass Property1="PropertyVal1" Property2="123">
<SomeOtherClass Property5="0" >STRING DATA</SomeOtherClass>
</BaseClass>
<BaseClass Property="PropertyVal2" Property4="456" />
</ItemArray>
</SomeClass>
As you can see in the code, I've tried to use XmlType and XmlTypeAttribute, but without success.
Any advice on how can I generate the XML structure as described above (without d3p1:type and xmlns:d3p1 attributes)?
Do you need the subclass elements to be called "BaseClass" - or would the name of the derived class do?
I am serializing similar subclass structures, and also wanted to get rid of the "d3p1:type" and "xmlns:d3p1" tags - instead replacing the "BaseClass" tags with derived class tags. So it is possible to generate xml for your example:
<ItemArray>
<DerivedClass1 Property1="PropertyVal1" Property2="123">
....
</DerivedClass1>
You're using an XmlInclude attribute on your BaseClass to let the serliazer know which derived classes to expect. Instead, you can tell the serializer about the expected subtypes on each element:
public class SomeClass
{
private BaseClass[] _itemArray;
[XmlElement(typeof(DerivedClass1))]
[XmlElement(typeof(DerivedClass2))]
public BaseClass[] ItemArray
{
get { return _itemArray; }
set { _itemArray = value; }
}
Eliminating the "type" attributes. Deserialiazation will also work fine.
You cannot accomplish what you want directly.
If the type being serialized isn't the exact type contained, .NET has to track what the type really is to allow proper deserialization.
To accomplish what you want - use RegEx.Replace() or other post-processing to replace the namespaces.
(e.g. serializing Object which is really a String will cause this problem).
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(file, settings))
{
XmlSerializer serializer = new XmlSerializer(source.GetType());
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
// ...
}

Categories

Resources