Parsing parts of XML to different classes - c#

I am trying to figure out how I can read in a parts of an XML file into a class.
This is the structure of the XML File:
<Root>
<Element1>
<Element2>
<ElementID></ElementId>
<Element3a>
<Element3aId id="">0</Element3aId>
<Element3aId id="">0</Element3aId>
</Element3a>
<Element3b>
<Element3bId id="">0</Element3bId>
<Element3bId id="">0</Element3bId>
</Element3b>
<Element3c>
<Element3cId id="">0</Element3cId>
<Element3cId id="">0</Element3cId>
</Element3c>
</Element2>
</Element1>
</Root>
I have created the following classes (I removed accessors/mutators to try to simplify the code:
public struct Element3a
{
[XmlAttribute("id")]
string id;
[XmlElement("Element3aId")]
int Element3aId;
}
[XmlRootAttribute("Element2")]
public struct Element1
{
[XmlElement("ElementID")]
int id;
List<Element3a> Element3aId;
List<Element3a> Element3bId;
List<Element3a> Element3cId;
}
Here is my parsing code:
XDocument xDoc = XDocument.Load(filePath);
var Element2List = xDoc.Root.Descendants().FirstOrDefault(c => c.Name.LocalName.Equals("Element1"));
XmlSerializer serializer = new XmlSerializer(typeof(Element1));
using (var reader = Element2List.CreateReader())
{
do
{
reader.ReadToDescendant("Element2");
Element1 tag1 = (Element1)serializer.Deserialize(reader.ReadSubtree());
// How do I populate the Element3a Lists here?
} while (reader.ReadToNextSibling("Station"));
reader.Close();
}
Element3a/Element3b/Element3c all have the same data in them, so I want to be able to serialize them into just the Element3a structs. I am able to get the Element2 information into the Element2 class, but I am unable to populate the Element3a lists in that class.

Instead of serialize use Xml Linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Element1 elements = doc.Descendants("Element2").Select(x => new Element1() {
id = (string)x.Element("ElementID") == "" ? null : (int?)x.Element("ElementID"),
Element3aId = x.Descendants("Element3a").Select(y => new Element3a {
id = (string)y.Attribute("id"),
Element3aId = (string)y.Element("Element3aId") == "" ? null : (int?)y.Element("Element3aId")
}).ToList(),
Element3bId = x.Descendants("Element3b").Select(y => new Element3a {
id = (string)y.Attribute("id"),
Element3aId = (string)y.Element("Element3bId") == "" ? null : (int?)y.Element("Element3bId")
}).ToList(),
Element3cId = x.Descendants("Element3c").Select(y => new Element3a
{
id = (string)y.Attribute("id"),
Element3aId = (string)y.Element("Element3cId") == "" ? null : (int?)y.Element("Element3cId")
}).ToList()
}).FirstOrDefault();
}
}
public struct Element3a
{
public string id;
public int? Element3aId;
}
public struct Element1
{
public int? id;
public List<Element3a> Element3aId;
public List<Element3a> Element3bId;
public List<Element3a> Element3cId;
}
}

Related

Replacing values stored in a File with values in an Object C#

I have an svg file defined as below ,
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<svg width="200mm" height="300mm"
xmlns="http://www.w3.org/2000/svg" dominant-baseline="hanging">
<text x="139.85mm" y="1.85mm" font-size="12pt">"studentName"</text>
<text x="142.05mm" y="289.72mm" font-size="12pt">"studentAge"</text>
</svg>
I have written a program to replace values stored in "" such as "studentName" with values that are actually assigned in program runtime , however I am finding it hard to replace all such values at once as I cannot apply the && operator.
Here is my code so far , would really appreciate some help
class Program
{
static void Main(string[] args)
{
Student student = new Student();
student.Name = "Max";
student.Age = "10";
string file = File.ReadAllText(#"D:\Del\structure.svg");
updatedDocu(file, student);
}
public static string updatedDocu(string intialDocument , Student studemt)
{
string updatedDoc = intialDocument.Replace("{studentName}", studemt.Name) && intialDocument.Replace("{studentAge}",studemt.Age);
return updatedDoc;
}
}
}
public class Student
{
public Student()
{
}
public string Name{ get; set; }
public string Age { get; set; }
}
You need to replace
string updatedDoc = intialDocument.Replace("{studentName}", studemt.Name) && intialDocument.Replace("{studentAge}",studemt.Age);
with
string updatedDoc = intialDocument.Replace("\"studentName\"", studemt.Name).Replace("\"studentAge\"",studemt.Age);
it will work. Please correct spelling mistake of student object (studemt) to student
I would suggest using stringbuilder, for example:
public static string updatedDocu(string intialDocument, Student student)
{
return new StringBuilder(initialDocument)
.Replace("{studentName}", student.Name)
.Replace("{studentAge}", student.Age)
.ToString();
}
Try following xml linq :
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XNamespace ns = doc.Root.GetDefaultNamespace();
Dictionary<string, XElement> dict = doc.Descendants(ns + "text")
.GroupBy(x => ((string)x).Replace("\"",""), y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
XElement studentName = dict["studentName"];
studentName.SetValue("John");
XElement studentAge = dict["studentAge"];
studentAge.SetValue(20);
}
}
}
I don't know whether it's the approach you need, but you could automate the process with the use of reflection to get the property from the object based on the property name mentioned in XML. Please, note that I don't check whether a property exists on an object (in your case - Student).
var xmlstr = #"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg width='200mm' height='300mm'
xmlns='http://www.w3.org/2000/svg' dominant-baseline='hanging'>
<text x='139.85mm' y='1.85mm' font-size='12pt'>{studentName}</text>
<text x='142.05mm' y='289.72mm' font-size='12pt'>{studentAge}</text>
</svg>";
XNamespace ns = "http://www.w3.org/2000/svg";
var xml = XElement.Parse(xmlstr);
var student = new Student { Name = "Steve", Age = "30" };
var type = student.GetType();
foreach(var text in xml.Elements(ns + "text"))
{
// Get the name of the property we need to find in Student
var prop_name = Regex.Match(text.Value, "[A-Z][a-z]+(?=})").Value;
// Get the property on Student object
var prop = type.GetProperty(prop_name);
// Get the value of the propery on Student object
var prop_val = prop.GetValue(student) as string;
// Replace value of <text> element in XML
text.Value = prop_val;
}

