Read nodes of a xml file in C# - c#

How can I read the following xml file into a List:
Partial XML file (data.log)
<ApplicationLogEventObject>
<EventType>Message</EventType>
<DateStamp>10/13/2016 11:15:00 AM</DateStamp>
<ShortDescription>N/A</ShortDescription>
<LongDescription>Sending 'required orders' email.</LongDescription>
</ApplicationLogEventObject>
<ApplicationLogEventObject>
<EventType>Message</EventType>
<DateStamp>10/13/2016 11:15:10 AM</DateStamp>
<ShortDescription>N/A</ShortDescription>
<LongDescription>Branches Not Placed Orders - 1018</LongDescription>
</ApplicationLogEventObject>
<ApplicationLogEventObject>
<EventType>Message</EventType>
<DateStamp>10/13/2016 11:15:10 AM</DateStamp>
<ShortDescription>N/A</ShortDescription>
<LongDescription>Branches Not Placed Orders - 1019</LongDescription>
</ApplicationLogEventObject>
...
And here is the data access layer (DAL):
public List<FLM.DataTypes.ApplicationLogEventObject> Get()
{
try
{
XmlTextReader xmlTextReader = new XmlTextReader(#"C:\data.log");
List<FLM.DataTypes.ApplicationLogEventObject> recordSet = new List<ApplicationLogEventObject>();
xmlTextReader.Read();
while (xmlTextReader.Read())
{
xmlTextReader.MoveToElement();
FLM.DataTypes.ApplicationLogEventObject record = new ApplicationLogEventObject();
record.EventType = xmlTextReader.GetAttribute("EventType").ToString();
record.DateStamp = Convert.ToDateTime(xmlTextReader.GetAttribute("DateStamp"));
record.ShortDescription = xmlTextReader.GetAttribute("ShortDescription").ToString()
record.LongDescription = xmlTextReader.GetAttribute("LongDescription").ToString();
recordSet.Add(record);
}
return recordSet;
}
catch (Exception ex)
{
throw ex;
}
}
And the Data Types which will hold the child elements from the XML file:
public class ApplicationLogEventObject
{
public string EventType { get; set; }
public DateTime DateStamp { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
}
After I've read the child nodes into a List I would then like to return it and display it in a DataGridView.
Any help regarding this question will be much appreciated.

Your log file is not an XML document. Since an XML document must have one and only one root element, it's a series of XML documents concatenated together. Such a series of documents can be read by XmlReader by setting XmlReaderSettings.ConformanceLevel == ConformanceLevel.Fragment. Having done so, you can read through the file and deserialize each root element individually using XmlSerializer as follows:
static List<ApplicationLogEventObject> ReadEvents(string fileName)
{
return ReadObjects<ApplicationLogEventObject>(fileName);
}
static List<T> ReadObjects<T>(string fileName)
{
var list = new List<T>();
var serializer = new XmlSerializer(typeof(T));
var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment };
using (var textReader = new StreamReader(fileName))
using (var xmlTextReader = XmlReader.Create(textReader, settings))
{
while (xmlTextReader.Read())
{ // Skip whitespace
if (xmlTextReader.NodeType == XmlNodeType.Element)
{
using (var subReader = xmlTextReader.ReadSubtree())
{
var logEvent = (T)serializer.Deserialize(subReader);
list.Add(logEvent);
}
}
}
}
return list;
}
Using the following version of ApplicationLogEventObject:
public class ApplicationLogEventObject
{
public string EventType { get; set; }
[XmlElement("DateStamp")]
public string DateStampString {
get
{
// Replace with culturally invariant desired formatting.
return DateStamp.ToString(CultureInfo.InvariantCulture);
}
set
{
DateStamp = Convert.ToDateTime(value, CultureInfo.InvariantCulture);
}
}
[XmlIgnore]
public DateTime DateStamp { get; set; }
public string ShortDescription { get; set; }
public string LongDescription { get; set; }
}
Sample .Net fiddle.
Notes:
The <DateStamp> element values 10/13/2016 11:15:00 AM are not in the correct format for dates and times in XML, which is ISO 8601. Thus I introduced a surrogate string DateStampString property to manually handle the conversion from and to your desired format, and then marked the original DateTime property with XmlIgnore.
Using ReadSubtree() prevents the possibility of reading past the end of each root element when the XML is not indented.
According to the documentation for XmlTextReader:
Starting with the .NET Framework 2.0, we recommend that you use the System.Xml.XmlReader class instead.
Thus I recommend replacing use of that type with XmlReader.
The child nodes of your <ApplicationLogEventObject> are elements not attributes, so XmlReader.GetAttribute() was not an appropriate method to use to read them.
Given that your log files are not formatting their times in ISO 8601, you should at least make sure they are formatted in a culturally invariant format so that log files can be exchanged between computers with different regional settings. Doing your conversions using CultureInfo.InvariantCulture ensures this.

Related

Add dynamic comments to an xml file using XmlSerializer [duplicate]

I have an object Foo which I serialize to an XML stream.
public class Foo {
// The application version, NOT the file version!
public string Version {get;set;}
public string Name {get;set;}
}
Foo foo = new Foo { Version = "1.0", Name = "Bar" };
XmlSerializer xmlSerializer = new XmlSerializer(foo.GetType());
This works fast, easy and does everything currently required.
The problem I'm having is that I need to maintain a separate documentation file with some minor remarks. As in the above example, Name is obvious, but Version is the application version and not the data file version as one could expect in this case. And I have many more similar little things I want to clarify with a comment.
I know I can do this if I manually create my XML file using the WriteComment() function, but is there a possible attribute or alternative syntax I can implement so that I can keep using the serializer functionality?
This is possible using the default infrastructure by making use of properties that return an object of type XmlComment and marking those properties with [XmlAnyElement("SomeUniquePropertyName")].
I.e. if you add a property to Foo like this:
public class Foo
{
[XmlAnyElement("VersionComment")]
public XmlComment VersionComment { get { return new XmlDocument().CreateComment("The application version, NOT the file version!"); } set { } }
public string Version { get; set; }
public string Name { get; set; }
}
The following XML will be generated:
<Foo>
<!--The application version, NOT the file version!-->
<Version>1.0</Version>
<Name>Bar</Name>
</Foo>
However, the question is asking for more than this, namely some way to look up the comment in a documentation system. The following accomplishes this by using extension methods to look up the documentation based on the reflected comment property name:
public class Foo
{
[XmlAnyElement("VersionXmlComment")]
public XmlComment VersionXmlComment { get { return GetType().GetXmlComment(); } set { } }
[XmlComment("The application version, NOT the file version!")]
public string Version { get; set; }
[XmlAnyElement("NameXmlComment")]
public XmlComment NameXmlComment { get { return GetType().GetXmlComment(); } set { } }
[XmlComment("The application name, NOT the file name!")]
public string Name { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
public XmlCommentAttribute(string value)
{
this.Value = value;
}
public string Value { get; set; }
}
public static class XmlCommentExtensions
{
const string XmlCommentPropertyPostfix = "XmlComment";
static XmlCommentAttribute GetXmlCommentAttribute(this Type type, string memberName)
{
var member = type.GetProperty(memberName);
if (member == null)
return null;
var attr = member.GetCustomAttribute<XmlCommentAttribute>();
return attr;
}
public static XmlComment GetXmlComment(this Type type, [CallerMemberName] string memberName = "")
{
var attr = GetXmlCommentAttribute(type, memberName);
if (attr == null)
{
if (memberName.EndsWith(XmlCommentPropertyPostfix))
attr = GetXmlCommentAttribute(type, memberName.Substring(0, memberName.Length - XmlCommentPropertyPostfix.Length));
}
if (attr == null || string.IsNullOrEmpty(attr.Value))
return null;
return new XmlDocument().CreateComment(attr.Value);
}
}
For which the following XML is generated:
<Foo>
<!--The application version, NOT the file version!-->
<Version>1.0</Version>
<!--The application name, NOT the file name!-->
<Name>Bar</Name>
</Foo>
Notes:
The extension method XmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName) assumes that the comment property will be named xxxXmlComment where xxx is the "real" property. If so, it can automatically determine the real property name by marking the incoming memberName attribute with CallerMemberNameAttribute. This can be overridden manually by passing in the real name.
Once the type and member name are known, the extension method looks up the relevant comment by searching for an [XmlComment] attribute applied to the property. This could be replaced with a cached lookup into a separate documentation file.
While it is still necessary to add the xxxXmlComment properties for each property that might be commented, this is likely to be less burdensome than implementing IXmlSerializable directly which is quite tricky, can lead to bugs in deserialization, and can require nested serialization of complex child properties.
To ensure that each comment precedes its associated element, see Controlling order of serialization in C#.
For XmlSerializer to serialize a property it must have both a getter and setter. Thus I gave the comment properties setters that do nothing.
Working .Net fiddle.
Isn't possible using default infrastructure. You need to implement IXmlSerializable for your purposes.
Very simple implementation:
public class Foo : IXmlSerializable
{
[XmlComment(Value = "The application version, NOT the file version!")]
public string Version { get; set; }
public string Name { get; set; }
public void WriteXml(XmlWriter writer)
{
var properties = GetType().GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
{
writer.WriteComment(
propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
.Cast<XmlCommentAttribute>().Single().Value);
}
writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());
}
}
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
public string Value { get; set; }
}
Output:
<?xml version="1.0" encoding="utf-16"?>
<Foo>
<!--The application version, NOT the file version!-->
<Version>1.2</Version>
<Name>A</Name>
</Foo>
Another way, maybe preferable: serialize with default serializer, then perform post-processing, i.e. update XML, e.g. using XDocument or XmlDocument.
Add comment at the end of xml after serialization (magic is to flush xmlWriter).
byte[] buffer;
XmlSerializer serializer = new XmlSerializer(result.GetType());
var settings = new XmlWriterSettings() { Encoding = Encoding.UTF8 };
using (MemoryStream memoryStream = new MemoryStream())
{
using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
{
serializer.Serialize(xmlWriter, result);
xmlWriter.WriteComment("test");
xmlWriter.Flush();
buffer = memoryStream.ToArray();
}
}
Probably late to the party but I had problems when I was trying to deserialize using Kirill Polishchuk solution. Finally I decided to edit the XML after serializing it and the solution looks like:
public static void WriteXml(object objectToSerialize, string path)
{
try
{
using (var w = new XmlTextWriter(path, null))
{
w.Formatting = Formatting.Indented;
var serializer = new XmlSerializer(objectToSerialize.GetType());
serializer.Serialize(w, objectToSerialize);
}
WriteComments(objectToSerialize, path);
}
catch (Exception e)
{
throw new Exception($"Could not save xml to path {path}. Details: {e}");
}
}
public static T ReadXml<T>(string path) where T:class, new()
{
if (!File.Exists(path))
return null;
try
{
using (TextReader r = new StreamReader(path))
{
var deserializer = new XmlSerializer(typeof(T));
var structure = (T)deserializer.Deserialize(r);
return structure;
}
}
catch (Exception e)
{
throw new Exception($"Could not open and read file from path {path}. Details: {e}");
}
}
private static void WriteComments(object objectToSerialize, string path)
{
try
{
var propertyComments = GetPropertiesAndComments(objectToSerialize);
if (!propertyComments.Any()) return;
var doc = new XmlDocument();
doc.Load(path);
var parent = doc.SelectSingleNode(objectToSerialize.GetType().Name);
if (parent == null) return;
var childNodes = parent.ChildNodes.Cast<XmlNode>().Where(n => propertyComments.ContainsKey(n.Name));
foreach (var child in childNodes)
{
parent.InsertBefore(doc.CreateComment(propertyComments[child.Name]), child);
}
doc.Save(path);
}
catch (Exception)
{
// ignored
}
}
private static Dictionary<string, string> GetPropertiesAndComments(object objectToSerialize)
{
var propertyComments = objectToSerialize.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(typeof(XmlCommentAttribute), false).Any())
.Select(v => new
{
v.Name,
((XmlCommentAttribute) v.GetCustomAttributes(typeof(XmlCommentAttribute), false)[0]).Value
})
.ToDictionary(t => t.Name, t => t.Value);
return propertyComments;
}
[AttributeUsage(AttributeTargets.Property)]
public class XmlCommentAttribute : Attribute
{
public string Value { get; set; }
}
Proposed solution by user dbc looks fine, however it seems to need more manual work to create such comments than using an XmlWriter that knows how to insert comments based on XmlComment attributes.
See https://archive.codeplex.com/?p=xmlcomment - it seems you can pass such a writer to XmlSerializer and thus not have to implement your own serialization which could be tricky.
I did myself end up using dbc's solution though, nice and clean with no extra code. See https://dotnetfiddle.net/Bvbi0N. Make sure you provide a "set" accessor for the comment element (the XmlAnyElement). It doesn't need to have a name btw.
Update: better pass a unique name always, aka use [XmlAnyElement("someCommentElement")] instead of [XmlAnyElement]. Was using the same class with WCF and it was choking upon those XmlAnyElements that didn't have a name provided, even though I had [XmlIgnore, SoapIgnore, IgnoreDataMember] at all of them.
for nested xml, I changed the method this way(for me i was having simple property as string(its possible to make it more complex in the logic)
public void WriteXml(XmlWriter writer)
{
var properties = GetType().GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
{
writer.WriteComment(
propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
.Cast<XmlCommentAttribute>().Single().Value);
}
if (propertyInfo.GetValue(this, null).GetType().ToString() != "System.String")
{
XmlSerializer xmlSerializer = new XmlSerializer(propertyInfo.GetValue(this, null).GetType());
xmlSerializer.Serialize(writer, propertyInfo.GetValue(this, null));
}
else
{
writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());
}
}
}

