Accessing nested elements while iterating an XML LINQ query? - c#

With this XML data:
<item>
<placemarks>
<placemark>
<uid>{EA5FA2B2-78CB-4FAA-9F17-EBB361410499}</uid>
</placemark>
</placemarks>
<links>
<link>
<title>Book Online</title>
<link>https://blah</link>
</link>
<link>
<title>Call to Book</title>
<link>tel:1-866-555-5555</link>
</link>
</links>
</item>
I'm trying to create Hotel objects w/ various attributes from the XML. Everything works fine until I hit nested XML tags and then I got stuck:
var items = root.Descendants ("item");
var hotels = from it in items
select new Hotel () {
Uid = it.Element ("uid").Value,
Name = it.Element ("name").Value,
Description = it.Element ("description").Value,
ImageUrl = it.Element ("image_url").Value,
RoomType = it.Element("custom_1").Value,
PlacemarkId = it.Element("placemarks").Element("placemark").Element("uid").Value,
BookUrl = (from links in it.Descendents("links") where links.Element("link").Element("title) = "Book Online").Value
};
How do I get PlacemarkId to work? I keep getting null because the methods after the first Element("placemarks") evidently fails :-(
And obviously, setting the BookUrl property won't compile, but that's what I'd like to do. It's really ugly because of the weird XML schema w/ nested link tags :-(
Sorry for the noob question. I tried googling for every combo of "nested xml linq select" I could think of w/ no luck :-P
Would help even more if someone can let me know what I'm trying to do is called in LINQ. I would think it's possible...
Thanks in advance :-)

You can use XPathSelectElement() extension method to avoid null reference exception in case some <item> don't have placemark child (without having to manually check for nulls from C# code) :
var hotels = from it in items
select new Hotel()
{
......
......
PlacemarkId = (string)it.XPathSelectElement("placemarks/placemark/uid"),
BookUrl = (string)it.XPathSelectElement("links/link[title='Book Online']/link"),
};
Getting BookUrl value can also be done using XPath as demonstrated above. Or if you're sure the XML structure is consistent for this part (no element is ever missing), you can use LINQ without null checking like so :
BookUrl = (from link in it.Elements("links").Elements("link")
where (string)link.Element("title") == "Book Online"
select link.Element("link")).First().Value
For reference :
W3C : XML Path Language (XPath) Version 1.0 specification (formal & complete spec of xpath)
w3schools : XPath syntax (relaxed intro to basic xpath syntax)

Related

How to get a second or third XML node when using an anonymous type?

I'm using an anonymous type to grab some XML data. All was going well until I ran across a section of XML where there can be 2 or 3 similar nodes. Like in the XML sample below there are 3 separate "Phones". My code was working fine when there was only ONE element that was possible to grab after following the "element path" I led it to. How can i grab a specific one? Or all 3 for that matter? Handling XML is still new to me and there seems to be soo many ways of handling it Searching the web for my exact need here didn't prove successful. Thanks.
var nodes = from node in doc.Elements("ClaimsSvcRs").Elements("ClaimDownloadRs")
select new
{
Phone1 = (string)node.Elements("Communications").Elements("PhoneInfo").Elements("PhoneNumber").FirstOrDefault(),
Phone2 = (string)node.Elements("Communications").Elements("PhoneInfo").Elements("PhoneNumber").FirstOrDefault(),
};
The XML Code is
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<ClaimsSvcRs>
<ClaimDownloadRs>
<Communications>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Home</CommunicationUseCd>
<PhoneNumber>+1-715-5553944</PhoneNumber>
</PhoneInfo>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Business</CommunicationUseCd>
<PhoneNumber>+1-715-5552519</PhoneNumber>
</PhoneInfo>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Cell</CommunicationUseCd>
<PhoneNumber>+1-715-5551212</PhoneNumber>
</PhoneInfo>
</Communications>
</ClaimDownloadRs>
</ClaimsSvcRs>
</TEST>
I haven't used xpath in a while so i'll let someone else stand in there... but there's a way to select a particular PhoneInfo object based upon its subelements. So if you knew whether you wanted Home or Business or Cell or whatever, you'd be able to select that particular PhoneInfo object. Otherwise if you wanted simple Phone1,2,3 and nulls where ok, use the Skip linq function. Phone2 = query.Skip(1).FirstOrDefault()
lol no worries ;) xpath can be intermixed in here, was my thought, and might be more elegant if your CommunicationUseCd fields were deterministic. Then you could have Home = ... and Work = ..., etc, instead of Phone1 & Phone2
The same could be accomplished by slipping a where clause into each your query lines
If you're up for LINQ you can get all your elements in one go:
foreach(XElement phone in XDocument.Parse(xmlString).Descendants("PhoneInfo"))
{
Console.WriteLine(phone.Element("PhoneNumber").Value);
//etc
}
I find XDocument & LINQ a lot easier than XmlDocument & XPath, if you're okay with the alternative. There's more info on them here

Need to add xml element to last parent using linq c#

I'm really new to Linq and C# and I'm stuck on what is probably an obvious problem.
I have an existing XML file
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<books>
<book>
<title>This is Title 1</title>
<author>John Doe</author>
<categories>
<category>How to</category>
<category>Technical</category>
</book>
<book>
<title>This is Title 2</title>
<author>Jane Brown</author>
<categories>
<category>Fantasy</category>
</categories>
</book>
</books>
I want to add a 2nd category to the second book in this file.
I've gotten this far:
var thiscat = doc.Root
.Element("book")
.Element("categories");
thiscat.Add(new XElement("category", "novel"));
But this adds a 3rd category to the first book. I need to learn how to point 'thiscat' at the last categories element rather than the first one. I've been sniffing around LastNode but haven't managed to get the syntax right.
This is my first question here. Please let me know if I'm not being clear or if I'm doing anything wrong.
Pete,
Here is an example that will search for the book by title This is Title 2 and add another category.
var elem = doc.Root.Elements("book").FirstOrDefault(x => x.Element("title").Value.Equals("This is Title 2"));
if (elem != null)
{
var category = elem.Element("categories");
category.Add(new XElement("category", "novel"));
}
Edit: More explanatoin.
First of we search the documents book elements for the matching title of This is Title 2 (effectively your second entry). By executing the FirstOrDefault extension method we either the get the first matching element (as XElement) or null.
Because we 'could' get a null value we must check if the value is null if not we move into the next step of locating the categories element. This can be done simply calling the elem.Element() method as we only expect one element.
Finally we add a new XElement to the category element.
Hope this helps.
Cheers.
To answer your question quite literally, you could modify the statement as follows:
var thiscat = doc.Root
.Elements("book")
.Skip(1)
.First()
.Element("categories");
The "Element" function returns the first element of that type found. In this case, we used "Elements" instead to return an IEnumerable containing all of the elements named "book", and then we used the LINQ "skip" function to skip the first (returning another IEnumerable of all the remaining elements), and then we took just the first element in the IEnumerable (back to a single XElement).
Another way you could have gotten to the answer is as follows:
var thiscat = doc.Root
.Element("book")
.ElementsAfterSelf()
.First()
.Element("categories");
ElementsAfterSelf returns an IEnumerable of all the sibling elements after the calling object.
LINQ is a really critical part of programming in C# and it's good to see you're trying to learn it from the beginning. Although your methodology here in adding a specific element to a specific place programmatically is questionable (obviously it is a contrived example), in playing around like this you will probably learn a bit about LINQ and that is always good.
First you should get your second book element.According to your code:
var thiscat = doc.Root
.Element("book")
.Element("categories");
This statement returns just one categories element which belongs to your first book.Because you are using Element instead of Elements. Let's go step by step.
A proper way to get second element is using Descendants like this:
var secondBook = doc.Descendants("book")[1];
Descendants returning a collection of your books.And we are getting second element with indexer.Now we need to select your categories element under the book element.
var categories = secondBook.Element("categories");
Now we have our categories element and we can add our new category and save Xml Document:
categories.Add(new XElement("category", "novel"));
doc.Save(path);
And that's all.If you understand that logic you can modify your html file however you like.Besides you can make all of these in one line:
doc.Descendants("book")[1]
.Element("categories")
.Add(new XElement("category", "novel"));
This should work( slightly lengthy solution as it helps understand the fundamentals better):
XmlElement rootNode = xd.DocumentElement; //gives <books> the root node
XmlNodeList cnodes= rootNode.ChildNodes; //gets the childnodes of <books>
XmlNode secondBook= cnodes.Item(1); //second child of <books> i.e., the <book> you want
XmlNodeList bnodes= secondBook.ChildNodes; //gets the childnodes of that <book>
XmlNode categories= bnodes.Item(2); //gets the third child i.e.,<categories>
//making the new <category> node
string xmlContent = "<category>novel</category>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
XmlNode newNode = doc.DocumentElement;
//making the new node completes
categories.AppendChild(newNode); //append the new node to <categories> as a child

Getting XML Elements By ID in C#

XML
<?xml version="1.0" encoding="utf-8" ?>
<animals>
<animal id="fisrt">
<type>Dog</type>
<name>Han</name>
</animal>
<animal id="second">
<type>Cat</type>
<name>Leia</name>
</animal>
</animals>
C#
using System.Xml.Linq;
string id = "second";
var filter = from ab in element.Elements("animal") where ab.Attribute("id").Equals(id) select ab;
foreach (XElement selector in filter)
{
label1.Content = selector.Element("name").Value;
}
What I need help with is selecting elements based on the parent element's id. The goal is to select the name who's parent's id is "second", so I'm trying to get "Leia". The problem I'm encountering is that nothing is happening to the label.
What am I doing wrong and how can I fix this issue. I'm also open to different approach if someone knows of a better way of achieving my goal.
You miss to check the value of attribute:
where ab.Attribute("id").Value.Equals(id)
Hope this help!
How about this:
string name = xdoc.Elements("animal")
.Where (e=>e.Attribute("id")=="first")
.Elements("name")
.Select(e=>e.Value)
.FirstOrDefault();
Essentially you want to put the condition about id attribute inside the where and continue the query.
I know this is the method annotation instead of linq syntax, I prefer it for being easier to read when things get hairy.

Can't add items to ListView from my XMLs in WPF

The main problem is this one.
I have 2 XMLs containing information about what my company does. One is considered the template XML where you can find the general information and the other one is the Catalog containing information about each individual equipment, containing a reference to the template XML.
They look like this
Catalog XML
<list>
<A>
<B>
<c>reference to template</c>
<d>info 2</d>
<e>info 3</e>
<f>info 4</f>
<g>
<h>info5</h>
<i>info5</i>
</g>
</B>
<B>
<c>reference to template</c>
<d>info a</d>
<e>info s</e>
<f>info d</f>
<g>
<h>infof</h>
<i>infog</i>
</g>
</B>
<B>
<c>reference to template</c>
<d>info h</d>
<e>info j</e>
<f>info k</f>
<g>
<h>infot</h>
<i>infoy</i>
</g>
</B>
</A>
</list>
Template
<list>
<R>
<S>
<t>info 7</t>
<u>info 8</u>
<v>info 9</v>
<w>info 10</w>
</S>
</R>
</list>
What I need to do is to display all of the equipment catalogued in a listView, which will lits information from both XMLs.
I've tried that and had no succes, all I can display is one equipment, weel it actually isn'y displayed, all that appears is invisible information.
I run through both XMLs using this:
xDocument load = xDocument.load("Myxml.xml");
var run = (from x in load.Descendants("A")
where x.Element("c").Value == comboBox1.SelectedItems.ToString()
select new
{
a = x.Element("d").Valuye.ToString(),
//here I gather the rest of the information
}).ToList();
listView.Items.Add(run);
//after that I tried listview.Items.Add(run.a) ... but the code which I use to run through
//ends with FirstorDefault(), instead of ToList() and I try adding all the components manually
The only thing that Appears is an invisible Equipment, which means that when I click on it I can see there's something there, but I just can't see the information.
So I tried adding strings using the same methodology, but got the same result.
Can anyone please tell me where's my mistake? I can't see it.
PS: After I manage to do this, I'm gonna Implement a function, that by double clicking on the information, the client will be able to alter the information. If someone knows where to start with this one, please point me in the right direction
I believe your linq query needs a bit of a touch up, something like:
xDocument load = xDocument.load("Myxml.xml");
var run = (from x in load.Descendants("B")
where x.Element("c") == comboBox1.SelectedItems.ToString()
select new
{
a = x.Element("d").Valuye.ToString(),
//here I gather the rest of the information
}).ToList();
Also, you should try using a for loop on the list and adding the strings one by one
foreach (var item in run)
listView.Items.Add(item.a);
You can take a look at the different overloads of the Add method on this MSDN page:
http://msdn.microsoft.com/en-us/library/system.windows.forms.listview.listviewitemcollection.aspx
To be honest, I'm not entirely sure what the question is. I'll take a shot at the XDocument query, though.
When doing comparisons, you need to compare the correct types. The comparison from the example won't even compile because there is a comparison of an XElement with a String:
where x.Element("c") == comboBox1.SelectedItems.ToString()
Should be:
where x.Element("c").Value == comboBox1.SelectedItems.ToString()
If the overall goal is to get a list of strings from the query, then take a look at the following:
string match = comboBox1.SelectedItems.ToString();
var doc = XDocument.Load( "MyXml.xml" );
var query = doc.Descendants( "B" )
.Where( x => x.Element( "c" ).Value == match )
.Select( x => x.Element( "d" ).Value )
.ToList();
Notice that the query starts with the "B" element. Starting at the "A" element will result in 0 elements matched in the where clause.
Also, it is easier to break these types of problems down by using additional variables to break down the statement, i.e., variables for the matching criteria, for the XDocument, for the query, etc... Even the query could be broken down into further sub-queries if needed.
This should get you started.
If you are using the Detail mode of the list view, you need to add columns to the list and subitems in the items corresponding to each column, else your items will be "invisible".
See the ListView.Columns and ListViewItem.SubItems members for more details.
I found out what was wrong, I needed to add an observable collection, to which I added the content I wanted to put on the viewlist, and then I put the information in the observablecollection in the viewlist.
Thank you all for helping me.

Infopath XML query from C#

I have included an XML file in my InfoPath form as a secondary data source. The data connection is named Divisions. Below is the structure and content of the file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Divisions>
<Division>
<Name>Business Application Services</Name>
<Abbr>BAS</Abbr>
</Division>
<Division>
<Name>Network Security Services</Name>
<Abbr>NSS</Abbr>
</Division>
<Division>
<Name>Back Office</Name>
<Abbr>BO</Abbr>
</Division>
</Divisions>
What I need to do is query this file using the Abbr of the division to get the Name.
I have used the following code to create a XPathNodeIterator:
XPathNodeIterator XPNIDivisions = this.DataSources["Divisions"].CreateNavigator().Select("/Divisions/Division/Name");
So now, how do I search for the name of the division whose Abbr is 'NSS'? Or is the code I'm using wrong (I'm new to XML manipulation)?
Please note that these abbreviations and names could change (more could be added, or some could be deleted); so I need it to be dynamic, meaning that if I have the Abbr in a variable MyAbbr, I have to look for its Name.
Thanks in advance,
Yusuf
Can you use Linq to Xml?
I was able to get this to work
string name = XDocument.Load("XMLFile1.xml")
.Descendants("Division")
.Where(x => x.Element("Abbr").Value == "NSS")
.First()
.Element("Name")
.Value;
Finally I've been able to find a solution, and I must say it's a bit more complex than Roger's solution; you have to know a bit about XPath expressions.
So what I had to do was just change the select from before
XPathNodeIterator XPNIDivisions = this.DataSources["Divisions"].CreateNavigator().Select("/Divisions/Division/Name");
to
XPathNodeIterator XPNIDivisions = this.DataSources["Divisions"].CreateNavigator().Select("/Divisions/Division[Abbr=\"thevalue\"]");
where thevalue is of course the value you're looking for.
In fact what I did was define a string variable
StrXPathDiv = "/Divisions/Division[Abbr=\"" + thevalue + "\"]";
and then pass it to the Select() method.
Then use the following code to get the value:
if (XPNIDivisions.MoveNext()) //If any record found
{
XPNIDivisions.Current.MoveToChild(XPathNodeType.Element);
XPNavMyDivision.SetValue(XPNIDivisions.Current.Value);
}
else { XPNavMyDivision.SetValue(""); }
where XPNavMyDivision is the navigator for where I need to set the value.
Thank you very much for your time and help Roger; I would have tried your solution if I were sure everyone had .NET 3.5 installed; however I'm quite sure of the opposite, so I had to stick with this.

Categories