Read XML to linq object, then create XML - c#

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<pp010 xmlns="http://www.123456768.com/technology">
<rptHdr>
<exchNam>MyXML</exchNam>
<envText>P</envText>
<rptCod>pp010</rptCod>
<rptNam>Daily Stock</rptNam>
<membLglNam>CompanyA</membLglNam>
<rptPrntEffDat>2015-04-14</rptPrntEffDat>
<rptPrntRunDat>2015-04-14</rptPrntRunDat>
</rptHdr>
<pp010Grp>
<pp010KeyGrp>
<membClgIdCod>HBGKP</membClgIdCod>
</pp010KeyGrp>
<pp010Grp1>
<pp010KeyGrp1>
<membExchIdCod>JBGJG</membExchIdCod>
</pp010KeyGrp1>
<pp010Grp2>
<pp010KeyGrp2>
<currTypCod>CHF</currTypCod>
</pp010KeyGrp2>
<pp010Grp3>
<pp010KeyGrp3>
<acctTypGrp>PP</acctTypGrp>
</pp010KeyGrp3>
<pp010Rec>
<mgnGrpCod> </mgnGrpCod>
<mgnClsCod>CSLN </mgnClsCod>
<mgnPremiumAmnt>+222926.00</mgnPremiumAmnt>
<mgnLiqDlvAmnt>+0.00</mgnLiqDlvAmnt>
<mgnSprdAmnt>+0.00</mgnSprdAmnt>
<mgnAddlAmnt>+89349.30</mgnAddlAmnt>
<unadjMgnReq>+312275.30</unadjMgnReq>
</pp010Rec>
<pp010Rec>
<mgnGrpCod> </mgnGrpCod>
<mgnClsCod>CSLM </mgnClsCod>
<mgnPremiumAmnt>+55112.00</mgnPremiumAmnt>
<mgnLiqDlvAmnt>+0.00</mgnLiqDlvAmnt>
<mgnSprdAmnt>+0.00</mgnSprdAmnt>
<mgnAddlAmnt>+30854.40</mgnAddlAmnt>
<unadjMgnReq>+85966.40</unadjMgnReq>
</pp010Rec>
I am using the following code but cannot seem to create an IEnumberable with the data from ...
public class MarginRep
{
public string mgnGrpCod {get;set;}
public string mgnClsCod {get;set;}
public string mgnPremiumAmnt {get;set;}
public string mgnLiqDlvAmnt {get;set;}
public string mgnSprdAmnt {get;set;}
public string mgnAddlAmnt {get;set;}
public string unadjMgnReq {get;set;}
}
private void button1_Click(object sender, EventArgs e)
{
string file = #"D:\WorkDesktop\VisualStudio\file.xml";
XDocument xmlDoc = XDocument.Load(file);
IEnumerable<MarginRep> myMarginRep =
from c in xmlDoc.Descendants("pp010Rec")
select new MarginRep()
{
mgnGrpCod = (string)c.Attribute("mgnGrpCod"),
mgnClsCod = (string)c.Attribute("mgnClsCod"),
mgnPremiumAmnt = (string)c.Attribute("mgnPremiumAmnt"),
mgnLiqDlvAmnt = (string)c.Attribute("mgnLiqDlvAmnt"),
mgnSprdAmnt = (string)c.Attribute("mgnSprdAmnt"),
mgnAddlAmnt = (string)c.Attribute("mgnAddlAmnt"),
unadjMgnReq = (string)c.Attribute("unadjMgnReq"),
};
}
Sorry for the large amount of the XML. I felt i needed to display it as the first couple of lines i dont need and cannot seem to navigate down the pp010Rec
IF you can offer any help I would be grateful, if you can point me into the direction literature I can spend time reading and try it alone.
Cheers,

It seems you want the values not the attributes, so change:
mgnGrpCod = (string)c.Attribute("mgnGrpCod"),
To:
mgnGrpCod = (string)c.Element("mgnGrpCod").Value,
Do the same with the other properties