U-SQL custom extractor on custom row delimiter and json

I have several text files with the following data structure:
{
huge
json
block that spans across multiple lines
}
--#newjson#--
{
huge
json
block that spans across multiple lines
}
--#newjson#--
{
huge
json
block that spans across multiple lines
} etc....
So it is actually json blocks that are row delimited by "--##newjson##--" string .
I am trying to write a customer extractor to parse this. The problem is that I can't use string data type to feed json deserializer because it has a maximum size of 128 KB and the json blocks do not fit in this. What is the best approach to parse this file using a custom extractor?
I have tried using the code below, but it doesn't work. Even the row delimiter "--#newjson#--" doesn't seem to work right.
public SampleExtractor(Encoding encoding, string row_delim = "--#newjson#--", char col_delim = ';')
{
this._encoding = ((encoding == null) ? Encoding.UTF8 : encoding);
this._row_delim = this._encoding.GetBytes(row_delim);
this._col_delim = col_delim;
}
public override IEnumerable<IRow> Extract(IUnstructuredReader input, IUpdatableRow output)
{
//Read the input by json
foreach (Stream current in input.Split(_encoding.GetBytes("--#newjson#--")))
{
var serializer = new JsonSerializer();
using (var sr = new StreamReader(current))
using (var jsonTextReader = new JsonTextReader(sr))
{
var jsonrow = serializer.Deserialize<JsonRow>(jsonTextReader);
output.Set(0, jsonrow.status.timestamp);
}
yield return output.AsReadOnly();
}
}
You dont need a custom extractor to do that.
The best solution is add one json by line. Then you can use a text extractor and extract line by line. You can also pick your own delimiter.
REFERENCE ASSEMBLY [Newtonsoft.Json];
REFERENCE ASSEMBLY [Microsoft.Analytics.Samples.Formats];
#JsonLines=
EXTRACT
[JsonLine] string
FROM
#Full_Path
USING
Extractors.Text(delimiter:'\b', quoting : false);
#ParsedJSONLines =
SELECT
Microsoft.Analytics.Samples.Formats.Json.JsonFunctions.JsonTuple([JsonLine]) AS JSONLine
FROM
#JsonLines
#AccessToProperties=
SELECT
JSONLine["Property"] AS Property
FROM
#ParsedJSONLines;
Here is how you can achieve the solution:
1) Create a c# equivalent of your JSON object
Note:- Assuming all your json object are same in your text file.
E.g:
Json Code
{
"id": 1,
"value": "hello",
"another_value": "world",
"value_obj": {
"name": "obj1"
},
"value_list": [
1,
2,
3
]
}
C# Equivalent
public class ValueObj
{
public string name { get; set; }
}
public class RootObject
{
public int id { get; set; }
public string value { get; set; }
public string another_value { get; set; }
public ValueObj value_obj { get; set; }
public List<int> value_list { get; set; }
}
2) Change your de-serializing code like below after you have done the split based on the delimiter
using (JsonReader reader = new JsonTextReader(sr))
{
while (!sr.EndOfStream)
{
o = serializer.Deserialize<List<MyObject>>(reader);
}
}
This would deserialize the json data in c# class object which would solve your purpose.
Later which you can serialize again or print it in text or ...any file.
Hope it helps.

