I am trying to query this very complicated XML document using xDocument and LINQ to XML. I want to perform the following action:
Get all elements that answer to a certain criteria, and if they don't, return another attribute from the xDocument.
Example:
<cars>
<car>
<patrol type="oil">
<url> http://Toyotaoil.com </url>
</patrol>
</car>
<car>
<patrol type="oil">
<url> http://BMWoil.com </url>
</patrol>
<patrol type="gas">
<url> http://BMWgas.com </url>
</patrol>
</car>
<car>
<patrol type="gas">
<url> http://Hondagas.com </url>
</patrol>
</car>
Now what I'd like to get from this query is a list of patrols of type oil, unless the car doesn't use petrol, and then I'd be satisfied with gas.
If I use the where clause I just miss the cases where the car uses gas. Is there any such thing like a where clause, where I can specify what to do if they condition wasn't met?
The solution below should give you the flexibility to query whatever you like:
var result = from car in xdoc.Element("cars").Elements("car")
let patrols = car.Elements("patrol")
let oils = patrols.Where(patrol => patrol.Attribute("type") == "oil")
select new {
Car = car,
Patrols = (oils.Any() ? oils : patrols)
}
I don't have Visual Studio here, so I hope it compiles :)
Give a bit more information on what you like to select, and I'll give you a more specific LINQ statement.
xdoc.Element("cars")
.Elements("car")
.Select(car => car.Elements("patrol")
.SingleOrDefault(p => (string)p.Attribute("type") == "oil")
??
car.Elements("patrol")
.Single(p => (string)p.Attribute("type") == "gas"));
You can just make something like this:
var query = from element in someElements
select element.Attribute("type").Value == "oil"
? returnSomethingWhenItIsOil
: returnSomethingWhenItIsSomethingElse;
or
var query = from element in someElements
where element.Attribute("type") == "oil"
|| element.Attribute("type") == "gas"
select element;
But explain the problem better, thanks :)
It sounds like you don't actually want a where clause at all - you just want a select clause that either picks one value or another.
However, your example doesn't really describe how you'd select a different item based on the values - which "other attribute" would you select? What do you mean by "where the car uses gas"? If you can give more details of the example, it shouldn't be too hard to give you matching code.
var cars = from c in xdoc.Descendants("car")
where
(c.Element("patrol").Attribute("type").Value == "oil" ||
c.Element("patrol").Attribute("type").Value == "gas")
select new Car
{
FuelType = c.Element("patrol").Attribute("type").Value.ToString()
};
foreach (Car c in cars)
{
Console.WriteLine(c.ToString());
}
class Car
{
public string FuelType { get; set; }
public override string ToString()
{
return "Car FeulType = " + this.FuelType.ToString();
}
}
These are the results, I am getting
alt text http://img694.imageshack.us/img694/5016/carse.jpg
Order by type according to your specifications, take FirstOrDefault().
Edit: What I meant was something like this:
var patrols = from car in doc.Root.Elements()
let p = car.Elements().OrderBy(patrol=>patrol.Attribute("type").Value).First()
select p;
This returns one result per car. Check the OrderBy clause and adjust it accordingly. This would result in:
<patrol type="oil">
<url> http://Toyotaoil.com </url>
</patrol>
<patrol type="gas">
<url> http://BMWgas.com </url>
</patrol>
<patrol type="gas">
<url> http://Hondagas.com </url>
</patrol>
Edit again: Ah, now it clicked. Yes, this only returns a single item per car - Zyphrax gave a nice solution.
Related
I'm trying to learn how to save and extract data with XML in C#, and even though I've read various answers to similar questions on here, I cannot get a hold of why my statement isn't returning anything.
The program I'm writing is just a test, in which I've saved data about a couple movies in the xml document and now I'm trying to retrieve some of them based on their cost.
This is the class that I've written for searching:
class ExtractData
{
private XDocument _xdoc;
public List<string> SearchByCost(double cost)
{
_xdoc = XDocument.Load(FileLocation.XmlFileLocation);
List<string> list = new List<string>();
var movies = from movie in _xdoc.Root.Elements("Name")
where Convert.ToDouble(movie.Element("Cost").Value) < cost
select movie;
foreach (var item in movies)
{
list.Add(item.Value);
}
return list;
}
}
This is how I'm trying to make it print it in the console:
eData = new ExtractData();
foreach (var movie in eData.SearchByCost(9))
{
Console.WriteLine(movie);
}
And this is the content of the XML document:
<?xml version="1.0" encoding="utf-8"?>
<Movies>
<Movie>
<Id>1</Id>
<Name>Shawshank Redemption</Name>
<Director>Frank Darabont</Director>
<Year>1994</Year>
<Cost>9.95</Cost>
</Movie>
<Movie>
<Id>2</Id>
<Name>Pulp Fiction</Name>
<Director>Quentin Tarantino</Director>
<Year>1995</Year>
<Cost>8.95</Cost>
</Movie>
<Movie>
<Id>3</Id>
<Name>Sharknado</Name>
<Director>Anthony Ferrante</Director>
<Year>2013</Year>
<Cost>5.95</Cost>
</Movie>
</Movies>
I hope this is enough information to try and help me out, and thanks in advance! :)
Root element contains Movie elements:
var movies = from movie in _xdoc.Root.Elements("Movie") // here instead of "Name"
where (double)movie.Element("Cost") < cost
select movie;
Also, XElement supports explicit casting to double. And you can replace the second loop with LINQ query (assume you want to select names of movies):
List<string> list = movies.Select(m => (string)m.Element("Name")).ToList();
In plain English, you seem to be trying to find movies cheaper than 9 (of some arbitrary currency).
You can write this as:
public IReadOnlyCollection<string> SearchByCost(XDocument xdoc, double cost)
{
return xdoc.Root.Elements("Movie")
.Where(movie => (double)movie.Element("Cost") < cost)
.Select(movie => movie.Element("Name").Value)
.ToList();
}
I have sitemap format as below.
I want to delete a complete node that
I find loc.
For example:
Where a node has <loc>with a value of http://www.my.com/en/flight1.
I want to delete the <url> node and his child
I want to delete loc
than lastmod than priority and than changefreq
<url>
<loc>http://www.my.com/en/flight1
</loc>
<lastmod>2015-03-05</lastmod>
<priority>0.5</priority>
<changefreq>never</changefreq>
</url>
<url>
<loc>
http://www.my.com/en/flight2
</loc>
<lastmod>2015-03-05</lastmod>
<priority>0.5</priority>
<changefreq>never</changefreq>
</url>
<url>
<loc>
http://www.my.com/en/flight3
</loc>
<lastmod>2015-03-05</lastmod>
<priority>0.5</priority>
<changefreq>never</changefreq>
</url>
If you're using C# you should use System.xml.linq (XDocument)
You can remove a node like so:
XDocument.Load(/*URI*/);
var elements = document.Root.Elements().Where(e => e.Element("loc") != null && e.Element("loc").Value == "http://www.my.com/en/flight1");
foreach (var url in elements)
{
url.Remove();
}
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;
<World>
<Animals>
<Tab>
<Dogs id ="1">
<Dog1></Dog1>
<Dog2></Dog2>
<Dog3></Dog3>
</Dogs>
<Dogs id ="2"></Dogs>
<Dogs id ="3"></Dogs>
</Tab>
</Animals>
</World>
How do I get all elements under tag where id == 1?
My Linq query. (doesn't work) why?
XDocument xml= XDocument.Load(xml.xml);
var elements = from e in xml.Descendants("Animals").Descendants("Tab").Elements("Dogs")
where e.Attribute("id").toString().Equals("1")
select c;
Could you check it please?
Thanks!
var result = xdoc.Descendants("World")
.Descendants("Animals")
.Descendants("Tab")
.Elements("Dogs")
.Where(n => n.Attribute("id").Value == "1");
Output:
<Dogs id="1">
<Dog1></Dog1>
<Dog2></Dog2>
<Dog3></Dog3>
</Dogs>
Or use XPath:
xml.XPathSelectElements("/World/Animals/Tab/Dogs[#id=1]")
or
xml.XPathSelectElements("//Dogs[#id=1]")
which would find all Dogs wherever they occurred.
From your sample data I think you want
//from e in xml.Descendants("Animals").Descendants("Tab").Elements("Dogs")
from e in xml.Descendants("Animals").Elements("Tab").Descendants("Dogs")
And in the same line, Descendants("Animals") could be Elements("Animals") if you want to enforce the structure.
For the rest your query looks OK.
I have an XML document looking similar to this:
<items>
<item cat="1" owner="14">bla</item>
<item cat="1" owner="9">bla</item>
<item cat="1" owner="14">bla</item>
<item cat="2" owner="12">bla</item>
<item cat="2" owner="12">bla</item>
</items>
Now I'd like to get all unique owners (I actually only need the attribute value of the owner) belonging to a specified category using a linq query. In my example, the query for cat 1 would return a list containing 9 and 14. How can I do that? Linq syntax would be preferred over Lambdas. Thanks in advance ;)
Presuming the fragment is in itemsElement:
var distinctOwners = (from item in itemsElement.Element("item")
where itemElements.Attribute("cat") == 1
select item.Attribute("owner")).Distinct();
Apologies for formatting and indentation!
Try this function:-
static IEnumerable<int> GetOwners(XDocument doc, string cat)
{
return from item in doc.Descendants("item")
where item.Attribute("cat").Value == cat
select (int)item.Attribute("owner")).Distinct();
}
XElement ele = XElement.Parse(#"<items><item cat=""1"" owner=""14"">bla</item><item cat=""1"" owner=""9"">bla</item>" +
#"<item cat=""1"" owner=""14"">bla</item><item cat=""2"" owner=""12"">bla</item>" +
#"<item cat=""2"" owner=""12"">bla</item></items>");
int cat = 1;
List<int> owners = ele.Elements("item")
.Where(x=>x.Attribute("cat").Value==cat.ToString()).Select(x=>Convert.ToInt32(x.Attribute("owner").Value)).Distinct().ToList();