In my C# program, I'm loading car data from an xml file into car objects.
This is my xml file:
<Car>
<CarID>1</CarID>
<CarName>Honda</CarName>
<CarColor>Blue</CarColor>
</Car>
<Car>
<CarName>Ford</CarName>
<CarColor>Yellow</CarColor>
</Car>
<Car>
<CarID>3</CarID>
<CarName>BMW</CarName>
<CarColor>Green</CarColor>
</Car>
NOTE THAT the second car entry does NOT have an ID. So I would need to check for this to avoid a null exception.
I load the xml data in my C# code like this:
List<Car> Cars =
(
from el in XDocument.Load("XML_Files/cars.xml").Root.Elements("Car")
select new Car
{
CarID = (int)el.Element("CarID"),
CarName = (string)el.Element("CarName"),
CarColor = (string)el.Element("CarColor")
}).ToList();
I've read in another question that to get around this, for string data, we replace this:
CarName = (string)el.Element("CarName")
with this:
CarName = ((string)el.Element("CarName") != null) ? (string)el.Element("CarName") : string.Empty
That works fine for string values, but what I cannot figure out is how to apply this logic for int values.
So how do I modify this line:
CarID = (int)el.Element("CarID")
To test for the null value?
I've tried this way, but it does not work:
CarID = ((int)el.Element("CarID") >= 0) ? Convert.ToInt32(el.Element("CarID").Value) : 0
Any suggestions?
You're doing two fundamentally different checks in your two examples. In the string example, you're checking if the element is null. In the int example, you're assuming that it exists and jump straight to checking the integer value. Check first that it's not null like you do with the string example.
CarID = (el.Element("CarID") != null) ? Convert.ToInt32(el.Element("CarID").Value) : 0;
Related
I wrote a method to read XML and write information to an object. The XML contains elements with the information, but some of the information is encapsulated and I can't figure out how to get to the information out of it. The XML contains about 200 "results".
XML structure
<result id="xxxxx">
<name>Name</name>
<age>25</age>
<info>
<x>Some text</x>
<y>More Text</y>
</info>
</result>
Code
XDocument rootDocument = XDocument.Load(file);
var xy = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name")
Age = r.Element("age"),
x = r.Element("info").Element("x"),
y = r.Element("info").Element("y"),
};
foreach (var r in xy)
{
Object o = new Object()
{
Name = r.Name,
Age = r.Age,
x = r.x,
y = r.y
};
}
Error
Object reference not set to an instance of an object.
The Error occurs at the line
x = r.Element("info")...
and the following one.
You could do the following:
var query = from r in rootDocument.Descendants("result")
select new
{
Name = (string)r.Element("name"),
Age = (int?)r.Element("age"),
x = (string)r.Elements("info").Elements("x").SingleOrDefault(),
y = (string)r.Elements("info").Elements("x").SingleOrDefault(),
};
var resultList = query.ToList();
Notes:
Once you have selected an XElement with a primitive value, you can convert the element to a c# primitive such as string or int? by using one of XElement's explicit casting operators, like so:
Name = (string)r.Element("name")
Age = (int?)r.Element("age")
The fact that you are seeing an Object reference not set to an instance of an object exception suggests that an element is unexpectedly missing. That could easily happen if one of the <result> elements were missing an <info> child element. The expression
r.Elements("info").Elements("x")
returns all child elements named <x> of child element(s) named <info>. Then SingleOrDefault() returns the only element of that sequence, or a default value if the sequence is empty. This protects against the situation when an <info> is missing.
Similarly, if the <age> element is missing, trying to cast it to int would throw a null reference exception since int is a value type. Casting to int? instead returns null instead of throwing the exception.
The final ToList() evaluates the query and returns the results in a list.
Sample fiddle.
I have an xml schema like below
<library>
<book>
<id>1</id>
<name>abc</name>
<read>
<data>yes</data>
<num>20</num>
</read>
</book>
<book>
<id>20</id>
<name>xyz</name>
<read>
<data>yes</data>
<num>32</num>
</read>
</book>
</library>
Now if the id is 20 i need to take the value of tag <num> under <read>
I done the code as below
var xmlStr = File.ReadAllText("e_test.xml");
var str = XElement.Parse(xmlStr);
var result = str.Elements("book").Where(x => x.Element("id").Value.Equals("20")).ToList();
this give the whole <book> tag with id 20. From this how can I extract only the value of tag <num>.
ie is i need to get the value 32 in to a variable
Before you try to extract the num value, you need to fix your Where clause - at the moment you're comparing a string with an integer. The simplest fix - if you know that your XML will always have an id element which has a textual value which is an integer - is to cast the element to int.
Next, I'd use SingleOrDefault to make sure there's at most one such element, assuming that's what your XML document should have.
Then you just need to use Element twice to navigate down via read and then num, and cast the result to int again:
// Or use XDocument doc = ...; XElement book = doc.Root.Elements("book")...
XElement root = XElement.Load("e_test.xml")
XElement book = root.Elements("book")
.Where(x => (int) x.Element("id") == 20)
.SingleOrDefault();
if (book == null)
{
// No book with that ID
}
int num = (int) book.Element("read").Element("num");
If you're not dead set on using Linq, how about this XPath? XPath is probably more widely understood, and is really simple. The XPath to find your node would be:
/library/book[id=20]/read/num
Which you could use in C# thus:
var doc = new XmlDocument();
doc.LoadXml(myString);
var id = 20;
var myPath = "/library/book[id=" + id + "]/read/num";
var myNode = doc.SelectSingleNode(myPath);
Then you can do whatever you like with myNode to get its value etc..
Helpful reference:
http://www.w3schools.com/xsl/xpath_syntax.asp
I have the following XML:
<?xml version="1.0" ?>
<NewDataSet>
<Data>
<ElementDefinition>
<ID>1</ID>
<QUANTITY>0</QUANTITY>
</ElementDefinition>
<ElementDefinition>
<ID>2</ID>
<QUANTITY>1</QUANTITY>
</ElementDefinition>
</Data>
</NewDataSet>
I need to create an array which contains all ElementDefinitions which contain a QUANTITY element with a value other then 0.
I tried:
var f = XDocument.Load(path);
var xe = f.Root.Elements("QUANTITY").Where(x => x.Value != "0").ToArray();
But that doesn't seem to work. With the above XML the array should contain 1 item, but it stays 0.
After that I need to create a string for each ElementDefinition in the array, the string must contain the value of the corresponding ID element.
For that I tried:
foreach (string x in xe)
{
string ID = //not sure what to do here
}
You want something like this:
var ids = f.Root.Descendants("ElementDefinition")
.Where(x => x.Element("QUANTITY").Value != "0")
.Select(x => x.Element("ID").Value);
As you want the ID, it is not very helpful to select all QUANTITY nodes. Instead, select exactly what you specified in your question:
All ElementDefinitions (Descendants("ElementDefinition")), that have a QUANTITY with a Value other than 0 (Where(x => x.Element("QUANTITY").Value != "0"). From the resulting nodes, select the ID (Select(x => x.Element("ID").Value)).
Yo can replace with
var xe = f.Root.Elements("Data/ElementDefinition/QUANTITY").Where(x => x.Value != "0").ToArray();
I'm having trouble trying to update my xml file with a new value. I have a class Person, which only contains 2 strings, name and description. I populate this list and write it as an XML file. Then I populate a new list, which contains many of the same names, but some of them contains descriptions that the other list did not contain. How can I check if the name in the current XML file contains a value other than "no description", which is the default for "nothing"?
Part of the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Names>
<Person ID="2">
<Name>Aaron</Name>
<Description>No description</Description>
</Person>
<Person ID="2">
<Name>Abdi</Name>
<Description>No description</Description>
</Person>
</Names>
And this is the method for writing the list to the xml file:
public static void SaveAllNames(List<Person> names)
{
XDocument data = XDocument.Load(#"xml\boys\Names.xml");
foreach (Person person in names)
{
XElement newPerson = new XElement("Person",
new XElement("Name", person.Name),
new XElement("Description", person.Description)
);
newPerson.SetAttributeValue("ID", GetNextAvailableID());
data.Element("Names").Add(newPerson);
}
data.Save(#"xml\boys\Names.xml");
}
In the foreach loop how do I check if the person's name is already there, and then check if the description is something other than "no description", and if it is, update it with the new information?
I'm not sure I understand properly what you want, but I'm assuming you want to update the description only when the name is already there and the description is currently No description (which you should probably change to an empty string, BTW).
You could put all the Persons into a Dictionary based by name:
var doc = …;
var persons = doc.Root.Elements()
.ToDictionary(x => (string)x.Element("Name"), x => x);
and then query it:
if (persons.ContainsKey(name))
{
var description = persons[name].Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
That is, if you care about performance. If you don't, you don't need the dictionary:
var person = doc.Root.Elements("Person")
.SingleOrDefault(x => (string)x.Element("Name") == name);
if (person != null)
{
var description = person.Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
You can use the Nodes-Method on XElement and check manually.
But i will advise you to use the XPathEvaluate-Extension Method
For XPath expression take a look at:
How to check if an element exists in the xml using xpath?
I think you could create a peoplelist which only contains people not in the xml.
like ↓
var containlist = (from p in data.Descendants("Name") select p.Value).ToList();
var result = (from p in peoplelist where !containlist.Contains(p.Name) select p).ToList();
so that , you would no need to change anything with your exist method ...
just call it after..
SaveAllNames(result);
I'm working with an existing XML document which has a structure (in part) like so:
<Group>
<Entry>
<Name> Bob </Name>
<ID> 1 </ID>
</Entry>
<Entry>
<Name> Larry </Name>
</Entry>
</Group>
I'm using LINQ to XML to query the XDocument to retrieve all these entries as follows:
var items = from g in xDocument.Root.Descendants("Group").Elements("Entry")
select new
{
name = (string)g.element("Name").Value,
id = g.Elements("ID").Count() > 0 ? (string)g.Element("ID").Value : "none"
};
The "ID" elements aren't always there and so my solution to this was the Count() jazz above. But I'm wondering if someone has a better way to do this. I'm still getting comfortable with this new stuff and I suspect that there may be a better way to do this than how I'm currently doing it.
Is there a better/more preferred way to do what I want?
XElement actually has interesting explicit conversion operators that do the right thing in this case.
So, you rarely actually need to access the .Value property.
This is all you need for your projection:
var items =
from g in xDocument.Root.Descendants("Group").Elements("Entry")
select new
{
name = (string) g.Element("Name"),
id = (string) g.Element("ID") ?? "none",
};
And if you'd prefer to use the value of ID as an integer in your anonymous type:
var items =
from g in xDocument.Root.Descendants("Group").Elements("Entry")
select new
{
name = (string) g.Element("Name"),
id = (int?) g.Element("ID"),
};
In a similar situation I used an extension method:
public static string OptionalElement(this XElement actionElement, string elementName)
{
var element = actionElement.Element(elementName);
return (element != null) ? element.Value : null;
}
usage:
id = g.OptionalElement("ID") ?? "none"
How about:
var items = from g in xDocument.Root.Descendants("Group").Elements("Entry")
let idEl = g.Element("ID")
select new
{
name = (string)g.element("Name").Value,
id = idEl == null ? "none" : idEl.Value;
};
if this barfs, then FirstOrDefault() etc might be useful, else just use an extension method (as already suggested).