Displaying a class list on the main static void - c#

I'm looking to output the list that I've made and usually I just do a for each loop and call up the list like shown in the code below. The console wants me to correct it but then it doesn't display as the reference is null.
Thanks for any help
class Program
{
const string FILENAME = #"royalTreeResults.xml";
// THIS SECTION OF CODE IS WHAT IT SUGGESTS private static readonly IEnumerable<Family> families;
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Tree tree = new Tree();
tree.families = doc.Descendants("family").Select(x => new Family()
{
FamilyName = (string)x.Element("name"),
FamilyTotalReign = (int)x.Element("totalReign"),
People = x.Elements("person").Select(y => Person.Recursive(y)).ToList()
}).ToList();
foreach(Family per in families) // <--- THE ERROR IS HERE
{
Console.WriteLine(per.FamilyName + " " + per.FamilyTotalReign + " " + per.People);
}
}
}
public class Family
{
public string FamilyName { get; set; }
public int FamilyTotalReign { get; set; }
public List<Person> People { get; set; }
}
public class Person
{
public int? PersonBorn { get; set; }
public int? PersonCoronation { get; set; }
public int? PersonDied { get; set; }
public string PersonName { get; set; }
public int? PersonYinPower { get; set; }
public List<Person> Children { get; set; }
public static Person Recursive(XElement person)
{
Person newPerson = new Person();
newPerson.PersonName = (string)person.Element("name");
newPerson.PersonYinPower = (int?)person.Element("yearsInpower");
newPerson.PersonBorn = (int?)person.Element("born");
newPerson.PersonDied = (int?)person.Element("died");
newPerson.PersonCoronation = (int?)person.Element("coronation");
if (person.Element("children") != null)
{
newPerson.Children = person.Element("children").Elements("person").Select(y => Person.Recursive(y)).ToList();
}
return newPerson;
}
}
public class Tree
{
public List<Family> families = new List<Family>();
}
A sample of the .xml is below, however I know this is not the problem
<?xml version="1.0" encoding="utf-8"?>
<royaltree>
<family>
<name>Wessex</name>
<totalReign>137</totalReign>
<person>
<name>Alfred the Great</name>
<yearsInpower>28</yearsInpower>
<born>849</born>
<died>899</died>
<coronation>871</coronation>
<children>
<person>
<name>Edward the Elder</name>
<yearsInpower>25</yearsInpower>
<born>879</born>
<died>924</died>
<coronation>899</coronation>
<children>
<person>
<name>Edmund I the Elder</name>
<yearsInpower>6</yearsInpower>
<born>939</born>
<died>946</died>
<coronation>940</coronation>
<children>
<person>
<name>Edger the Peaceful</name>
<yearsInpower>16</yearsInpower>
<born>944</born>
<died>975</died>
<coronation>959</coronation>
<children>
<person>
<name>Ethelred II</name>
<yearsInpower>38</yearsInpower>
<born>962</born>
<died>1016</died>
<coronation>978</coronation>
<children>
<person>
<name>Edward Confessor</name>
<yearsInpower>24</yearsInpower>
<born>1002</born>
<died>1066</died>
<coronation>1042</coronation>
</person>
<person>
<name>Edward II Ironside</name>
<yearsInpower>0</yearsInpower>
<born>1002</born>
<died>1016</died>
<coronation>1016</coronation>
</person>
</children>
</person>
</children>
</person>
</children>
</person>
</children>
</person>
</children>
</person>
</family>
<family>
<name>Norman</name>
<totalReign>69</totalReign>
<person>
<name>William I</name>
<yearsInpower>21</yearsInpower>
<born>1028</born>
<died>1087</died>
<coronation>1066</coronation>
<children>
<person>
<name>Adela</name>
<born>1050</born>
<died>1080</died>
</person>
<person>
<name>William II</name>
<yearsInpower>13</yearsInpower>
<born>1056</born>
<died>1100</died>
<coronation>1087</coronation>
</person>
<person>
<name>Henry I Beauclerc</name>
<yearsInpower>35</yearsInpower>
<born>1068</born>
<died>1135</died>
<coronation>1100</coronation>
<children>
<person>
<name>Matilda</name>
<born>1130</born>
<died>1167</died>
</person>
</children>
</person>
</children>
</person>
</family>

