C# Query XML with Linq to find match - c#

Here is a snippet of my XML (with only a few entries whereas there are actually around a thousand).
<?xml version="1.0" encoding="UTF-8"?>
<information_code version="3.5">
<entry code="000" title="Function, data for plans and description"/>
<entry code="001" title="Title page"/>
<entry code="002" title="List of pages or data modules"/>
<entry code="003" title="Change records and highlights"/>
<entry code="004" title="Access illustration"/>
</information_code>
What I need to do is match the 'code' attribute with a value that I pass into my query, then return the value of the 'title' attribute. It really shouldn't be hard but I'm going around in circles.
Here's where I currently am, but it always gets caught without matching. Obviously something wrong with my query.
private string getInfoName(string infoCode)
{
XDocument mydoc = XDocument.Load(GlobalVars.pathToInfoCodes);
string name = string.Empty;
try
{
var entry = mydoc
.Elements("entry")
.Where(e => e.Attribute("code").Value == infoCode)
.Single();
name = entry.Attribute("title").Value;
}
catch
{
MessageBox.Show("Info code not recognised: " + infoCode);
}
return name;
}

The problem is that when you use Elements it searches only in the level you are currently at, which in this point is the <information_code> - so there are no <entry> elements there.
You can use .Element("information_code").Elements("entry") or use instead .Descendants:
string wantedCode = "001";
var title = XDocument.Load(GlobalVars.pathToInfoCodes)
.Descendants("entry")
.Where(e => e.Attribute("code")?.Value == wantedCode)
.Select(e => e.Attribute("title").Value).FirstOrDefault();
You can also do it with the query syntax. Might look nicer:
var title = (from e in XDocument.Load("data.xml").Descendants("entry")
where e.Attribute("code")?.Value == wantedCode
select e.Attribute("title")?.Value).FirstOrDefault();
Note that the ?. syntax is C# 6.0 Null Propagation. If you are with an earlier C# version then you will have to store the attribute in a variable, check that it is not null and only then access .Value

Related

Duplicate values in Xml database

I need to insert only unique values in my XML database based on the "userName" value. For example there can't be two players which has same name in the database. The implementation of my xml database which is named as "MyXML.xml" is like that :
<?xml version="1.0" encoding="utf-8"?>
<Players>
<player name="cag" score="5555" />
<player name="cihan" score="1222" />
<player name="can" score="333" />
</Players>
and my related code in order to detetect duplication is:
public bool insertUserDetails(string userName,float userScore){
XDocument doc = Document.Load(HttpContext.Current.Server.MapPath("MyXML.xml"));
var duplicate = doc.Element("Players").Elements("player").Where(x =>(string)x.Value == userName).SingleOrDefault();
if (duplicate != null){
return false;
}
else{
return true;
}
}
when I try to insert a duplicate value for example "can" and "333", var duplicate value turns out to be null. How can I fix this problem ?
"can" is defined as a attribute in your XML; It is not a value. Value is something which comes in between open and closing tags of xml.
For example, if you have a xml element like this
<SomeTag name="somename">Hello world</SomeTag>
Then, SomeTag is the element name, somename is the attribute value of attribute "name" and "Hello world" is the value of the xml element itself.
So, You need to find the attribute using Attribute method to access its value.
var duplicate = doc.Element("Players")
.Elements("player")
.Where(x=>x.Attribute("name").Value == userName)
.SingleOrDefault();
You're iterating over the Value of each player element, rather than the Attributes of those elements. Since there's no text inside the player element, the value is always an empty string, and thus never matched the name you're looking for. Instead, select the .GetAttribute("name") for each element
var duplicate = doc
.Element("Players")
.Elements("player")
.Select(ele => ele.GetAttribute("name"))
.Where(att =>(string)att.Value == userName)
.SingleOrDefault();
you're comparing the value of an XElement, "x", to userName.
you should compare the "name" attribute of x to it.
var duplicate = doc.Element("Players").Elements("player").Where(x => (string)x.Attribute("name").Value == userName).SingleOrDefault();

Filter descendants parent [or at least higher level than results]

Still a Linq newbie here, and now having issues with the WHERE clause. I'm trying to return anything found in the printer tags, but only from below the element list type="lff".
If I try to output the descendant elements with no WHERE clause, I get everything (from both <list> elements). When I try to add various versions of a WHERE clause, I get nothing back. I'm obviously not putting the WHERE condition in correctly.
(I need to get the element object, so I can check the NAME and the VALUE. In my example below, I am only outputting the VALUE for now).
Can you advise?
Here is the XML:
<?xml version="1.0"?>
<printerlist>
<list type="aff">
<printserver>print-server1</printserver>
<printserver>print-server2</printserver>
<printserver>print-server3</printserver>
<additionalprinters>
<printer>
<fullname>\\servera\bbb</fullname>
</printer>
</additionalprinters>
</list>
<list type="lff">
<printserver>print-sever4</printserver>
<additionalprinters>
<printer>
<fullname>\\serverb\bbb</fullname>
</printer>
<printer>
<fullname>\\serverc\aaa</fullname>
</printer>
</additionalprinters>
</list>
</printerlist>
And here is the code to try and get the list:
var qq = from c in xml.Descendants("additionalprinters").Descendants("printer")
//where (string) c.Parent.Attribute("type") == "lff"
//Uncommenting the above line means that nothing is returned.
select c;
foreach (XElement q in qq)
{
Console.WriteLine("Test Output: {0}", q.Value );
}
Output is:
Test Output: \\servera\bbb
Test Output: \\serverb\bbb
Test Output: \\serverc\aaa
I am only looking for the final two outputs to be returned, in this particular case.
The parent of printer is additionalprinters and it doesn't have type property, you need to use .Parent twice to get list element.
from c in xml.Descendants("additionalprinters").Descendants("printer")
where (string) c.Parent.Parent.Attribute("type") == "lff"
select c
Or you can also do the following
xml.Descendants("list")
.Where(c => (string) c.Attribute("type") == "lff")
.SelectMany(x => x.Element("additionalprinters").Descendants("printer"))
You can also use XPath selector from System.Xml.XPath namespace for this purpose:
var doc = XDocument.Parse(xml);
var printers = doc.XPathSelectElements("//list[#type='lff']/additionalprinters/printer");

