I need to take in a text document from a server and create an XML hierarchy from it in order to use it in a C# program.
It will be an organizational hierarchy. Here is the same of the text file:
EmployeeID; Employee Name; SupervisorID
1; Bob; 3
2; Mark; 1
3; Jill; 0
4; Ann ; 1
Where the above relationships would be:
Bob's boss is Jill. Mark's boss is Bob, and Jill has no boss.
I want to create an XML file from that data to look something like this:
<Employee> Jill
<Employee> Bob
<Employee> Mark </Employee>
<Employee> Ann </Employee>
</Employee>
</Employee>
I don't know if this makes sense, because I have never worked with C# or XML before.
Feel free to change how the tags are named, the main things I will need to do:
Be able to get the names of everyone supervised by the same person. (Ex: Mark would want Mark and Ann)
Be able to get the names of all supervisors above an employee (Ex: Mark's would be Bob and Jill)
Be able to get the names of all people under then (Ex: Mark would have nobody, Jill would have everybody, Bob would have Mark and Ann)
I've looked at XElement and XDoc and various tutorials and SO questions but most of the SO questions are too advanced for me at this point, and most of the tutorials aren't trying to create a hierarchy like mine.
Define a class Employee:
public class Employee{
[XmlIgnore]
public int ID{get;set;}
[XmlIgnore]
public int BossID{get;set;}
[XmlText]
public string Name{get;set;}
[XmlElement("Employee")
public Employee[] Minions{get;set;}
public SetMinions(List<Employee> list){
Minions = list.Where(e=>e.BossID==ID).ToArray();
}
}
then parse your input:
List<Employee> list = File.ReadAllLines("MyInputFile.txt")
.Select(l=>new Emplyee(){
ID = l.Split(';')[0],
Name= l.Split(';')[1],
BossID = l.Split(';')[2]}).ToList();
and then set the minions for each:
list.ForEach(e=>e.SetMinions(list));
And produce the XML like this:
XmlSerializer xser = new XmlSerializer(typeof(Employee));
xser.Serialize(File.OpenWrite("output.txt", list.First(e=>e.BossID==0)));
If it's not obvious this code is quite dirty and unreliable, add some checks and cleanups
Employee class:
public class Employee
{
public Employee()
{
Subordinates = new List<Employee>();
}
public int Id { get; set; }
public string Name { get; set; }
public int SupervisorId { get; set; }
public List<Employee> Subordinates { get; private set; }
public XElement ToXml()
{
return new XElement("Employee",
new XElement("Id", Id),
new XElement("Name", Name),
new XElement("Subordinates", Subordinates.Select(s => s.ToXml())));
}
}
And parsing logic:
// dictionary as a storage for data read from file
var Employees = new Dictionary<int,Employee>();
// file reading
var file = File.Open("Input.txt", FileMode.Open);
using (var reader = new StreamReader(file))
{
reader.ReadLine();
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
string[] fields = line.Split(';');
var newEmployee = new Employee { Id = int.Parse(fields[0]), Name = fields[1].Trim(), SupervisorId = int.Parse(fields[2]) };
Employees.Add(newEmployee.Id, newEmployee);
}
}
// filling Subordinates within every employee
foreach (var emp in Employees.Values)
{
if (Employees.ContainsKey(emp.SupervisorId))
Employees[emp.SupervisorId].Subordinates.Add(emp);
}
// taking first (root) employee by selecting the one with supervisorId == 0
var first = Employees.Values.First(e => e.SupervisorId == 0);
// XML generation
var xml = first.ToXml();
Result that I received from that code with your sample input:
<Employee>
<Id>3</Id>
<Name>Jill</Name>
<Subordinates>
<Employee>
<Id>1</Id>
<Name>Bob</Name>
<Subordinates>
<Employee>
<Id>2</Id>
<Name>Mark</Name>
<Subordinates />
</Employee>
<Employee>
<Id>4</Id>
<Name>Ann</Name>
<Subordinates />
</Employee>
</Subordinates>
</Employee>
</Subordinates>
</Employee>
I think that structure is better than you suggested. However, you can easily modify XML structure by modifying ToXml method within Employee class. Following one would give output you suggested:
public XElement ToXml()
{
return new XElement("Employee",
new XText(Name),
Subordinates.Select(s => s.ToXml()));
}
Result:
<Employee>Jill
<Employee>Bob
<Employee>Mark</Employee>
<Employee>Ann</Employee>
</Employee>
</Employee>
Related
In my c# code I am trying to use XML serialization instead of xml linq. I am not sure which one is better.
But for now the issue I am having is that my node is coming twice. So this is my class structure
public class Enterprise
{
public Properties properties;
public List<Person> person;
}
This is the xml I get generated
<?xml version="1.0" encoding="iso-8859-1"?>
<enterprise>
<properties lang="nb">
<Comments>Utlasting av LMS-data</Comments>
<Datasource>SIKT</Datasource>
<Target />
<Datetime>onsdag 24. oktober 2018</Datetime>
</properties>
<person>
<Person>
<sourceid>
<Source>AD</Source>
<Id>123</Id>
</sourceid>
<Userid>mohsin</Userid>
</Person>
As you see the Person tag is coming twice. This is how it is set up
Enterprise enterprise = new Enterprise();
enterprise.properties.LanguageCode = "nb";
enterprise.properties.Comments = "Utlasting av LMS-data";
enterprise.properties.Datasource = "SIKT";
enterprise.properties.Target = "";
enterprise.properties.Datetime = DateTime.Now.ToLongDateString();
List<Person> person = new List<Person>();
person.Add(new Person
{
//sourceid = new SourceId
//{
// Id = "123",
// Source = "AD"
//},
Userid = "mohsin"
});
enterprise.person = person;
Does anyone knows the reason?
When you use List or array it will conside as `XmlArrayItem' to overcome use 'XmlElement'
public class Enterprise
{
public Properties properties;
[XmlElement("person")]
public List<Person> person;
}
I am new to unit testing and I am wondering what would be the best practices for unit testing xml deserialisation.
Consider the following xml:
<people>
<person id="1">
<name>Joe</name>
<age>28</age>
</person>
<person id="2">
<name>Jack</name>
<age>38</age>
</person>
</people>
And the following model class for the people:
[XmlRoot(ElementName ="people")]
public class People
{
public People() { PeopleList = new List<Person>(); }
[XmlElement("person")]
public List<Person> PeopleList { get; set; }
}
public class Person
{
[XmlAttribute("id")]
public int id { get; set; }
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("age")]
public int Age { get; set; }
}
I deserialize the xml using:
public List<Person> GetListOfPeople()
{
People plist = new People();
string content;
using (StreamReader sr = new StreamReader(manager.Open("People.xml")))
{
var serializer = new XmlSerializer(typeof(People));
plist = (People)serializer.Deserialize(sr);
}
return plist.PeopleList;
}
What would be the best methods to unit test the GetListOfPeople method above?
If you can change your method to take an xml file as an input parameter, you can have a sample xml file created and added to your test project. Since you know the values of your xml file, you can start comparing the values directly.
Considering you'll use the sample file you provided in your test, you can verify like this:
var persons = x.GetListOfPeople("sample.xml");
Assert.AreEqual("Joe", persons[0].Name);
Assert.AreEqual(38, persons[1].Age);
If the xml file is coming to your code from some source and you think it couldn't be following your xml schema all the time, then probably you can create some sample xml files again which violate your schema and prepare tests to call your method which should throw some exception if schema is not correct.
Hope this helps.
I would like to know how can I read a XML file from my desktop and put it into a string?
Here is my XML:
<smallusers>
<user id="1">
<name>John</name>
<motto>I am john, who are you?</motto>
</user>
<user id="2">
<name>Peter</name>
<motto>Hello everyone!</motto>
</user>
</smallusers>
<bigusers>
<user id="3">
<name>Barry</name>
<motto>Earth is awesome</motto>
</user>
</bigusers>
I want to store each user, but still detect if their small or big, is there a way to do this?
Before you downrate this, you might want to check google because I did research, but found nothing.
"Before you downrate this, you might want to check google because I
did research, but found nothing"
You found nothing because you don't know what you are searching for, also your XML is invalid, you need to enclose it in a rootElement. Then the first thing you need to do is read this file from the desktop if it exists.
You can check the size if you wish at that time and determine if this is "too large" even though it doesn't really matter. I highly doubt your XML file will be 5+ GB in size. If it is then you need an alternative, no single object in a .Net program may be over 2GB, the best you could do is 1,073,741,823 on a 64bit machine.
For very large XML files, anything above 1.0 GB, combine XmlReader and LINQ as stated by Jon Skeet here:
If your document is particularly huge, you can combine XmlReader and
LINQ to XML by creating an XElement from an XmlReader for each of your
"outer" elements in a streaming manner: this lets you do most of the
conversion work in LINQ to XML, but still only need a small portion of
the document in memory at any one time.
For small XML files, anything 1.0 GB or lower stick to the DOM as shown below.
With that said, what you need is to learn what Serialization and Deserialization mean.
Serialize convert an object instance to an XML document.
Deserialize convert an XML document into an object instance.
Instead of XML you can also use JSON, binary, etc.
In your case this is what can be done to Deserialize this XML document back into an Object in order for you to use in your code.
First fix up the XML and give it a Root.
<?xml version="1.0" encoding="UTF-8"?>
<DataRoot>
<smallusers>
<user id="1">
<name>John</name>
<motto>I am john, who are you?</motto>
</user>
<user id="2">
<name>Peter</name>
<motto>Hello everyone!</motto>
</user>
</smallusers>
<bigusers>
<user id="3">
<name>Barry</name>
<motto>Earth is awesome</motto>
</user>
</bigusers>
</DataRoot>
Then create the root class in C#, you may generate this directly in Visual Studio 2012+ by copying your XML and going to Edit - Paste Special, but I like to use: XML to C# Class Generator
Here is what your code would look like after you generate the C# Root Class for your XML, hope it helps you understand it better.
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
public class Program
{
[XmlRoot(ElementName = "user")]
public class User
{
[XmlElement(ElementName = "name")]
public string Name { get; set; }
[XmlElement(ElementName = "motto")]
public string Motto { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
[XmlRoot(ElementName = "smallusers")]
public class Smallusers
{
[XmlElement(ElementName = "user")]
public List<User> User { get; set; }
}
[XmlRoot(ElementName = "bigusers")]
public class Bigusers
{
[XmlElement(ElementName = "user")]
public User User { get; set; }
}
[XmlRoot(ElementName = "DataRoot")]
public class DataRoot
{
[XmlElement(ElementName = "smallusers")]
public Smallusers Smallusers { get; set; }
[XmlElement(ElementName = "bigusers")]
public Bigusers Bigusers { get; set; }
}
static void Main(string[] args)
{
string testXMLData = #"<DataRoot><smallusers><user id=""1""><name>John</name><motto>I am john, who are you?</motto></user><user id=""2""><name>Peter</name><motto>Hello everyone!</motto></user></smallusers><bigusers><user id=""3""><name>Barry</name><motto>Earth is awesome</motto></user></bigusers></DataRoot>";
var fileXmlData = File.ReadAllText(#"C:\XMLFile.xml");
var deserializedObject = DeserializeFromXML(fileXmlData);
var serializedToXML = SerializeToXml(deserializedObject);
//I want to store each user, but still detect if their small or big, is there a way to do this?
foreach (var smallUser in deserializedObject.Smallusers.User)
{
//Iterating your collection of Small users?
//Do what you need here with `smalluser`.
var name = smallUser.Name; //Example...
}
Console.WriteLine(serializedToXML);
Console.ReadKey();
}
public static string SerializeToXml(DataRoot DataObject)
{
var xsSubmit = new XmlSerializer(typeof(DataRoot));
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
xsSubmit.Serialize(writer, DataObject);
var data = sw.ToString();
writer.Flush();
writer.Close();
sw.Flush();
sw.Close();
return data;
}
}
}
public static DataRoot DeserializeFromXML(string xml)
{
var xsExpirations = new XmlSerializer(typeof(DataRoot));
DataRoot rootDataObj = null;
using (TextReader reader = new StringReader(xml))
{
rootDataObj = (DataRoot)xsExpirations.Deserialize(reader);
reader.Close();
}
return rootDataObj;
}
}
}
I have the following XML Document loaded in a XDocument
<XXXXXXXXXXXXXXXXXX xmlns:XXXXX="http://XXXXX/XXXXX/XXXXX/" xmlns:mbs="http://www.microsoft.com/xml">
<ProdOrderRoutingLine xmlns="Prod. Order Routing Line" />
<Count>2</Count>
<Records>
<Record>
<Field>
<Name xmlns="Name">Company-Company-Company-Company</Name>
<Value xmlns="Value">CRONUS Canada, Inc.</Value>
<FieldCaption xmlns="FieldCaption">Name</FieldCaption>
</Field>
<Field>
<Name xmlns="Name">Operation No.</Name>
<Value xmlns="Value">020</Value>
<Caption xmlns="Caption">Operation No.</Caption>
</Field>
<Field>
<Name xmlns="Name">Line No.</Name>
<Value xmlns="Value">771521</Value>
<Caption xmlns="Caption" />
</Field>
</Record>
<Record>
<Field>
<Name xmlns="Name">Company-Company-Company-Company</Name>
<Value xmlns="Value">CRONUS Canada, Inc.</Value>
<FieldCaption xmlns="FieldCaption">Name</FieldCaption>
</Field>
<Field>
<Name xmlns="Name">Operation No.</Name>
<Value xmlns="Value">020</Value>
<Caption xmlns="Caption">Operation No.</Caption>
</Field>
<Field>
<Name xmlns="Name">Line No.</Name>
<Value xmlns="Value">798122</Value>
<Caption xmlns="Caption" />
</Field>
</Record>
</Records>
</XXXXXXXXXXXXXXXXXX>
I am trying to read it using LINQ and populate this class for each record
public class Record
{
public IEnumerable<Field> Fields { get; set; }
public Record()
{
Fields = new List<Field>();
}
}
public class Field
{
public string Name { get; set; }
public string Value { get; set; }
public string Caption { get; set; }
}
Any proper way of feeding the XML inside my collection is welcome.
I tried messing around with :
var doc = XDocument.Load(#"c:\test\output.xml");
var query = from table in doc.Element("XXXXXXXXXXXXXXXXXX").Element("Records").Elements("Record")
select new
{
name = table.Elements("Field").Attributes("Name"),
value = table.Elements("Field").Attributes("Value"),
caption = table.Elements("Field").Attributes("FieldCaption")
};
I get nothing close to what I am looking for.
I'm not going to the full gory details here but that is a very badly designed XML document. You or the designer of the document needs to read up one the XML standard, in particular, namespaces.
There are namespaces involved in the document. The elements under the Field elements are all namespaced (incorrectly I might add). The Name element is in the namespace: "Name". Value is in the namespace: "Value". And the Caption element is either in the namespace "Caption" or "FieldCaption". You will have to write your query in terms of those namespaces.
This method could help in reading those field elements:
Field ReadField(XElement fieldElement)
{
XNamespace nsName = "Name";
XNamespace nsValue = "Value";
XNamespace nsFieldCaption = "FieldCaption";
XNamespace nsCaption = "Caption";
return new Field
{
Name = (string)fieldElement.Element(nsName + "Name"),
Value = (string)fieldElement.Element(nsValue + "Value"),
// Try the Caption namespace, otherwise try FieldCaption
Caption = (string)fieldElement.Element(nsCaption + "Caption") ?? (string)fieldElement.Element(nsFieldCaption + "FieldCaption"),
};
}
Then your query simply becomes this:
var query =
from record in doc.XPathSelectElements("/XXXXXXXXXXXXXXXXXX/Records/Record")
select new Record
{
Fields = record.Elements("Field")
.Select(f => ReadField(f))
.ToList(),
};
I think you are on the right track but have an issue with your LINQ query.
I don't have time to fully test the following example, but the following looks a bit closer:
var result = from c in doc.Descendants("Records")
select new Record()
{
Fields = from f in c.Descendants("Field")
select new Field()
{
Name = f.Element("Name").Value,
Value = f.Element("Value").Value,
Caption = f.Element("FieldCaption").Value
}
};
Note: The following links seem to have valuable information:
Parse xml using LINQ to XML to class objects
How to get elements by name in XML using LINQ
Good luck.
Ok Using the information given by the people above, i ended up
Fixing the XML to normalize the Caption and FieldCaption tags
Removed the XMLNS that were not needed
So now my code is this..
public class Record
{
public List<Field> Fields { get; set; }
public Record()
{
Fields = new List<Field>();
}
}
public class Field
{
public string Name { get; set; }
public string Value { get; set; }
public string Caption { get; set; }
public Field(){}
}
Field ReadField(XElement fieldElement)
{
return new Field{
Name = (string)fieldElement.Element("Name"),
Value = (string)fieldElement.Element("Value"),
Caption = (string)fieldElement.Element("Caption")
};
}
void Main()
{
var lstRecords = new List<Record>();
XDocument doc = XDocument.Load(#"c:\fred\output.xml");
var query = from record in doc.XPathSelectElements("/XXXXXXXXXXXXXXXXXX/Records/Record")
select new Record
{
Fields = record.Elements("Field").Select (f => ReadField(f)).ToList(),
};
query.Dump();
}
And It works flawlessly
Ok i have an Xml file like this :
<?xml version="1.0" encoding="utf-8" ?>
<Books>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
</Books>
And i want to use Deserialization in order to fill a List with books.
I have a class like this :
[Serializable]
public class Book
{
public string Name{get; set;}
public string Url { get; set; }
public string Genre { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
And a List like this :
List<Book> BookList;
I try to read from the xml File like this :
XmlSerializer serializer = new XmlSerializer(typeof(Book));
FileStream fs = new FileStream(#"Book.xml", FileMode.Open);
using (XmlReader reader = XmlReader.Create(fs))
{
BookList.Add((Book)serializer.Deserialize(reader));
reader.Close();
}
But i get an error in Xml File whenever i run my app. If i change my xml file to this :
<Book>
<Name>book name</Name>
<Url>book url</Url>
<Genre>book genre</Genre>
<City>book city</City>
<Country>book country</Country>
</Book>
everything goes fine , expect that i only read 1 book. What i want to do is read around 100 books. Any ideas?
if you change your deserialization code a little bit, it will work.
(See the usage of XmlRootAttribute & and type Book[])
XmlSerializer serializer = new XmlSerializer(typeof(Book[]),new XmlRootAttribute("Books"));
FileStream fs = new FileStream(#"Book.xml", FileMode.Open);
using (XmlReader reader = XmlReader.Create(fs))
{
var books = (Book[])serializer.Deserialize(reader);
}
BTW: you don't need Serializable attribute, It is only used by BinaryFormatter
PS: You can also use the type List<Book> instead of Book[]
EDIT
After modifiying your code a little bit more, It can be as simple as:
XmlSerializer serializer = new XmlSerializer(typeof(List<Book>),
new XmlRootAttribute("Books"));
using (FileStream fs = new FileStream(#"Book.xml", FileMode.Open))
{
List<Book> books = (List<Book>)serializer.Deserialize(fs);
}
You need to create a class that will contains a list of books and seriazlie this class:
[Serializable]
public class BookData
{
[XmlArray(ElementName="Books")]
[XmlArrayItem(ElementName="Book")]
public List<Book> Books {get; set;}
}
and then create the serializer like this:
var serializer = new XmlSerializer(typeof(BookData));
Instead of loading from FileStream that we can read from XmlDataDocument and iteration of the book element will give us a serialization pretty much easier. Please take a look at the modified approach.
List<Book> BookList = new List<Book>();
XmlDataDocument objDocument = new XmlDataDocument();
objDocument.Load(#"Book.xml");
XmlSerializer serializer = new XmlSerializer(typeof(Book));
foreach(XmlNode objItem in objDocument.DocumentElement.ChildNodes)
{
TextReader objTextReader = new StringReader(objItem.OuterXml);
using (XmlReader reader = XmlReader.Create(objTextReader))
{
BookList.Add((Book)serializer.Deserialize(reader));
reader.Close();
}
}