I don't know if I get you right. But I hope this might be helpful:
static void Main(string[] args)
{
//... your stuff
//Iterate through families in tree
foreach (Family per in tree.families)
{
//Show the family info
Console.WriteLine(per.FamilyName + " " + per.FamilyTotalReign);
//Show the members recursively
ShowPeopleRecursive(per.People, 1);
}
}
//Shows the people tree.
private static void ShowPeopleRecursive(List<Person> people, int level)
{
const string indent = "";
foreach (var p in people)
{
Console.WriteLine($"{indent.PadLeft(level, ' ')} {p.PersonName} ({p.PersonBorn} - {p.PersonDied}). Years in power: {p.PersonYinPower}");
if (p.Children != null)
ShowPeopleRecursive(p.Children, level + 1);
}
}
You iterate over your families in the tree and then recursively print the people (and their children). You should see something like:
Hope this helps!

So you have to loop through you person list like below.
foreach (Family per in tree.families) // <--- THE ERROR IS HERE
{
Console.WriteLine(per.FamilyName + " " + per.FamilyTotalReign);
foreach (var ppl in per.People)
{
Console.WriteLine(ppl.PersonName + " " + per.FamilyName);
}
}
But I would like to recommend you to use XmlSerializer and you can Deserialize your XML to object.This is much more cleaner approach. if you want sample code let me know.

Related

XmlDocument - How to get all values from multiple nodes

Here is my sample XML file:
<Main>
<Person>
<Name>Božena</Name>
<Surname>Němcová</Surname>
<Gender>Female</Gender>
<OrderNum>18</OrderNum>
<BirthDate>04.02.1820</BirthDate>
</Person>
<Person>
<Name>Jan</Name>
<Surname>Žižka</Surname>
<Gender>Male</Gender>
<OrderNum>7</OrderNum>
<BirthDate>19.09.1360</BirthDate>
</Person>
<Person>
<Name>Che</Name>
<Surname>Guevara</Surname>
<Gender>Male</Gender>
<OrderNum>27</OrderNum>
<BirthDate>14.06.1928</BirthDate>
</Person>
<Person>
<Name>Antonie</Name>
<Surname>de Saint-Exupéry</Surname>
<Gender>Male</Gender>
<OrderNum>15</OrderNum>
<BirthDate>29.06.1900</BirthDate>
</Person>
</Main>
Here is a code which I want to use to get a list of all values of Name element:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("PersonWrite.xml");
XmlNodeList data = xmlDoc.SelectNodes("Main/Person/Name");
The problem is that I'm only getting a value from the first Person element.
I like using deserialisation, it's a lot easier to work with.
using System;
using System.Xml.Serialization;
using System.IO;
public class Main
{
[XmlElement("Person")]
public Person[] People { get; set; }
}
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
public string Gender { get; set; }
public int OrderNum { get; set; }
public string BirthDate { get; set; }
}
public class Program
{
public static void Main()
{
var xmlString = #"<Main>
<Person>
<Name>Božena</Name>
<Surname>Němcová</Surname>
<Gender>Female</Gender>
<OrderNum>18</OrderNum>
<BirthDate>04.02.1820</BirthDate>
</Person>
<Person>
<Name>Jan</Name>
<Surname>Žižka</Surname>
<Gender>Male</Gender>
<OrderNum>7</OrderNum>
<BirthDate>19.09.1360</BirthDate>
</Person>
<Person>
<Name>Che</Name>
<Surname>Guevara</Surname>
<Gender>Male</Gender>
<OrderNum>27</OrderNum>
<BirthDate>14.06.1928</BirthDate>
</Person>
<Person>
<Name>Antonie</Name>
<Surname>de Saint-Exupéry</Surname>
<Gender>Male</Gender>
<OrderNum>15</OrderNum>
<BirthDate>29.06.1900</BirthDate>
</Person>
</Main>";
var serializer = new XmlSerializer(typeof (Main));
Main main = null;
using (var reader = new StringReader(xmlString))
{
main = (Main)serializer.Deserialize(reader);
}
if (main == null)
{
return;
}
Console.WriteLine(main.People.Length);
}
}
Output:
4
I would go through the Child Nodes of Main Node
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("PersonWrite.xml");
XmlNodeList root = xmlDoc.SelectNodes("Main");
foreach (XmlNode xnode in root.ChildNodes)
{
//get data from xnode
}

C# Xml Deserialize, ignore data in XML