LINQ to XML - Selecting with multiple attribute filters?

I was a long time user of XmlTextReader but after noting that the answer to every related question on here was 'use LINQ' I decided to take the plunge, everything has been going well up until this point. I've been banging my head against this for a while, hopefully someone can help.
I have the following section in my document;
<action_areas>
<time_slice name="0 - 5" id="1">
<action_area id="1">
<aa_team id="1000">9</aa_team>
<aa_team id="1001">7</aa_team>
</action_area>
<action_area id="2">
<aa_team id="1000">5</aa_team>
<aa_team id="1001">2</aa_team>
Due to the structure of the document I need "action_areas" to be the thing passed to the initial selection, ie;
var stuff = from item in xDoc.Descendants("action_areas")
So the question is what statements do I follow that with so I can filter based on the time_slice name attribute AND the action_area id attribute AND the aa_team id attribute so I can ultimately get at the content contained in the aa_team element (9, 7, 5 and 2 in the above instance) with the 'select new' statement?
Thanks in advance.
Try this
string timeSliceToSearchFor = "0 - 5";
string actionAreaToSearchFor = "1";
string aaTeamIdToSearchFor = "1001";
// Returns 7
string value = xDoc .Descendants("action_areas")
.Elements("time_slice")
.Where(element => element.Attribute("name").Value == timeSliceToSearchFor)
.Elements("action_area")
.Where(element => element.Attribute("id").Value == actionAreaToSearchFor)
.Elements("aa_team")
.Where(element => element.Attribute("id").Value == aaTeamIdToSearchFor)
.Single().Value;

How properly work with LINQ to XML?

I have generated such xml file
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>212</numRequest>
<IDWork>12</IDWork>
<NumObject>21</NumObject>
<lvlPriority>2</lvlPriority>
<NumIn1Period>21</NumIn1Period>
</Single_Request>
</Requestes>
My aim is to get IDWork,numRequest and etc elements. I tried to get them this way:
foreach (XElement el in doc.Root.Elements())
{
if (el.Name == "Single_Request")
{
string num = el.Elements("numRequest").Value;
// but he says, that he cant do this .Value because it doest exist at all
}
}
How to fix this?
You have this error, because Elements("numRequest") returns collection of elements, not single element. You should use Element("numRequest") instead.
Also I suggest you to use query for getting elements by name instead of enumerating all elements and verifying their names:
var request = doc.Root.Element("Single_Request");
var num = (int)request.Element("numRequest");
Usually you use anonymous types or custom objects to group values parsed from xml:
var query = from r in doc.Root.Elements("Single_Request")
where (int)r.Attribute("num") == 1 // condition
select new {
NumRequest = (int)request.Element("numRequest"),
IdWork = (int)request.Element("IDWork"),
NumObject = (int)request.Element("NumObject")
};
var request = query.SinlgleOrDefault();
// use request.IdWork

Best way to query XDocument with LINQ?

I have an XML document that contains a series of item nodes that look like this:
<data>
<item>
<label>XYZ</label>
<description>lorem ipsum</description>
<parameter type="id">123</parameter>
<parameter type="name">Adam Savage</parameter>
<parameter type="zip">90210</parameter>
</item>
</data>
and I want to LINQ it into an anonymous type like this:
var mydata =
(from root in document.Root.Elements("item")
select new {
label = (string)root.Element("label"),
description = (string)root.Element("description"),
id = ...,
name = ...,
zip = ...
});
What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?
(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:
If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
You may want to pull all the values at once and convert them into a dictionary
If you're using different data types for the contents, you might want to cast the element instead of using .Value
You may want to create a method to return the matching XElement for a given type, instead of having several queries.
Personally I don't think I'd even use a query expression for this. For example:
static XElement FindParameter(XElement element, string type)
{
return element.Elements("parameter")
.SingleOrDefault(p => (string) p.Attribute("type") == type);
}
Then:
var mydata = from item in document.Root.Elements("item")
select new {
Label = (string) item.Element("label"),
Description = (string) item.Element("description"),
Id = (int) FindParameter(item, "id"),
Name = (string) FindParameter(item, "name"),
Zip = (string) FindParameter(item, "zip")
};
I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.
use XPATH - it is very fast ( except xmlreader - but a lot of if's)
using (var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[#type='id']");
foreach (var x in query.Cast<XElement>())
{
Console.WriteLine( x.Value );
}
}

Categories

Resources