XDocument LINQ search based on attribute - c#

I have an XML document which I load from the disk
XDocument events = XDocument.Load("Content/GameData/events.xml");
The contents of this xml are the following:
<?xml version='1.0' encoding='UTF-8'?>
<Events>
<level1>
<NarrationEvent code="lvl1_fridge">
I can't even remember when I last ate.
I am not hungry though.
</NarrationEvent>
<NarrationEvent code="lvl1_tv">
Why do I even have a TV?
Oh right, I use it as a screen for my laptop.
</NarrationEvent>
<NarrationEvent code="lvl1_bed">
Oh man, I am beat.
</NarrationEvent>
<NarrationEvent code="lvl1_computer">
Oh, look at that. The project has been compiled.
</NarrationEvent>
</level1>
<level2>
</level2>
<level3>
</level3>
<level4>
</level4>
<cave>
</cave>
</Events>
I use this code here supposedly select the appropriate NarrationEvent element, based on its attribute "code"
IEnumerable<XElement> v =
(from narrationEvent in events.Elements("NarrationEvent")
where (string)narrationEvent.Attribute("code") == code
select narrationEvent);
foreach (XElement page in v)
{
//Console.WriteLine("ff");
narration.Add(page.Value);
}
This returns nothing, my XElement Ienumerable is empty. I used breakpoints and the code value is passed to this method just fine. e.g. "lvl1_bed"
What is wrong with this code?

You can use the Descendants-Method to get your NarrationEvent-Element. I have updated your code accordingly.
IEnumerable<XElement> v = from narrationEvent in events.Descendants("NarrationEvent")
where narrationEvent.Attribute("code").Value == code
select narrationEvent;

Related

c# Reading XML with XElement