Serialization: Dynamic class names

I have already tried various possibilities but maybe I am just too tired of seeing the solution -.-
I have an xml structure like this:
<diagnosisList>
<diagnosis>
<surgery1>
<date>1957-08-13</date>
<description>a</description>
<ops301>0-000</ops301>
</surgery1>
<surgery2>
<date>1957-08-13</date>
<description>a</description>
<ops301>0-000</ops301>
</surgery2>
<surgery...>
</surgery...>
</diagnosis>
</diagnosisList>
As you see there is a variable number of surgeries. I have a class "surgery" containing the XML elements.
class Surgery
{
[XmlElement("date")]
public string date { get; set; }
[XmlElement("description")]
public string description { get; set; }
[XmlElement("ops301")]
public string ops301 { get; set; }
public Surgery()
{
}
}
and a class diagnosis creating the structure by adding the surgery class to the constructor.
diagnosis.cs
class Diagnosis
{
[XmlElement("surgery")]
public Surgery surgery
{
get;
set;
}
public Diagnosis(Surgery Surgery)
{
surgery = Surgery;
}
}
I need to be able to serialize the class name of the surgery dynamically by adding a number before serialization happens.
does anybody know a way to achieve that?
any help is really appreciated :)
Kind regards
Sandro
-- EDIT
I create the whole structure starting from my root class "Import". this class then will be passed to the serializer. So I cannot use XMLWriter in the middle of creation of the structure. I Need to create the whole structure first and finally it will be serialized:
private static void XmlFileSerialization(Import import)
{
string filename = #"c:\dump\trauma.xml";
// default file serialization
XmlSerializer<Import>.SerializeToFile(import, filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Encoding = Encoding.UTF8;
settings.Indent = true;
settings.IndentChars = "\t";
XmlSerializer<Import>.SerializeToFile(import, filename, namespaces, settings);
}
and then in the Method "SerializeToFile"
public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
{
if (source == null)
throw new ArgumentNullException("source", "Object to serialize cannot be null");
XmlSerializer serializer = new XmlSerializer(source.GetType());
using (XmlWriter xmlWriter = XmlWriter.Create(filename, settings))
{
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
x.Serialize(xmlWriter, source, namespaces);
}
}
}
What I Need is to be able to instantiate a variable number of classes based on the main class "Surgery". The class must have a variable Name, i.e.
surgery1, surgery2, surgery3, etc.
This cannot be changed because this is given by the Institution defining the XML structure.
the class must be accessible by its dynamic Name because the property in the class must be set.
so:
surgery1.Property = "blabla";
surgery2. Property = "babla";
etc.
I am even thinking about using T4 methods to create this part of code, but there must be another way to achieve dynamic class names.
I also thought of creating instances with variable names of the class by using reflection:
System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(string className)
But this doesn't work actually -.-
Does anybody have a hint and could put me in the right direction?
I think, you could try implement methods from IXmlSerializable in object contains diagnosisList.
Try to use custom xml writer and reader.
public class SurgeryWriter : XmlTextWriter
{
public SurgeryWriter(string url) : base(url, Encoding.UTF8) { }
private int counter = 1;
public override void WriteStartElement(string prefix, string localName, string ns)
{
if (localName == "surgery")
{
base.WriteStartElement(prefix, "surgery" + counter, ns);
counter++;
}
else
base.WriteStartElement(prefix, localName, ns);
}
}
public class SurgeryReader : XmlTextReader
{
public SurgeryReader(string url) : base(url) { }
public override string LocalName
{
get
{
if (base.LocalName.StartsWith("surgery"))
return "surgery";
return base.LocalName;
}
}
}
Classes:
[XmlRoot("diagnosisList")]
public class DiagnosisList
{
[XmlArray("diagnosis")]
[XmlArrayItem("surgery")]
public Surgery[] Diagnosis { get; set; }
}
[XmlRoot("surgery")]
public class Surgery
{
[XmlElement("date", DataType = "date")]
public DateTime Date { get; set; }
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("ops301")]
public string Ops301 { get; set; }
}
Use:
var xs = new XmlSerializer(typeof(DiagnosisList));
DiagnosisList diagnosisList;
using (var reader = new SurgeryReader("test.xml"))
diagnosisList = (DiagnosisList)xs.Deserialize(reader);
using (var writer = new SurgeryWriter("test2.xml"))
xs.Serialize(writer, diagnosisList);
Don't mix XML and C#.
You don't need dynamic names in the C# code!
If you need an arbitrary number of instances of a class, create them in a loop and place it in any collection.
var surgeries = new List<Surgery>();
for (int i = 0; i < 10; i++)
{
var surgery = new Surgery();
surgeries.Add(surgery);
}
Later you can access them by index or by enumerating.
surgeries[5]
foreach (var surgery in surgeries)
{
// use surgery
}
As you can see no need dynamic names!
Alternatively, use the dictionary with arbitrary names as keys.
var surgeryDict = new Dictionary<string, Surgery>();
for (int i = 0; i < 10; i++)
{
var surgery = new Surgery();
surgeryDict["surgery" + i] = surgery;
}
Access by name:
surgeryDict["surgery5"]