During de-serialization, how do I ignore a property? In my case, I don't want the FullName property to get initialized at all. I am not looking for [XMLIgnore] solutions - think it as a scenario where I don't have access to change the class.
Here's my class:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
}
Here's how I am initializing:
Person p1 = new Person() { Id = 1, FullName = "P1" };
Person p2 = new Person() { Id = 2, FullName = "P2" };
List<Person> persons = new List<Person> { p, q }; //this object is xml serialized.
And here's my XML: ( I got it though the XML Serialization)
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Person>
<Id>1</Id>
<FullName>P1</FullName>
</Person>
<Person>
<Id>2</Id>
<FullName>P2</FullName>
</Person>
</ArrayOfPerson>
You can use a custom XmlReader in the deserialization process that will simply skip over the FullName elements. Something like this:
public class MyXmlReader : XmlTextReader
{
public MyXmlReader(string filePath) : base(filePath)
{
}
public override bool Read()
{
if (base.Read())
{
if (Name == "FullName")
return base.Read();
return true;
}
return false;
}
}
Then use it like this
var serializer = new XmlSerializer(typeof(List<Person>));
using (var reader = new MyXmlReader("XMLFile.xml"))
{
var person = (List<Person>)serializer.Deserialize(reader);
}
You can implement a different constructor to take a stream or whatever you have. It doesn't have to be a file path.

How to parse xml with LINQ to an Object with XDocument

I have an xml file as below:
<Message xsi:schemaLocation ="..">
<Header>..</Header>
<Body>
<History
xmlns="..">
<Number></Number>
<Name></Name>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description></Description>
</Item>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description>1..</Description>
<Description>2..</Description>
</Item>
</History>
</Body>
</Message>
I would like to create and object from this as History object.
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
var xElement = XDocument.Parse(xmlString);
XElement body = (XElement)xElement.Root.LastNode;
XElement historyElement = (XElement)body.LastNode;
var history = new History
{
Name = (string)historyElement.Element("Name"),
Number = (string)historyElement.Element("Number"),
Items = (
from e in historyElement.Elements("Item")
select new Item
{
CraeteDate = DateTime.Parse(e.Element("CreateDate").Value),
Type = (string)e.Element("Type").Value,
Description = string.Join(",",
from p in e.Elements("Description") select (string)p.Element("Description"))
}).ToList()
};
Why this does not work?
The values are always null.
It seems that "historyElement.Element("Name")" is always null even there is an element and value for the element.
Any idea what am I missing?
Thanks
It's due to the namespace, try doing this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";// the namespace you have in the history element
var xElement = XDocument.Parse(xmlString);
var history= xElement.Descendants(ns+"History")
.Select(historyElement=>new History{ Name = (string)historyElement.Element(ns+"Name"),
Number = (string)historyElement.Element(ns+"Number"),
Items = (from e in historyElement.Elements(ns+"Item")
select new Item
{
CraeteDate= DateTime.Parse(e.Element(ns+"CreateDate").Value),
Type = (string) e.Element(ns+"Type").Value,
Description= string.Join(",",
from p in e.Elements(ns+"Description") select (string)p)
}).ToList()
}).FirstOrDefault();
If you want to read more about this subject, take a look this link
A couple of minor things here, the xml was malformed here. So had to take a while to test and make it work.
You have an xsi in the front which I assume should be somewhere mentioned in the xsd.
Turns out you have to append the namespace if your xml node has a namespace attached to it as you are parsing the xml tree here:
My sample solution looked like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public DateTime? CreateDate { get; set; }
public string Type { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xmlString =
#"<Message>
<Header>..</Header>
<Body>
<History
xmlns=""http://schemas.somewhere.com/types/history"">
<Number>12</Number>
<Name>History Name</Name>
<Item>
<CreateDate></CreateDate>
<Type>Item 1 Type</Type>
<Description>Item 1 Description</Description>
</Item>
<Item>
<CreateDate></CreateDate>
<Type>Item 2 Type</Type>
<Description>Item 2 Description 1</Description>
<Description>Item 2 Description 2</Description>
</Item>
</History>
</Body>
</Message>";
XNamespace ns = "http://schemas.somewhere.com/types/history";
var xElement = XDocument.Parse(xmlString);
var historyObject = xElement.Descendants(ns +"History")
.Select(historyElement => new History
{
Name = historyElement.Element(ns + "Name")?.Value,
Number = historyElement.Element(ns + "Number")?.Value,
Items = historyElement.Elements(ns + "Item").Select(x => new Item()
{
CreateDate = DateTime.Parse(x.Element(ns + "CreateDate")?.Value),
Type = x.Element(ns + "Type")?.Value,
Description = string.Join(",", x.Elements(ns + "Description").Select(elem=>elem.Value))
}).ToList()
}).FirstOrDefault();
}
}
}
If you dont wan't to care about finding the namespace, you might want to try the following:
var document = XDocument.Parse(xmlString);
var historyObject2 = document.Root.Descendants()
.Where(x=>x.Name.LocalName == "History")
.Select(historyElement => new History
{
Name = historyElement.Element(historyElement.Name.Namespace + "Name")?.Value,
Number = historyElement.Element(historyElement.Name.Namespace+ "Number")?.Value,
Items = historyElement.Elements(historyElement.Name.Namespace + "Item").Select(x => new Item()
{
//CreateDate = DateTime.Parse(x.Element("CreateDate")?.Value),
Type = x.Element(historyElement.Name.Namespace + "Type")?.Value,
Description = string.Join(",", x.Elements(historyElement.Name.Namespace + "Description").Select(elem => elem.Value))
}).ToList()
}).FirstOrDefault();