When I run the following code and step through it with a break point and look at temp I see "Empty, Enumeration yielded no results" and the MessageBox.Show never fires. I'm trying to pull everything under Season no="1"
XElement sitemap = XElement.Load(#"http://services.tvrage.com/feeds/episode_list.php?sid=" + this.showID);
IEnumerable<XElement> temp = from el in sitemap.Elements("Season")
where (string)el.Attribute("no") == "1"
select el;
foreach (XElement el in temp)
{
MessageBox.Show("found something");
}
This is the XML that's being loaded:
http://services.tvrage.com/feeds/episode_list.php?sid=6312
You're looking for elements called Season directly under the root element... whereas your XML looks like this:
<Show>
<name>The X-Files</name>
<totalseasons>9</totalseasons>
<Episodelist>
<Season no="1">
<episode>
...
If you want to look for all descendant elements with a given name, use Descendants instead of Elements. That certainly finds elements in the example XML you've given.

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");

Locating a value in XML

I have an xml file loaded into an XDocument that I need to extract a value from, and I'm not sure of the best way to do it. Most of the things I'm coming up with seem to be overkill or don't make good use of xml rules. I have the following snippet of xml:
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
There can be any number of <entry> nodes, and they will all follow a similar pattern. The value under the root attribute on the templateId element contains a known UID that identifies this entry as the one I want. I need to get the reference value.
My thought is to find the correct templateID node, back out to the observation node, find <valuexsi:type="ED"> and then get the reference value. This seems overly complex, and I am wondering if there is another way to do this?
EDIT
The xml I receive can sometimes have xml nested under the same node name. In other words, <observation> may be located under another node named <observation>.
You have problems, because your document uses Namespaces, and your query is missing them.
First of all, you have to find xsi namespace declaration somewhere in your XML (probably in the most top element).
It will look like that:
xmlns:xsi="http://test.namespace"
The, take the namespace Uri and create XNamespace instance according to it's value:
var xsi = XNamespace.Get("http://test.namespace");
And use that xsi variable within your query:
var query = from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
let tId = o.Element("templateId")
where tId != null && (string)tId.Attribute("root") == "2.16.840.1.113883.10.20.6.2.12"
let v = o.Element("value")
where v != null && (string)v.Attribute(xsi + "type") != null
let r = v.Element("reference")
where r != null
select (string)r.Attribute("value");
var result = query.FirstOrDefault();
I have tested it for following XML structure:
<root xmlns:xsi="http://test.namespace">
<entries>
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
</entries>
</root>
The query returns #121071 for it.
For your input XML you will probably have to change first line of query:
from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
to match <observation> elements from your XML structure.
Would something along the lines of the following help?
XDocument xdoc = GetYourDocumentHere();
var obsvlookfor =
xdoc.Root.Descendants("observation")
.SingleOrDefault(el =>
el.Element("templateId")
.Attribute("root").Value == "root value to look for");
if (obsvlookfor != null)
{
var reference = obsvlookfor
.Element("value")
.Element("reference").Attribute("value").Value;
}
My thought is as follows:
Pull out all the observation elements in the document
Find the only one (or null) where the observation's templateId element has a root attribute you're looking for
If you find that observation element, pull out the value attribute against the reference element which is under the value element.
You might have to include the Namespace in your LINQ. To retrieve that you would do something like this:
XNamespace ns = xdoc.Root.GetDefaultNamespace();
Then in your linq:
var obsvlookfor = xdoc.Root.Descendants(ns + "observation")
I know I had some issues retrieving data once without this. Not saying its the issue just something to keep in mind particularly if your XML file is very in depth.

Linq to XML Question

Given the following XML, what query can I use to extract the value of preapprovalKey to a string variable? Still a little new to LINQ to XML.
<?xml version="1.0" encoding="UTF-8" ?>
- <ns2:PreapprovalResponse xmlns:ns2="http://svcs.paypal.com/types/ap">
- <responseEnvelope>
<timestamp>2011-04-05T18:35:32.952-07:00</timestamp>
<ack>Success</ack>
<correlationId>7cec030fa3eb2</correlationId>
<build>1655692</build>
</responseEnvelope>
<preapprovalKey>PA-9AG427954Y7578617</preapprovalKey>
</ns2:PreapprovalResponse>
XDocument doc = XDocument.Load("test.xml");
string preapprovalKey = doc.Descendants("preapprovalKey").Single().Value;
See below my exmaple, it help you to resolve your issue and problem. :)
Consider this below XML is there as one of the SQL table's column.
<Root>
<Name>Dinesh</Name>
<Id>2</Id>
</Root>
The objective of the query is to fetch the Name from the XML. In this example we will fetch the 'Dinesh' as the value.
var Query = (from t in dbContext.Employee.AsEnumerable()
where t.active == true
select new Employee
{
Id = t.AtpEventId,
Name = XDocument.Parse(t.Content).Descendants("Root").Descendants("Name").ToList().
Select(node => node.Value.ToString()).FirstOrDefault()
});
Note the following :-
Here in above LINQ , t.active == true is just an example to make some condition if needed.
Please note, in the above LInQ query, always use the AsEnumerable(), as I did in the
first file of the Linq query.exmaple(var Query = (from t in dbContext.Employee.AsEnumerable())
Descendants("Root").Descendants("Name") , Here Root should be the Element matching with the XML, And under the Root we have Name element, thats why we wrote
Descendants("Root").Descendants("Name")
For any further clarification you can reach me via danish.eggericx#gmail.com

Direct access and edit to an xml node, using properties

El Padrino showed a solution:
How to change XML Attribute
where an xml element can be loaded directly (no for each..), edited and saved!
My xml is:
<?xml version="1.0" encoding="ISO-8859-8"?>
<g>
<page no="1" href="page1.xml" title="נושא 1">
<row>
<pic pos="1" src="D:\RuthSiteFiles\webSiteGalleryClone\ruthCompPics\C_WebBigPictures\100CANON\IMG_0418.jpg" width="150" height="120">1</pic>
</row>
</page>
</g>
and I need to select a node by two attributes(1. "no" in the page tag and "pos" in the pic tag)
I've found :
How to access a xml node with attributes and namespace using selectsinglenode()
where direct access is possible but beside the fact that I dont understand the solution, I think it uses the xpath object which can't be modified and save changes.
What's the best way to
access directly an xml node (I'm responsible that the node will be unique)
edit that node
save changes to the xml
Thanks
Asaf
You can use the same pattern as the first answer you linked to, but you will need to include the conditions on the attributes in the XPath. Your basic XPath would be g/page/row/pic. Since you want the no attribute of page to be 1, you add [#no='1'] as a predicate on page. So, the full XPath query is something like g/page[#no='1']/row/pic[#pos='1']. SelectSingleNode will return a mutable XmlNode object, so you can modify that object and save the original document to save changes.
Putting the XPath together with El Padrino's answer:
//Here is the variable with which you assign a new value to the attribute
string newValue = string.Empty;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFile);
XmlNode node = xmlDoc.SelectSingleNode("g/page[#no='1']/row/pic[#pos='1']");
node.Attributes["src"].Value = newValue;
xmlDoc.Save(xmlFile);
//xmlFile is the path of your file to be modified
Use the new, well-designed XDocument/XElement instead of the old XmlDocument API.
In your example,
XDocument doc = XDocument.Load(filename);
var pages = doc.Root.Elements("page").Where(page => (int?) page.Attribute("no") == 1);
var rows = pages.SelectMany(page => page.Elements("row"));
var pics = rows.SelectMany(row => row.Elements("pic").Where(pic => (int?) pic.Attribute("pos") == 1));
foreach (var pic in pics)
{
// outputs <pic pos="1" src="D:\RuthSiteFiles\webSiteGalleryClone\ruthCompPics\C_WebBigPictures\100CANON\IMG_0418.jpg" width="150" height="120">1</pic>
Console.WriteLine(pic);
// outputs 1
Console.WriteLine(pic.Value);
// Changes the value
pic.Value = 2;
}
doc.Save(filename);

Categories

Resources