I a bit changed .xml:
<?xml version="1.0" encoding="UTF-8"?>
<pp010 xmlns="http://www.123456768.com/technology">
<rptHdr>
<exchNam>MyXML</exchNam>
<envText>P</envText>
<rptCod>pp010</rptCod>
<rptNam>Daily Stock</rptNam>
<membLglNam>CompanyA</membLglNam>
<rptPrntEffDat>2015-04-14</rptPrntEffDat>
<rptPrntRunDat>2015-04-14</rptPrntRunDat>
</rptHdr>
<pp010Grp>
<pp0510KeyGrp>
<membClgIdCod>HBGKP</membClgIdCod>
</pp0510KeyGrp>
<pp010Grp1>
<pp010KeyGrp1>
<membExchIdCod>JBGJG</membExchIdCod>
</pp010KeyGrp1>
<pp010Grp2>
<pp010KeyGrp2>
<currTypCod>CHF</currTypCod>
</pp010KeyGrp2>
<pp010Grp3>
<pp010KeyGrp3>
<acctTypGrp>PP</acctTypGrp>
</pp010KeyGrp3>
<pp010Rec>
<mgnGrpCod> </mgnGrpCod>
<mgnClsCod>CSLN </mgnClsCod>
<mgnPremiumAmnt>+222926.00</mgnPremiumAmnt>
<mgnLiqDlvAmnt>+0.00</mgnLiqDlvAmnt>
<mgnSprdAmnt>+0.00</mgnSprdAmnt>
<mgnAddlAmnt>+89349.30</mgnAddlAmnt>
<unadjMgnReq>+312275.30</unadjMgnReq>
</pp010Rec>
<pp010Rec>
<mgnGrpCod> </mgnGrpCod>
<mgnClsCod>CSLM </mgnClsCod>
<mgnPremiumAmnt>+55112.00</mgnPremiumAmnt>
<mgnLiqDlvAmnt>+0.00</mgnLiqDlvAmnt>
<mgnSprdAmnt>+0.00</mgnSprdAmnt>
<mgnAddlAmnt>+30854.40</mgnAddlAmnt>
<unadjMgnReq>+85966.40</unadjMgnReq>
</pp010Rec>
</pp010Grp3>
</pp010Grp2>
</pp010Grp1>
</pp010Grp>
</pp010>
So you can run
public class MarginRep
{
public string mgnGrpCod { get; set; }
public string mgnClsCod { get; set; }
public string mgnPremiumAmnt { get; set; }
public string mgnLiqDlvAmnt { get; set; }
public string mgnSprdAmnt { get; set; }
public string mgnAddlAmnt { get; set; }
public string unadjMgnReq { get; set; }
public override string ToString()
{
return string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}",
mgnGrpCod, mgnClsCod, mgnPremiumAmnt, mgnLiqDlvAmnt, mgnSprdAmnt,
mgnAddlAmnt, unadjMgnReq);
}
}
var xmlDoc = XDocument.Load("1.xml");
XNamespace xn = xmlDoc.Root.Name.Namespace;
IEnumerable<MarginRep> myMarginRep = xmlDoc.Root.Descendants(xn + "pp010Rec")
.Select(c => new MarginRep()
{
mgnGrpCod = c.Element(xn + "mgnGrpCod").Value,
mgnClsCod = c.Element(xn + "mgnClsCod").Value,
mgnPremiumAmnt = c.Element(xn + "mgnPremiumAmnt").Value,
mgnLiqDlvAmnt = c.Element(xn + "mgnLiqDlvAmnt").Value,
mgnSprdAmnt = c.Element(xn + "mgnSprdAmnt").Value,
mgnAddlAmnt = c.Element(xn + "mgnAddlAmnt").Value,
unadjMgnReq = c.Element(xn + "unadjMgnReq").Value
});
foreach (var x in myMarginRep)
Console.WriteLine(x);
Print:
CSLN
+222926.00
+0.00
+0.00
+89349.30
+312275.30
CSLM
+55112.00
+0.00
+0.00
+30854.40
+85966.40
Update:
Link: http://rextester.com/UFLPQ70590

