I have an application that I'm writing that allows users to search Twitter, written mainly just for fun and learning about how XML and LINQ play together. I've written code to fetch the atom feed (example query: http://search.twitter.com/search.atom?q=twitter), and I can verify that it is, in fact, receiving XML.
Just to get started with parsing the document, I figured it would be simple enough to just parse out the content of each tweet. Once I verify that works, then I would move on to the author, then date, and so on and so forth until everything is parsed.
Here is what I'm using to get the content:
var list = from tweet in doc.Element("feed").Descendants("entry")
select new Tweet("AUTHOR", tweet.Element("content").Value, new DateTime(), "TITLE");
As you can see, the document structure looks something like this:
<feed><entry><content></content></entry>.....</feed>
At least as far as we are concerned. I get a NullReferenceException on this line of code, but the debugger shows that the document is not null (it does in fact have the whole feed loaded in it). The previous line calls XDocument.Parse(), which throws no exceptions.
Does anyone know what could be causing my downfall?
The feed element includes this:
xmlns="http://www.w3.org/2005/Atom"
which changes the default namespace for the elements. You should use that like this:
XNamespace ns = "http://www.w3.org/2005/Atom";
var list = from tweet in doc.Element(ns + "feed")
.Descendants(ns + "entry")
select new Tweet("AUTHOR",
tweet.Element(ns + "content").Value,
new DateTime(), "TITLE");
(Just to explain, you were looking for a namespace-less "feed" element; that didn't exist, so Element returned null. If you'd fixed just that, Descendants would have returned an empty sequence. If you'd fixed those two, tweet.Element("content") would have returned null, causing a different NullPointerException.)
There are several possibilities:
Element("feed") is null
Descendants("entry") is null
tweet.Element("content") is null
You'll need to step through to figure out which is the culprit.
Related
I've been playing with LINQ in VB.Net and some other things in an attempt to delete XML nodes based on attribute values. Basically, if any node in my XML documents has an attribute of a particular value, "cats" for example, I want to delete it.
The catch is I won't really know exactly what the XML structures will look like, so I can't give a path. Also, I know some of the attributes that may contain "cats", but I don't want to hard code them if possible.
So, in other words, I don't have a set XML structure, and I want to delete ANY node that has "cats" as an attribute value, like Caption = "cats" or Title = "cats", anywhere in the node. If it has "cats", nuke it.
Is this at all possible? Or do I just need to give up on this project?
BTW, I'm trying to write the solution in VB.Net, but I am quite capable of reading and converting C# if someone happens to know how to accomplish this but can only give C# code.
Thanks a ton for any help!
You can do this using:
XDocument.Descendants() to iterate through all elements in your document.
XElement.Attributes() to loop through all attributes of an element, to see if any have a value of "cats".
Extensions.Remove() to remove all elements that have an attribute value that matches.
In c# this becomes:
var doc = XDocument.Parse(xmlString);
var attributeValue = "cats";
doc.Descendants().Where(e => e.Attributes().Any(a => (string)a == attributeValue)).Remove();
And in VB.NET:
Dim doc = XDocument.Parse(xmlString)
Dim attributeValue = "cats"
doc.Descendants().Where(Function(e) e.Attributes().Any(Function(a) CStr(a) = attributeValue)).Remove()
Example fiddle.
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
I am trying to get address from the XML file returned by google map api (by giving lat/lng as parameters). I am using the following code.
XDocument doc = XDocument.Load("uri");
var city = doc.Descendants("result").Where(s => s.Descendants("type").FirstOrDefault().Value == "locality");
Then I am reading for a specific descendant
address = Convert.ToString(city.Descendants("formatted_address").First().Value);
which I am trying to show on a map. All works fine for the first time(when I get the lat/lng values based on my IPAddress) but when the user double clicks on the map (am supposed to get the location of the click) the program is crashing. Works fine sometimes. I checked and its because in the xml file returned, sometimes there is no node type "locality" under "result". I wrote if and else if statements for this case but the debugger is still going inside the if loop and later showing the error "Sequence contains no elements". Doing this in a WPF application.
For ex : The only result nodes returned for 27/14 are type country & administrative_area_level_1.
You haven't shown us the XML involved, but I strongly suspect the problem is that you're not specifying the namespace. You probably want something like:
XNamespace ns = "some namespace URI";
XDocument doc = XDocument.Load("uri");
var city = doc.Descendants(ns + "result")
.Where(s => s.Descendants(ns + "type")
.FirstOrDefault().Value == "locality");
Of course there may be multiple namespaces involved - be aware of namespace inheritance due to xmlns="..." as well.
I would personally suggest using a simple cast from XElement to string instead of using the Value property - that way if FirstOrDefault() returns null, the result of the cast is null as well.
(It's not clear that your query really makes much sense, to be honest.)
To retieve the value of a nested XElement I have the following in my code:
XElement xml = new XElement("UserDefinedSettings", new XElement("RootFolder", new XElement("FolderName", "User Input Goes Here")));
xml.Save("UserDefinedSettings.xml");
Which gives me this saved to the hard drive:
<?xml version="1.0" encoding="utf-8"?>
<UserDefinedSettings>
<RootFolder>
<FolderName>User Input Goes Here</FolderName>
</RootFolder>
</UserDefinedSettings>
Later, To retrieve the name of the folder that the user has chosen I am using:
XDocument xdoc = XDocument.Load("UserDefinedSettings.xml");
var myVar = xdoc.Descendants("FolderName").Single();
textBox1.Text = myVar.Value;
I am new to Linq-XML and I am wondering if what I have done is the right way to go about it?
Initially I had been using the following line of code to get the name of the folder, but I knew there had to be a better way and after searching here on SO for examples I am using the code above instead.
string s =xdoc.Element("UserDefinedSettings").Element("RootFolder").Element("FolderName").Value;
What you have should be fine (the newer way), as long as you are certain those elements exist. It runs the risk of throwing a null reference exception however if any of them do not exist. I typically query with Elements() rather than Element(). Elements() returns an IEnumerable which you can safely chain together with more Elements() queries (or whatever). For example, you might consider:
var folder = (string)xdoc.Elements("UserDefinedSettings").Elements("RootFolder").Elements("FolderName").FirstOrDefault();
Another thing I typically do when I want the value from an attribute or element is cast my XElements and XAttributes with a string as I did above. A null value cast as a string will return a null string, preventing a null reference exception that you would get with a .Value call.
Also would work:
xdoc.XPathSelectElement("/UserDefinedSettings/RootFolder/FolderName").Value
I prefer to use XPath for it's succinctness but it's your choice.
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.