How to return an XML element value from a Descendant when passing in another element value from that Descendant

I am new to working with LINQ to XML, but I can see how it could be helpful to my current problem. I want a method that you pass in an XML Document and an Element value, the method would then return a different Element Value from the same Descendant. For example if I provided a "StationName" I would like to know what "ScannerType" belongs to that "StationName"
Here is my XML
<?xml version="1.0" encoding="utf-8" ?>
<stations>
<station>
<stationName>CH3CTRM1</stationName>
<scannerType>GE LightSpeed VCT</scannerType>
<scannerID>COL02</scannerID>
<siteName>CUMC</siteName>
<inspDose>180</inspDose>
<expDose>100</expDose>
<kernel>STANDARD</kernel>
</station>
<station>
<stationName>CTAWP75515</stationName>
<scannerType>SIEMENS Force</scannerType>
<scannerID>UIA07</scannerID>
<siteName>Iowa</siteName>
<inspDose>careDose</inspDose>
<expDose>careDose</expDose>
<kernel>Qr40 5</kernel>
</station>
<station>
<stationName>JHEB_CT06N_JHOC2</stationName>
<scannerType>SIEMENS Force</scannerType>
<scannerID>JHU04</scannerID>
<siteName>JHU</siteName>
<inspDose>careDose</inspDose>
<expDose>careDose</expDose>
<kernel>Qr40 5</kernel>
</station>
</stations>
Here are the methods that are currently in question
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace ManualPhantomProcessor.XMLParser
{
class SearchXML
{
public string filename = "SiteData.xml";
public string currentDirectory = Directory.GetCurrentDirectory();
public XDocument LoadXML()
{
string siteDataFilePath = Path.Combine(currentDirectory, filename);
XDocument siteData = XDocument.Load(siteDataFilePath);
return siteData;
}
public IEnumerable<string> GetScannerModel(XDocument xmlDocument, string stationName)
{
var query = xmlDocument.Descendants("station")
.Where(s => s.Element("stationName").Value == stationName)
.Select(s => s.Element("scannerType").Value)
.Distinct();
return query;
}
}
}
Here is my Programs.cs file
using ManualPhantomProcessor.XMLParser;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace ManualPhantomProcessor
{
class Program
{
static void Main(string[] args)
{
SearchXML searchXML = new SearchXML();
XDocument siteData = searchXML.LoadXML();
IEnumerable<string> data = searchXML.GetScannerModel(siteData, "CH3CTRM1");
Console.WriteLine(data);
}
}
}
I should be a simple console application, but it seem like no matter what I try I keep getting a null value when I expect the scannerType value from the XML document that corresponds with the station name "CH3CTRM1"
the application doesn't crash but in my console I get the following:
System.Linq.Enumerable+DistinctIterator`1[System.String]
Could explain what I am doing incorrectly?
Your code is good, the problem is here Console.WriteLine(data); the WriteLine take string like a parameter not a list of string. to display the station names use a loop, like the following code :
foreach(string stationName in data)
{
Console.WriteLine(stationName);
}
The documentation of WriteLine
i hope that will help you fix the issue.
What you're seeing is the string form of the IEnumerable<string> returned by GetScannerModel().
There are two possibilities:
Only one scanner model is expected to be found (because a station name is expected
to be unique).
Any number of scanner models can be found.
In the first case, change GetScannerModel() to return a string and have it do soemthing like return query.FirstOrDefault(); (or .First() if you want an exception if no match was found). Your client program then remains unchanged.
In the second case, #Sajid's answer applies - you need to enumerate the IEnumerable in some way, for example through foreach.
Use a dictionary :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<Station> stations = doc.Descendants("station").Select(x => new Station
{
stationName = (string)x.Element("stationName"),
scannerType = (string)x.Element("scannerType"),
scannerID = (string)x.Element("scannerID"),
siteName = (string)x.Element("siteName"),
inspDose = (string)x.Element("inspDose"),
expDose = (string)x.Element("expDose"),
kernel = (string)x.Element("kernel")
}).ToList();
Dictionary<string, Station> dict = stations
.GroupBy(x => x.stationName, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
public class Station
{
public string stationName {get;set;}
public string scannerType {get;set;}
public string scannerID {get;set;}
public string siteName {get;set;}
public string inspDose {get;set;}
public string expDose {get;set;}
public string kernel { get; set; }
}
}

C# - parse xml nodes

I am loading my data from XML using C# this way:
XmlDocument xmlDoc = new XmlDocument();
TextAsset xmlFile = Resources.Load("levels/" + levelID) as TextAsset;
xmlDoc.LoadXml(xmlFile.text);
XmlNodeList levelsList = xmlDoc.GetElementsByTagName("level");
foreach (XmlNode levelInfo in levelsList)
{
XmlNodeList childNodes = levelInfo.ChildNodes;
foreach (XmlNode value in childNodes)
{
switch (value.Name)
{
case "info":
//levelWidth = getInt(value, 0);
//levelHeight = getInt(value, 1);
break;
}
}
}
And heres XML I am loading:
<?xml version="1.0" encoding="utf-8" ?>
<level>
<info w="1000" h="500"/>
</level>
It works just fine, I am now trying to find best way to load child nodes, inside my level node with multiple points nodes inside
<?xml version="1.0" encoding="utf-8" ?>
<level>
<info w="1000" h="500"/>
<ground>
<point val1="val1" val2="val2"/>
</ground>
</level>
I will be grateful for some guidance how to move in the right direction, thank you.
Using XML Linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
"<level>" +
"<info w=\"1000\" h=\"500\"/>" +
"</level>";
XDocument doc = XDocument.Parse(xml);
XElement level = (XElement)doc.FirstNode;
level.Add("ground", new object[] {
new XElement("point", new XAttribute[] {
new XAttribute("val1", "val1"),
new XAttribute("val2", "val2")
})
});
}
}
}
​
If you need read all points, you can use
var nodeList = Xmldocument.SelectNodes("level/info/ground/point");
SelectNodes return a list of nodes.
I would go for a slidely different way and use a data object. Then you don't have to analyse xml, you just code your data class:
[Serializable()]
public class CLevel
{
public string Info { get; set; }
}
[Serializable()]
public class CDatafile
{
public List<CLevel> LevelList { get; set; }
public CDatafile()
{
LevelList = new List<CLevel>();
}
}
public class DataManager
{
private string FileName = "Data.xml";
public CDatafile Datafile { get; set; }
public DataManager()
{
Datafile = new CDatafile();
}
// Load file
public void LoadFile()
{
if (System.IO.File.Exists(FileName))
{
System.IO.StreamReader srReader = System.IO.File.OpenText(FileName);
Type tType = Datafile.GetType();
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
object oData = xsSerializer.Deserialize(srReader);
Datafile = (CDatafile)oData;
srReader.Close();
}
}
// Save file
public void SaveFile()
{
System.IO.StreamWriter swWriter = System.IO.File.CreateText(FileName);
Type tType = Datafile.GetType();
if (tType.IsSerializable)
{
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
xsSerializer.Serialize(swWriter, Datafile);
swWriter.Close();
}
}
Then you can use it to create, save and load the file like this:
DataManager dataMng = new DataManager();
// Create some data
CLevel level = new CLevel();
level.Info = "Testlevel";
dataMng.Datafile.LevelList.Add(level);
// Save to file
dataMng.SaveFile();
// Load from file
dataMng.LoadFile();
So you can do everything in code checked by the compiler. Makes life a lot easier, or what do you think?

Model to XML file mapping - How to find location of deserialized object in soucre XML File.

Which is the best way to map my model to XML file using c# serializer. I mean that if for example I select an deserialized object I could be able to find the xml source text in XML file.
I got a working sample for you and you can explore further on it.
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.IO;
namespace ConsoleApplication5
{
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public int XMLLine { get; set; }
}
public class Persons : List<Person> { }
class Program
{
static void Main(string[] args)
{
//create your objects
Person p = new Person();
p.Age = 35;
p.Name = "Arnold";
Person p2 = new Person();
p2.Age = 36;
p2.Name = "Tom";
Persons ps = new Persons();
ps.Add(p);
ps.Add(p2);
//Serialize them to XML
XmlSerializer xs = new XmlSerializer(typeof(Persons));
XDocument d = new XDocument();
using (XmlWriter xw = d.CreateWriter())
xs.Serialize(xw, ps);
//print xml
//System.Diagnostics.Debug.WriteLine(d.ToString());
// it will produce following xml. You can save it to file.
//I have saved it to variable xml for demo
string xml = #"<ArrayOfPerson>
<Person>
<Age>35</Age>
<Name>Arnold</Name>
<XMLLine>0</XMLLine>
</Person>
<Person>
<Age>36</Age>
<Name>Tom</Name>
<XMLLine>0</XMLLine>
</Person>
</ArrayOfPerson>";
XDocument xdoc = XDocument.Parse(xml, LoadOptions.SetLineInfo);
// A little trick to get xml line
xdoc.Descendants("Person").All(a => { a.SetElementValue("XMLLine", ((IXmlLineInfo)a).HasLineInfo() ? ((IXmlLineInfo)a).LineNumber : -1); return true; });
//deserialize back to object
Persons pplz = xs.Deserialize((xdoc.CreateReader())) as Persons;
pplz.All(a => { Console.WriteLine(string.Format("Name {0} ,Age{1} ,Line number of object in XML File {2}", a.Name, a.Age, a.XMLLine)); return true; });
Console.ReadLine();
}
}
}
and It will give your results like
Name Arnold ,Age35 ,Line number of object in XML File 2
Name Tom ,Age36 ,Line number of object in XML File 7
You can try this extension method:
public static string ToXml<T>(this object obj)
{
using (var memoryStream = new MemoryStream())
{
using (TextWriter streamWriter = new StreamWriter(memoryStream))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(streamWriter, obj);
return Encoding.ASCII.GetString(memoryStream.ToArray());
}
}
}
public static void ToXmlFile<T>(this object obj, string fileName)
{
using (TextWriter streamWriter = new StreamWriter(fileName))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(streamWriter, obj);
}
}
USAGE:
// you will get this on a string variable
var xmlString = yourModel.ToXml<YourModel>();
// you will save our object in a file.
yourModel.ToXmlFile<YourModel>(#"C:\yourModelDump.xml");
Please be noted to add SerializableAttribute on your class
[Serializable]
public class YourModel
{
//...
}
This should do it

C# XML to Listbox Help

I have XML that is being returned back from a rest service WCF. The following is an example of the XML
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
<catalog version="1.1"><dataset id="354" name="Mariano - Ship Drift" description="Global Currents, Static" datatype="currents" rank="5" saropsrank="4" format="netcdf" starttime="1980-01-01T00:00:00" endtime="2019-01-01T00:00:00" extentleft="-180.0000" extentbottom="-90.0000" extentright="180.0000" extenttop="90.0000" source="STATIC" wmslayeridstr="MARIANO_currents" confidence="L" directionfrom="no" image="ne_ndbc.jpg" />
</catalog>
</string>
I need to get the value from id, name, description, etc... and put it into a list or a listbox.
WebResponse response = restWebRequest.GetResponse();
Stream responseStream = response.GetResponseStream();
Reponse stream is the stream that holds the XML.
Any ideas?
XDocument doc = XDocument.Load(responseStream);
var elem = doc.Descendants().FirstOrDefault(el => el.Name.LocalName == "dataset");
if(elem != null)
{
var attID = elem.Attribute("id");
if(attID != null)
Console.WriteLine(attID.Value);
}
OR
You could directly get the IEnumerable with an anonymous type:
XDocument doc = XDocument.Parse(xml);
var values = doc.Descendants("dataset")
.Select(el => new { id = el.Attribute("id").Value,
name = el.Attribute("name").Value
}
);
Here's another approach: (I tested your XML by loading it from a file.)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
namespace delete4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Load();
}
void Load()
{
var stream = new FileStream("c:\\pj\\data.txt", FileMode.Open);
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "string";
xRoot.IsNullable = true;
xRoot.Namespace = "http://schemas.microsoft.com/2003/10/Serialization/";
XmlSerializer s = new XmlSerializer(typeof(sstring), xRoot);
var o = s.Deserialize(stream) as sstring; // o is your loaded object
stream.Close();
}
[ XmlRoot("string"), XmlType("string")]
public class sstring
{
public Catalog catalog;
}
public class Catalog
{
public Dataset dataset;
}
public class Dataset
{
[XmlAttribute("name")]
public string Name;
[XmlAttribute("id")]
public string ID;
[XmlAttribute("description")]
public string Description;
}
}
}

Categories

Resources