You do need to do what JAT said in his post about changing the attributes to elements, but that isn't the reason why your list is not being created from the XML. It's the namespace ("xmlns="http://www.123456768.com/technology") that is giving you trouble. I'm not sure what your XML file is trying to accomplish by having it, but if you remove it and do what JAT recommended your IEnumerable will start to be populated.
For more information about namespaces, you can check out this link from w3:
http://www.w3schools.com/xml/xml_namespaces.asp

Related

How to parse XML data into the properties of a custom C# class?

Setup
I have this in my Main() function.
List<Token> tokens = new List<Token>();
string path = #"\(some directories)\tokens.xml";
XDocument doc = XDocument.Load(path);
I have this class with a few properties.
public partial class Token
{
public Token()
{
SetURLs = new List<string>();
SetNames = new List<string>();
}
public string Name { get; set; }
public List<string> SetURLs { get; set; }
public List<string> SetNames { get; set; }
public string Color { get; set; }
public string PT { get; set; }
public string Text { get; set; }
}
I have this XML file. Here is a snippet.
<?xml version="1.0" encoding="UTF-8"?> //EDIT3
<card_database version="2"> //EDIT3
<cards>
.
.
.
<card>
<name>Griffin</name>
<set picURL="http://magiccards.info/extras/token/duel-decks-ajani-vs-nicol-bolas/griffin.jpg" picURLHq="" picURLSt="">DDH</set>
<color>w</color>
<manacost></manacost>
<type>Token</type>
<pt>2/2</pt>
<tablerow>0</tablerow>
<text>Flying</text>
<token>1</token>
</card>
<card>
<name>Rat</name>
<set picURL="http://magiccards.info/extras/token/shadowmoor/rat.jpg" picURLHq="" picURLSt="">SHM</set>
<set picURL="http://magiccards.info/extras/token/gatecrash/rat.jpg" picURLHq="" picURLSt="">GTC</set>
<color>b</color>
<manacost></manacost>
<type>Token</type>
<pt>1/1</pt>
<tablerow>0</tablerow>
<text></text>
<token>1</token>
</card>
.
.
.
</cards>
</card_database> //EDIT3
As you can see, there are many <card> elements in the <cards> root element. Further, each <card> can have many <set> elements. I made the class accordingly.
Problem
How do I go through one <card> at a time and assign the appropriate values to each property?
What I Have Tried
I made a list that contains all of the <name> elements of each <card>. I would then go through this list and assign a name to a new instance of the Token class's Name property. Then populate my tokens list with each new instance.
List<string> names = new List<string>();
names = doc.Descendants("card").Elements("name").Select(r => r.Value).ToList();
int amount = doc.Descendants("card").Count();
for(int i = 0; i < amount; i++)
{
Token token = new Token();
token.Name = name[i];
.
.
.
tokens.Add(token);
}
I guess I could then make more lists that contain every other desired element and do the same process, but there has to be a more elegant way, right?
EDIT
I also tried serialization from another question. But for some reason, when I tried to write something from token to the console (say token.Name), it didn't write anything.
XmlSerializer serializer = new XmlSerializer(typeof(Token));
using (StringReader reader = new StringReader(path))
{
Token token = (Token)(serializer.Deserialize(reader));
}
Probably just an incorrect implementation. If that is the case, could someone use what I posted and show me the correct implementation? Also, I assume this will give 1 or many values to my two List properties, right?
EDIT
Thanks for the help.
EDIT2
An Answer
After some unsuccessful fiddling with serialization and some more searching, I made an implementation that works.
foreach (var card in doc.Descendants("card"))
{
Token token = new Token();
token.Name = card.Element("name").Value.ToString();
foreach (var set in card.Elements("set"))
{
token.SetURLs.Add(set.Attribute("picURL").Value.ToString());
token.SetNames.Add(set.Value.ToString());
}
token.Color = card.Element("color").Value.ToString();
token.PT = card.Element("pt").Value.ToString();
token.Text = card.Element("text").Value.ToString();
tokens.Add(token);
}
Much better than the number of Lists I first had in mind. Not as succinct as the serialization might have been. However, it does what I need.
Thanks for the help.
EDIT4
Not sure if this many edits are allowed or against etiquette. Just wanted to make this edit for future readers.
The stuff under the "An Answer" section does solve my problem but the XML Serialization posted below by Dave is much better; it is more flexible and easier to reuse/modify. So, pick the solution that has more benefits for your situation.
Using XML Serialization I was able to deserialize your snippet into some objects. I don't really understand your 2 different list variables so i modified it into 1 list.
I am not sure if this is exactly what you are trying to pull off, but i believe it should help you with your xml deserialization of multiple "set" elements.
I created a file with your same snippet named tokens.xml, edited to match your new layout.
<?xml version="1.0" encoding="UTF-8"?>
<card_database version="2">
<cards>
<card>
<name>Griffin</name>
<set picURL="http://magiccards.info/extras/token/duel-decks-ajani-vs-nicol-bolas/griffin.jpg" picURLHq="" picURLSt="">DDH</set>
<color>w</color>
<manacost></manacost>
<type>Token</type>
<pt>2/2</pt>
<tablerow>0</tablerow>
<text>Flying</text>
<token>1</token>
</card>
<card>
<name>Rat</name>
<set picURL="http://magiccards.info/extras/token/shadowmoor/rat.jpg" picURLHq="" picURLSt="">SHM</set>
<set picURL="http://magiccards.info/extras/token/gatecrash/rat.jpg" picURLHq="" picURLSt="">GTC</set>
<color>b</color>
<manacost></manacost>
<type>Token</type>
<pt>1/1</pt>
<tablerow>0</tablerow>
<text></text>
<token>1</token>
</card>
</cards>
</card_database>
I created a few classes
[XmlRoot(ElementName = "card_database")]
public class CardsDatabase
{
public CardsDatabase()
{
}
[XmlElement(ElementName = "cards", Form = XmlSchemaForm.Unqualified)]
public CardsList Cards { get; set; }
[XmlAttribute(AttributeName = "version", Form = XmlSchemaForm.Unqualified)]
public string Version { get; set; }
}
[XmlRoot(ElementName = "cards")]
public class CardsList
{
public CardsList()
{
Cards = new List<Card>();
}
[XmlElement(ElementName = "card", Form = XmlSchemaForm.Unqualified)]
public List<Card> Cards { get; set; }
}
[XmlRoot(ElementName = "card")]
public class Card
{
public Card()
{
SetURLs = new List<SetItem>();
}
[XmlElement(ElementName = "name", Form = XmlSchemaForm.Unqualified)]
public string Name { get; set; }
[XmlElement(ElementName = "set", Form = XmlSchemaForm.Unqualified)]
public List<SetItem> SetURLs { get; set; }
[XmlElement(ElementName = "color", Form = XmlSchemaForm.Unqualified)]
public string Color { get; set; }
[XmlElement(ElementName = "pt", Form = XmlSchemaForm.Unqualified)]
public string PT { get; set; }
[XmlElement(ElementName = "text", Form = XmlSchemaForm.Unqualified)]
public string Text { get; set; }
}
[XmlRoot(ElementName = "set")]
public class SetItem
{
public SetItem()
{
}
[XmlAttribute(AttributeName = "picURL", Form = XmlSchemaForm.Unqualified)]
public string PicURL { get; set; }
[XmlAttribute(AttributeName = "picURLHq", Form = XmlSchemaForm.Unqualified)]
public string PicURLHq { get; set; }
[XmlAttribute(AttributeName = "picURLSt", Form = XmlSchemaForm.Unqualified)]
public string PicURLSt { get; set; }
[XmlText]
public string Value { get; set; }
}
The main body is as follows (I know this is ugly, but i was going fast so please improve)
CardsDatabase cards = new CardsDatabase();
string path = #"tokens.xml";
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlSerializer serializer = new XmlSerializer(typeof(CardsDatabase));
using (StringReader reader = new StringReader(doc.InnerXml))
{
cards = (CardsDatabase)(serializer.Deserialize(reader));
}
The following is what the output looked like.
With Linq to Xml,
string path = #"~/tokens.xml";
var doc = XDocument.Load(Server.MapPath(Url.Content(path)));
var cards = doc.Descendants("card")
.Select(x =>
new Token
{
Name = x.Element("name").Value,
SetURLs = x.Elements("set").Select(y => y.Attribute("picURL").Value)
.ToList(),
SetNames = x.Elements("set").Select(y => y.Value).ToList(),
Color = x.Element("color").Value,
PT = x.Element("pt").Value,
Text = x.Element("text").Value
}).ToList();
hope this helps.
Check out this post:
XPath and *.csproj
But take the below and convert the anonymous types to your concrete class(es).
It should be enough to get you started.
XDocument xDoc = /* populate from somewhere */
XNamespace nsPlaceHolder = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
XNamespace ns = string.Empty;
var list1 = from list in xDoc.Descendants(ns + "cards")
from item in list.Elements(ns + "card")
/* where item.Element(ns + "card") != null */
select new
{
PicURL = item.Attribute("picURL").Value,
MyName = (item.Element(ns + "name") == null) ? string.Empty : item.Element(ns + "name").Value
};
foreach (var v in list1)
{
Console.WriteLine(v.ToString());
}

Read XML file with LINQ and create object with IEnumerable propety

Here is my problem:
I have this XML file:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<app name="Application1">
<log name="Log1" path="d:\paths\" filename="Log1File"/>
<log name="Log2" path="d:\paths\"/>
<log name="log3" path="d:\paths\" filename="Log3File"/>
</app>
</settings>
And I'm trying to read it with LINQ and create object of this class:
public class Apps
{
public string Name { get; set; }
public IEnumerable<Logs> Logs { get; set; }
}
public class Logs
{
public string Name { get; set; }
public string Path { get; set; }
public string Filename { get; set; }
}
So far I managed to create this bit of code however looks like it only gets first log element mean time I need all log elements for each app element:
public static IEnumerable<Apps> GetAllApps()
{
var items = from a in db.Descendants("app")
orderby a.Attribute("name").Value
select new Apps
{
Name = a.Attribute("name").Value,
Logs = from b in a.Descendants("log")
select new Logs
{
Name = b.Attribute("name").Value,
Path = b.Attribute("path").Value,
Filename = b.Attribute("filename").Value
}
};
return items;
}
I would use serialization here
XmlSerializer ser = new XmlSerializer(typeof(Settings));
var result = (Settings)ser.Deserialize(stream);
[XmlRoot("settings")]
public class Settings
{
[XmlElement("app")]
public Apps[] apps;
}
public class Apps
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("log")]
public Logs[] Logs { get; set; }
}
public class Logs
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("path")]
public string Path { get; set; }
[XmlAttribute("filename")]
public string Filename { get; set; }
}
I used fluent API, but let you adapt as you prefer...
Problem is a NullReferenceException, as one of your Logs in xml has no "filename" attribute. And when you use "Value" on a null, you get a NRE.
So, check if the Attribute exists before trying to get it's value.
var it = db.Descendants("app")
.OrderBy(app => app.Attribute("name").Value)
.Select(app => new Apps() {
Name = app.Attribute("name").Value,
Logs = app.Descendants("log").Select(a =>
new Logs() {
Name = a.Attribute("name") != null ? a.Attribute("name").Value : null,
Path = a.Attribute("path") != null ? a.Attribute("path").Value : null,
Filename = a.Attribute("filename") != null ? a.Attribute("filename").Value : null
}).ToList()
}).ToList();

