Making a class to keep track of related strings - c#

It is kind of funny, With the follwing class I know how my output should like but I could not figure it out how to hold the data for it.
Please see the code below:
public class QuickFailureReportText
{
public string[] device { get; set; }
public string[] group { get; set; }
public string[] pin { get; set; }
public override string ToString()
{
TextWriter tw;
StringBuilder sb = new StringBuilder();
tw = new StringWriter(sb);
tw.WriteLine("Quick Failure Report");
foreach (string dev in device)
{
tw.WriteLine("Failures in " + dev);
foreach (string grp in group)
{
tw.Write("Group " + grp + " : ");
foreach(string p in pin)
{
tw.Write(p + ", ");
}
tw.WriteLine(); //new line
}
tw.WriteLine(); //new line
}
return tw.ToString();
}
}
So what I want to do, is I want be able to somohe relate the three different string "device, group, pin" somhow that a PIN belongs to a GROUP and a GROUP belongs to a DEVICE. how that can be possible?
Please let me know if I am not clear enough.
UPDATE
Ok, I have a XML file that I can read data from it with no problem. the xml file looks like something like this:
<?xml version="1.0" encoding="utf-8"?>
<DEVICES>
<device>
<name>device 1</name>
<groups>
<group>
<group_name>group 1</group_name>
<pins>
<pin result="fail">A1</pin>
<pin result="pass">A2</pin>
</pins>
</group>
<group>
<group_name>group 2</group_name>
<pins>
<pin result="fail">B1</pin>
<pin result="pass">B2</pin>
</pins>
</group>
</groups>
</device>
</DEVICES>
So I want to gather the data from this XML(which may have a lot of devices) and using the class I wrote above, filter the failed pins.

Something like that?
public class Device
{
public string Name;
public List<Group> Groups = new List<Group>();
}
public class Group
{
public string Name;
public List<Pin> Pins = new List<Pin>();
}
public class Pin
{
public string Name;
public string Result;
}