Correct way to convert Xml to Objects?

I have very simple Xml structure that I want to convert to list of objects. My code does work but I think this is not the correct way of doing this and since I never did this I think there might be simpler way of doing what I want.
Xml example
<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>
Code to gather xml and create list of objects
class Program
{
static void Main(string[] args)
{
List<Item> itemList = new List<Item>();
var url = "http://xmlurl.com/xml";
// Load xml data
XmlDocument myXmlDocument = new XmlDocument();
myXmlDocument.Load(url);
// Select items and loop
var xmlItems = myXmlDocument.SelectNodes("/root/item");
foreach (XmlNode item in xmlItems)
{
var newItem = new Item();
foreach (XmlNode i in item)
{
// Since I cannot query them properly I need to check every item node
switch (i.Name)
{
case "name":
newItem.Name = i.InnerText;
break;
case "price":
newItem.Price = Convert.ToDecimal(i.InnerText);
break;
}
}
itemList.Add(newItem);
}
// Test it out
foreach (var item in itemList.OrderBy(x => x.Price))
{
Console.WriteLine(item.Name + " | " + item.Price);
}
Console.ReadLine();
}
}
class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
}
Using LINQ:
XDocument xdoc = XDocument.Load("myXml.xml");
List<Item> items = (from item in xdoc.Descendants("item")
select new Item {
Name = item.Element("name").Value,
Price = item.Element("price").Value
}).ToList();
You should use XmlSerializer, example:
Classes:
[XmlType(TypeName="item")]
public class Item {
public string Name { get; set; }
public decimal Price { get; set; }
}
[XmlRoot(ElementName = "root")]
public class ItemList : List<Item> {
}
Getting them from markup:
const string test = #"<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>";
var serializer = new XmlSerializer(typeof(ItemList));
List<Item> result;
using (var reader = new StringReader(test)) {
result = (List<Item>)serializer.Deserialize(reader);
}
You can define your class:
[XmlType("item")]
public class Item
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("price")]
public decimal Price { get; set; }
}
And then deserialize the Xml:
var xml = #"<root>
<item>
<name>Item 1</name>
<price>30.00</price>
</item>
<item>
<name>Item 2</name>
<price>55.00</price>
</item>
</root>";
List<Item> items;
var serializer = new XmlSerializer(typeof(List<Item>),
new XmlRootAttribute("root"));
using(var stream = new StringReader(xml))
{
items = (List<Item>)serializer.Deserialize(stream);
}
if(items != null)
{
foreach(var item in items)
{
Console.Write(item);
}
}

Linq to Xml selecting elements

I have this xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Calendar>
<item id="34">
<Date>26 Apr</Date>
<Name>aa</Name>
<Date>26 Apr</Date>
<Name>aaa</Name>
<Date>23 Apr</Date>
<Name>aaaa</Name>
<Date>23 Apr</Date>
<Name>aaaaa</Name>
</item>
<item id="35">
<Date>27 Apr</Date>
<Name>aa</Name>
<Date>27 Apr</Date>
<Name>aaa</Name>
<Date>27 Apr</Date>
<Name>aaaa</Name>
<Date>27 Apr</Date>
<Name>aaaaa</Name>
</item>
</Calendar>
this is my class
public class Calendar
{
public string Name{ get; set; }
public string Data { get; set; }
}
listBox.ItemsSource =
from var in xml.Descendants("item")
orderby Convert.ToInt32(var.Attribute("id").Value) ascending
select new Calendar
{
Name= var.Element("Name").Value,
Data = var.Element("Data ").Value,
};
but in listBox i have only the first date and name of every item
class Program
{
static void Main(string[] args)
{
XDocument xml =
XDocument.Load(
#"Path to your xml");
var q = from x in xml.Descendants("item")
orderby Convert.ToInt32(x.Attribute("id").Value) ascending
select new Calendar
{
Name = x.Elements("Name").Select(a => a.Value).ToList<String>(),
Date = x.Elements("Date").Select(a => a.Value).ToList<String>()
};
List<Calendar> calendars = q.ToList<Calendar>();
}
public class Calendar
{
public List<String> Name { get; set; }
public List<String> Date { get; set; }
}
}
XElement.Element(elementName) only gets the first element that matches the elementName. Try a different approach using XElement.Elements(elementName)

Categories

Resources