How to load XML Elements using LINQ from XDocument into a class (not using Descendants)

How to load XML Elements using LINQ from XDocument into a c# class. I don't want to use XDocument.Descendants because the settings are not repeated and occur only once in the XML.
This works but I think there must be another way where I don't have to use IEnumerable or use ToArray() and use the first element [0]. Any suggestions?
CODE
public class BackupItem // class needed so LINQ reads XML into an editable DbGrid
{
public bool Backup { get; set; }
public bool IncludeSubDir { get; set; }
public string BackupLabel { get; set; }
public string BackupPath { get; set; }
}
public class BackupSettings
{
public bool IncludeDateStamp { get; set; }
public bool DatePrefix { get; set; }
public bool DateSuffix { get; set; }
public string DateFormat { get; set; }
public bool ZipCompress { get; set; }
public bool ZipPrefix { get; set; }
public bool ZipSuffix { get; set; }
public string ZipText { get; set; }
public bool BackupToSiblingFolder { get; set; }
public string SiblingFolder { get; set; }
public bool BackupToFolder { get; set; }
public bool IncludeFullPath { get; set; }
public string BackupFolder { get; set; }
}
// use a LINQ query to load xml file into datagridview and make datagrid editable (create class with get/set)
var q = from arg in GvXMLDoc.Descendants("BackupItem")
select new BackupItem()
{
Backup = (bool)arg.Element("IncludeDirectory"),
IncludeSubDir = (bool)arg.Element("IncludeSubDirectories"),
BackupLabel = (string)arg.Element("BackupLabel"),
BackupPath = (string)arg.Element("BackupPath")
};
dataGridView1.DataSource = q.ToList();
// load global variable
IEnumerable<BackupSettings> GvBackupSettings = from arg in GvXMLDoc.Element("Backup").Elements("Settings")
select new BackupSettings()
{
IncludeDateStamp = (bool)arg.Element("IncludeDateStamp"),
DatePrefix = (bool)arg.Element("DatePrefix"),
DateSuffix = (bool)arg.Element("DateSuffix"),
DateFormat = (string)arg.Element("DateFormat"),
ZipCompress = (bool)arg.Element("ZipCompress"),
ZipPrefix = (bool)arg.Element("ZipPrefix"),
ZipSuffix = (bool)arg.Element("ZipSuffix"),
ZipText = (string)arg.Element("ZipText"),
BackupToSiblingFolder = (bool)arg.Element("BackupToSiblingFolder"),
SiblingFolder = (string)arg.Element("SiblingFolder"),
BackupToFolder = (bool)arg.Element("BackupToFolder"),
IncludeFullPath = (bool)arg.Element("IncludeFullPath"),
BackupFolder = (string)arg.Element("BackupFolder")
};
I can access the values but it seems a very 'messy way'.
var s = GvBackupSettings.ToArray()[0].DateSuffix;
var t = GvBackupSettings.ToArray()[0].DateFormat;
XML FILE
<?xml version='1.0'?>
<Backup>
<Settings>
<IncludeDateStamp>true</IncludeDateStamp>
<DatePrefix>false</DatePrefix>
<DateSuffix>true</DateSuffix>
<DateFormat>yyyy-MM-dd h.m.s</DateFormat>
<ZipCompress>false</ZipCompress>
<ZipPrefix>false</ZipPrefix>
<ZipSuffix>false</ZipSuffix>
<ZipText></ZipText>
<BackupToSiblingFolder>true</BackupToSiblingFolder>
<SiblingFolder>backups</SiblingFolder>
<BackupToFolder>false</BackupToFolder>
<IncludeFullPath>true</IncludeFullPath>
<BackupFolder>C:\\backup</BackupFolder>
</Settings>
<BackupItem>
<IncludeDirectory>true</IncludeDirectory>
<IncludeSubDirectories>true</IncludeSubDirectories>
<BackupLabel>Backup1</BackupLabel>
<BackupPath>C:\TestFiles\Xml\Samples</BackupPath>
</BackupItem>
<BackupItem>
<IncludeDirectory>true</IncludeDirectory>
<IncludeSubDirectories>false</IncludeSubDirectories>
<BackupLabel>Backup2</BackupLabel>
<BackupPath>C:\TestFiles\Xml\Samples</BackupPath>
</BackupItem>
</Backup>
EDIT 1
I also am trying to deserialize the XML (thanks for idea) so I don't have to cast each item. This does not work.. I don't want to have to run the XSD tool either and have a huge class file...
XmlRootAttribute rootAttribute = new XmlRootAttribute("Backup");
XmlSerializer deserializer = new XmlSerializer((typeof(BackupSettings)), rootAttribute);
GvBackupSettings = (BackupSettings)deserializer.Deserialize(XmlReader.Create(GvXMLFileName));
EDIT 2
Ended up serializing and deserializing xml as suggested below using stardard XSD.exe tool to generate the c# class. Especially as I had to read and write to same xml file. Note: Make sure you verify/modify the generated xsd file first.
You don't need a query if you just need the first element:
XElement settings = GvXMLDoc.Element("Backup").Element("Settings");
BackupSettings GvBackupSettings = new BackupSettings
{
IncludeDateStamp = (bool)settings.Element("IncludeDateStamp"),
DatePrefix = (bool)settings.Element("DatePrefix"),
...
};
var s = GvBackupSettings.DateSuffix;
var t = GvBackupSettings.DateFormat;