Deserialize nested XML element into class in C#

I have the following XML structure (edited for brevity) which I have no control over.
<GetVehicles>
<ApplicationArea>
<Sender>
<Blah></Blah>
</Sender>
</ApplicationArea>
<DataArea>
<Error>
<Blah></Blah>
</Error>
<Vehicles>
<Vehicle>
<Colour>Blue</Colour>
<NumOfDoors>3</NumOfDoors>
<BodyStyle>Hatchback</BodyStyle>
<Vehicle>
</Vehicles>
</DataArea>
</GetVehicles>
I have the following Class:
[XmlRoot("GetVehicles"), XmlType("Vehicle")]
public class Vehicle
{
public string Colour { get; set; }
public string NumOfDoors { get; set; }
public string BodyStyle { get; set; }
}
I want to be able to deserialize the XML into a single instance of this Vehicle class. 99% of the time, the XML should only return a single 'Vehicle' element. I'm not yet dealing with it yet if it contains multiple 'Vehicle' elements inside the 'Vehicles' element.
Unfortunately, the XML data isn't currently being mapped to my class properties; they are being left blank upon calling my Deserialize method.
For completeness, here is my Deserialize method:
private static T Deserialize<T>(string data) where T : class, new()
{
if (string.IsNullOrEmpty(data))
return null;
var ser = new XmlSerializer(typeof(T));
using (var sr = new StringReader(data))
{
return (T)ser.Deserialize(sr);
}
}
I don't care about the other more parent elements such as 'ApplicationArea', 'Error' etc. I am only interesting in extracting the data within the 'Vehicle' element. How do I get it to only deserialize this data from the XML?
Building on Ilya's answer:
This can be optimized slightly, since there is already a loaded node: there is no need to pass the xml down to a string (using vehicle.ToString()), only to cause the serializer to have to re-parse it (using a StringReader).
Instead, we can created a reader directly using XNode.CreateReader:
private static T Deserialize<T>(XNode data) where T : class, new()
{
if (data == null)
return null;
var ser = new XmlSerializer(typeof(T));
return (T)ser.Deserialize(data.CreateReader());
}
Or if data is a XmlNode, use a XmlNodeReader:
private static T Deserialize<T>(XmlNode data) where T : class, new()
{
if (data == null)
return null;
var ser = new XmlSerializer(typeof(T));
using (var xmlNodeReader = new XmlNodeReader(data))
{
return (T)ser.Deserialize(xmlNodeReader);
}
}
We can then use:
var vehicle = XDocument.Parse(xml)
.Descendants("Vehicle")
.First();
Vehicle v = Deserialize<Vehicle>(vehicle);
I'm not aware of the full context of your problem, so this solution might not fit into your domain. But one solution is to define your model as:
[XmlRoot("Vehicle")] //<-- optional
public class Vehicle
{
public string Colour { get; set; }
public string NumOfDoors { get; set; }
public string BodyStyle { get; set; }
}
and pass specific node into Deserialize method using LINQ to XML:
var vehicle = XDocument.Parse(xml)
.Descendants("Vehicle")
.First();
Vehicle v = Deserialize<Vehicle>(vehicle.ToString());
//display contents of v
Console.WriteLine(v.BodyStyle); //prints Hatchback
Console.WriteLine(v.Colour); //prints Blue
Console.WriteLine(v.NumOfDoors); //prints 3