I've written some code you can use to read required information from xml file, store in devices variable
public class Device
{
public string Name;
public Dictionary<string, Group> Groups = new Dictionary<string, Group>();
}
public class Group
{
public string Name;
public List<string> Pins = new List<string>();
}
public class QuickFailureReportText
{
public Dictionary<string, Device> devices = new Dictionary<string, Device>();
public void AddLog(string deviceName, string groupName, string pin)
{
if (!devices.ContainsKey(deviceName))
devices.Add(deviceName, new Device()
{ Name = deviceName, Groups = new Dictionary<string, Group>() });
if (!devices[deviceName].Groups.ContainsKey(groupName))
devices[deviceName].Groups.Add(groupName, new Group()
{ Name = groupName, Pins = new List<string>() });
devices[deviceName].Groups[groupName].Pins.Add(pin);
}
public override string ToString()
{
TextWriter tw;
StringBuilder sb = new StringBuilder();
tw = new StringWriter(sb);
tw.WriteLine("Quick Failure Report");
XDocument xDoc = XDocument.Load(#"devices.xml");
foreach (XElement device in xDoc.XPathSelectElements("DEVICES/device"))
{
foreach (XElement group in device.XPathSelectElements("groups/group"))
{
foreach (XElement pin in group.XPathSelectElements("pins/pin"))
{
if (pin.Attribute("result").Value == "fail")
{
AddLog(device.XPathSelectElement("name").Value,
group.XPathSelectElement("group_name").Value, pin.Value);
}
}
}
}
foreach (var device in devices.Values)
{
tw.WriteLine("Failures in " + device.Name);
foreach (var grp in device.Groups.Values)
{
tw.Write("Group " + grp.Name + " : ");
foreach (string p in grp.Pins)
{
tw.Write(p + ", ");
}
tw.WriteLine(); //new line
}
tw.WriteLine(); //new line
}
return tw.ToString();
}
}
class Program
{
static void Main(string[] args)
{
string s = new QuickFailureReportText().ToString();
}
}
Below is value of 's' string for your example file:
Quick Failure Report
Failures in device 1
Group group 1 : A1,
Group group 2 : B1,

I would implement an object data model, using three classes:
DEVICE HAS GROUP
GROUP HAS PIN
UPDATE
Class 1: DEVICE, with member field list_of_groups (you can use a different name)
Class 2: GROUP, with member field list_of_pins
Class 3: PIN, with member field result (boolean)

I think it's better if you change the xml in this way:
<?xml version="1.0" encoding="utf-8"?>
<DEVICES>
<device>
<name>device 1</name>
<groups>
<group>
<group_name>group 1</group_name>
<pins>
<pin result="fail">A1</pin>
<pin result="pass">A2</pin>
</pins>
</group>
<group>
<group_name>group 2</group_name>
<pins>
<pin result="fail">B1</pin>
<pin result="pass">B2</pin>
</pins>
</group>
</groups>
</device>
</DEVICES>
So you can define the object Device tha contains a List of Group that contains a list of object Pin.

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>

Convert XML to classes (deserialize it) and save it into database

Hi i have more than 20 links of xml files. I searched a lot to convert my xml to c# and then deserialize it according to classes and then put it into the database but not found a good solution for that. Please help me how to do it.
http://kithnyc.com/sitemap_products_1.xml?from=60594372&to=9586327751
This is the link of database and some xml is below
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
<url>
<loc>http://kithnyc.com/</loc>
<changefreq>daily</changefreq>
</url>
<url>
<loc>
http://kithnyc.com/products/adidas-originals-nmd-city-sock-black-blue
</loc>
<lastmod>2016-09-12T11:04:04-04:00</lastmod>
<changefreq>daily</changefreq>
<image:image>
<image:loc>...</image:loc>
<image:title>adidas Originals NMD City Sock - Black / Blue</image:title>
</image:image>
</url>
<url>
<loc>
http://kithnyc.com/products/kith-logo-mousepad-white
</loc>
<lastmod>2016-12-23T00:01:41-05:00</lastmod>
<changefreq>daily</changefreq>
<image:image>
<image:loc>
https://cdn.shopify.com/s/files/1/0094/2252/products/20150810-_MG_2963.jpg?v=1482353363
</image:loc>
<image:title>Kith Logo Mousepad - White</image:title>
</image:image>
</url>
</urlset>
This is the xml i want to put all its nodes in the database like "Loc" , "lastmod" and "changefreq" etc. Please help me how to convert it into classes and then how to deserialize it. Thanks
Hi you can do it like this:
Step 1: Read the file by the below line
XDocument xdoc = XDocument.Load(filebyte);
Step 2: The get all node of xml by Descendants method and convert it into list.
if need apply where clause to filter that data.
var pNodelist = xdoc.Root.Descendants().ToList().Where(i => i.Name.LocalName == "p");
Step 3: Create data table and store all the data in data table from list like
DataTable dt = new DataTable();
dt.Columns.Add("StartTime");
dt.Columns.Add("EndTime");
dt.Columns.Add("Message");
foreach (var data in pNodelist)
{
if (data.HasAttributes == true)
dt.Rows.Add(data.FirstAttribute.Value, data.LastAttribute.Value, data.Value);
}
Step 4 : Then you can save it db and you can avoid the step 4 and directly store date from list to data base
You can add following data classes and repository class in your code.You can just call Repository class with filepath and call its FindAll method.It returns Url list.
public class Url
{
public string Loc { get; set; }
public string Changefreq { get; set; }
public string Lastmod { get; set; }
public Image Image { get; set; }
}
public class Image
{
public string Loc { get; set; }
public string Title { get; set; }
}
public class Repository
{
private XDocument xmlDatas;
public Repository(string filePath)
{
xmlDatas = XDocument.Load(filePath);
}
public List<Url> FindAll()
{
return xmlDatas.Elements().Elements()
.Select(url =>
{
Url data = new Url();
foreach (var item in url.Elements())
{
switch (item.Name.LocalName)
{
case "loc":
data.Loc = item.Value;
break;
case "changefreq":
data.Changefreq = item.Value;
break;
case "lastmod":
data.Lastmod = item.Value;
break;
case "image":
Image image = new Image();
foreach (var img in item.Elements())
{
switch (img.Name.LocalName)
{
case "loc":
image.Loc = img.Value;
break;
case "title":
image.Title = image.Title;
break;
}
}
data.Image = image;
break;
}
var s = item.Name.LocalName;
}
return data;
}).ToList();
}
}

nested recursive XML to CSV (using Linq)

How should I create CSV out of List<SPSecurableObject> where SPSecurableObject is recursive
class SPSecurableObject
{
public string ObjectName {get;set;}
public List<RoleAssignments> RoleAssignments {get;set;}
public List<SPSecurableObject> ChildObjects {get;set;}
}
class RoleAssignments
{
public string PrincipalType {get;set;}
public string DisplayName {get;set;}
public string RoleDefBindings {get;set;}
}
I have XML data file (see below) and need to make CSV file out of it. Started writing query with Linq to XML and I load data into List<SPSecurableObject>, but now cannot think of the solution to write CSV which should be like this:
ObjectName,PrincipalType,DisplayName,RoleDefBindings
New Data,,,
Documents,,,
Documents2,User,John Doe,Limited Access
Documents2,Group,Group1,Full Control
XML:
<SPSecurableObject>
<ObjectName>New Data</ObjectName>
<ChildObjects>
<SPSecurableObject>
<ObjectName>Documents</ObjectName>
</SPSecurableObject>
<SPSecurableObject>
<ObjectName>Documents2</ObjectName>
<RoleAssignments>
<SPRoleAssignment>
<PrincipalType>User</PrincipalType>
<Member>
<User>
<DisplayName>John Doe</DisplayName>
</User>
</Member>
<RoleDefBindings>
<RoleName>Limited Access</RoleName>
</RoleDefBindings>
</SPRoleAssignment>
<SPRoleAssignment>
<PrincipalType>Group</PrincipalType>
<Member>
<Group>
<GroupName>Group1</GroupName>
</Group>
</Member>
<RoleDefBindings>
<RoleName>Full Control</RoleName>
</RoleDefBindings>
</SPRoleAssignment>
</RoleAssignments>
</SPSecurableObject>
</ChildObjects>
</SPSecurableObject>
My Code to form List<SPSecurableObject> is this:
void Main()
{
var xml = XDocument.Load (#"C:\temp\data.xml");
var root = xml.Elements("SPSecurableObject");
List<SPSecurableObject> result = Load(root);
}
List<SPSecurableObject> Load(IEnumerable<XElement> xml)
{
return xml.Select(x=>new SPSecurableObject(){
ObjectName = x.Elements("ObjectName").Select(y=>y.Value).FirstOrDefault(),
RoleAssignments=(from ra in x.Elements("RoleAssignments").Elements("SPRoleAssignment")
select new RoleAssignments{
PrincipalType=ra.Elements("PrincipalType").Select(y=>y.Value).FirstOrDefault(),
DisplayName=ra.Elements("PrincipalType").Select(y=>y.Value).FirstOrDefault() == "Group" ? ra.Elements("Member").Elements("Group").Elements("GroupName").Select(y=>y.Value).FirstOrDefault() : ra.Elements("Member").Elements("User").Elements("DisplayName").Select(y=>y.Value).FirstOrDefault(),
RoleDefBindings = ra.Elements("RoleDefBindings").Elements("RoleName").Select(y=>y.Value).FirstOrDefault()
}).ToList(),
ChildObjects = Load(x.Elements("ChildObjects").Elements("SPSecurableObject"))
}).ToList();
}
Thanks
Try this:
var xml = XDocument.Load("data.xml");
var sb = new StringBuilder();
foreach (var node in xml.Descendants("SPSecurableObject"))
{
var objectName = node.Element("ObjectName").Value;
if (node.Element("RoleAssignments") == null)
{
sb.Append(objectName).AppendLine(",,,");
continue;
}
var lines = node.Descendants("SPRoleAssignment")
.Select(elem => string.Join(",", elem.DescendantNodes().OfType<XText>()));
if (lines.Any())
{
foreach (var line in lines)
sb.Append(objectName).Append(',').AppendLine(line);
}
else
{
sb.Append(objectName).AppendLine(",,,");
}
}
Console.WriteLine(sb);
This will give you the result you wanted.

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?

How can I create a collection of business objects from multiple elements in a XmlDocument

I'm trying to create a collection of business objects from the following xml document using .net 4.0/c#
<WebServices ErrorFound="False" ServerDateTime="30/11/2010 14:58:58">
<Results>
<Users TotalResults="5">
<UserName UserID="2">John</UserName>
<UserName UserID="3">Dave</UserName>
<UserName UserID="4">Jim</UserName>
<UserName UserID="5">Bob</UserName>
</Users>
</Results>
</WebServices>
This is the class I need to create
public class User
{
public string Name { get; set; }
public int ID { get; set; }
}
Each of the Users child elements should be a new instance of the class.
So far I have this method which accepts an XmlDocument.
public static IEnumerable<User> GetUser(XmlDocument xmlDoc)
{
XmlReader reader = XmlReader.Create(new StringReader(xmlDoc.OuterXml));
XDocument doc = XDocument.Load(reader);
var user = from u in doc.Descendants("WebServices").DescendantsAndSelf("Users")
select new User()
{
Name = u.Element("UserName").Value,
ID = int.Parse(u.Element("UserName").Attribute("UserID").Value)
};
List<User> userInstance = user.ToList();
IEnumerable<User> users= from u in userInstance
select u;
return users;
}
This works fine as far as producing one instance of the object from the first child element is concerned but I am unsure as to how to create multiple instances from all the elements.
I need to be able to return a collection of the User objects eg Collection<User> users = new Collection<User>()
I could be barking up completely the wrong tree. Any help is much appreciated.
Using XPath you can write the code like this:
public class User
{
public string Name { get; set; }
public int ID { get; set; }
}
static void Main(string[] args)
{
string xml =
"<WebServices ErrorFound='False' ServerDateTime='30/11/2010 14:58:58'>" +
"<Results>" +
"<Users TotalResults='5'>" +
"<UserName UserID='2'>John</UserName>" +
"<UserName UserID='3'>Dave</UserName>" +
"<UserName UserID='4'>Jim</UserName>" +
"<UserName UserID='5'>Bob</UserName>" +
"</Users>" +
"</Results>" +
"</WebServices>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var users = from userNameElement in doc.SelectNodes("/WebServices/Results/Users/UserName").OfType<XmlElement>()
select new User
{
Name = userNameElement.InnerText,
ID = Int32.Parse(userNameElement.GetAttribute("UserID"))
};
foreach (var user in users)
{
Console.WriteLine(user.ID.ToString() + ": " + user.Name);
}
}
Ok, so I finally figured out what was wrong with my code.
Instead of
var user = from u in doc.Descendants("WebServices").DescendantsAndSelf("Users")
select new User()
{
Name = u.Element("UserName").Value,
ID = int.Parse(u.Element("UserName").Attribute("UserID").Value)
};
I now have
var user = (from u in doc.Descendants("UserName")
select new Provider()
{
Name = u.Value,
ID = int.Parse(u.Attribute("UserID").Value)
});
the statement: doc.Descendants("UserName") basically produces an array of the UserName elements which can be iterated through, I can then directly access the value of that element and set it to my class properties.

Categories

Resources