Can't read from xml into an <> using linq, get nulls

I have an XML file:
<?xml version="1.0" encoding="us-ascii"?>
<TestItems xmlns="http://www.blogger.com">
<TestItem correct="0" incorrect="0">
<Question>question</Question>
<Answer>answer</Answer>
<Tags>test1;test2</Tags>
</TestItem>
<TestItem correct="0" incorrect="0">
<Question>question3</Question>
<Answer>answer3</Answer>
<Tags>tagA;tagB;tagC</Tags>
</TestItem>
</TestItems>
I also have an object:
class TestItem
{
public string Question { get; set; }
public string Answer { get; set; }
public int NumberCorrect { get; set; }
public int NumberIncorrect { get; set; }
public string Tags { get; set; }
}
And here is the code that I use to read the data into memory:
XDocument ktdb = XDocument.Load("KTdb.xml");
XNamespace ns = ktdb.Root.Name.Namespace;
var testItems = from item in ktdb.Descendants(ns + "TestItem")
select new TestItem
{
Question = item.Element(ns + "Question").Value,
Answer = (string)item.Element(ns + "Answer").Value,
Tags = item.Element(ns + "Tags").Value,
NumberCorrect = Convert.ToInt32(item.Attribute(ns + "correct").Value),
NumberIncorrect = Convert.ToInt32(item.Attribute(ns + "incorrect").Value)
};
But it would not let me do that. When the LINQ statement is executed, the default constructor is called on the TestItem object, and then I get the null exception because item.Element(ns + "Question").Value is null.
Why do I get the error? How do I fix my code?
Thanks.
Change this:
NumberCorrect = Convert.ToInt32(item.Attribute(ns + "correct").Value),
NumberIncorrect = Convert.ToInt32(item.Attribute(ns + "incorrect").Value)
To this:
NumberCorrect = Convert.ToInt32(item.Attribute("correct").Value),
NumberIncorrect = Convert.ToInt32(item.Attribute("incorrect").Value)
i.e. Remove the concatenation of ns from the attribute name.