Using XMLReader to read large XML documents to parse the information into a class

I have been using XDocument combined with LINQ to XML to load in xml files and populate my class.
But now I am tasked with making sure my program can handle all sizes of XML documents which means i need to use XML Reader and at this time being i cant get my head around manipulating the XMLReader to populate my class.
currently i have the below class to populate:
public class DataRecord
{
private List<Fields> field = new List<Fields>();
public string ID { get; set; }
public string TotalLength { get; set; }
public List<Fields> MyProperty
{
get { return field; }
set { field = value; }
}
}
internal class Fields
{
public string name { get; set; }
public string startByte { get; set; }
public string length { get; set; }
}
}
I have been trying to switch statements to enforce the xmlreader to provide the data from me to populate the class. For example:
using (XmlReader reader = XmlReader.Create(filename))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
switch (reader.Name)
{
case "DataRecord":
var dataaa = new dataclass.DataRecord();
break;
}
break;
}
}
}
But as i said this is an example, I have searched for ages to try and find an answer but I am getting confused. Hopefully someone can help we my problem.
You can use XmlReader to move through the document, but then load each element using XElement.
Here's a short example:
using System;
using System.Xml;
using System.Xml.Linq;
class Test
{
static void Main()
{
using (var reader = XmlReader.Create("test.xml"))
{
while (reader.ReadToFollowing("foo"))
{
XElement element = XElement.Load(reader.ReadSubtree());
Console.WriteLine("Title: {0}", element.Attribute("title").Value);
}
}
}
}
With sample XML:
<data>
<foo title="x" /><foo title="y">asd</foo> <foo title="z" />
</data>
(Slightly inconsistent just to show that it can handle elements with content, elements with no space between them, and elements with space between them.)
Then obviously in the loop you'd do whatever you need to with the XElement - if you've already got a way of creating an instance of your class from an XElement, you can just call that, use the object, and you're away.

Categories