LINQ XML Read different hierarchies into 1 object

I have an XML file
<searchResponse requestID=“500” status=“success”>
<pso>
<psoID ID=“770e8400-e29b-41d4-a716-446655448549”
targetID=“mezeoAccount”/>
<data>
<email>user2#example.net</email>
<quotaMeg>100</quotaMeg>
<quotaUsed>23</quotaUsed>
<realm>Mezeo</realm>
<path>/san1/</path>
<billing>user2</billing>
<active>true</active>
<unlocked>true</unlocked>
<allowPublic>true</allowPublic>
<bandwidthQuota>1000000000</bandwidthQuota>
<billingDay>1</billingDay>
</data>
</pso>
</searchRequest>
and I want to extract the data into a single business object. Am I better to go
MezeoAccount mcspAccount = new MezeoAccount();
mcspAccount.PsoID = doc.Element("psoID").Attribute("ID").Value;
mcspAccount.Email = doc.Element("email").Value;
...
or build a list even though I know there is only 1 record in the file?
var psoQuery = from pso in doc.Descendants("data")
select new MezeoAccount {
PsoID = pso.Parent.Element("psoID").Attribute("ID").Value,
Email = pso.Element("email").Value,
... };
What would people suggest would be the more correct way, or a better way even, if I missed something.
If you know that your xml only will contain one record of the data in mind you shouldn't create a list for it. So your first example looks fine.
A pattern I personally use is something like this:
public class MezeoAccount
{
public string PsoID { get; set; }
public string Email { get; set; }
public static MezeoAccount CreateFromXml(XmlDocument xml)
{
return new MezeoAccount()
{
PsoID = xml.Element("psoID").Attribute("ID").Value,
Email = doc.Element("email").Value;
};
}
}
//Usage
var mezeoAccount = MezeoAccount.CreateFromXml(xml);
It looks like you didn't get a working answer to this question. Assuming that there can only be one account in the XML file, I would do it like this:
using System;
using System.Linq;
using System.Xml.Linq;
public class MezeoAccount
{
public string PsoId { get; set; }
public string Email { get; set; }
public int QuotaMeg { get; set; }
// Other properties...
}
public class Program
{
public static void Main()
{
XDocument doc = XDocument.Load("input.xml");
XElement pso = doc.Element("searchResponse").Element("pso");
XElement data = pso.Element("data");
MezeoAccount x = new MezeoAccount
{
PsoId = pso.Element("psoID").Attribute("ID").Value,
Email = data.Element("email").Value,
QuotaMeg = int.Parse(data.Element("quotaMeg").Value),
// Other properties...
};
}
}

Categories