I'm trying to parse an xml file using LINQ, but as I understand the query returns null. (It's WP7)
Here's the code:
var resultQuery = from q in XElement.Parse(result).Elements("Question")
select new Question
{
QuestionId = q.Attribute("id").Value,
Type = q.Element("Question").Attribute("type").Value,
Subject = q.Element("Subject").Value,
Content = q.Element("Content").Value,
Date = q.Element("Date").Value,
Timestamp = q.Element("Timestamp").Value,
Link = q.Element("Link").Value,
CategoryId = q.Element("Category").Attribute("id").Value,
UserId = q.Element("UserId").Value,
UserNick = q.Element("UserNick").Value,
UserPhotoURL = q.Element("UserPhotoURL").Value,
NumAnswers = q.Element("NumAnswers").Value,
NumComments = q.Element("NumComments").Value,
};
"result" is the xml string, just like this one.
http://i48.tinypic.com/1ex5s.jpg (couldn't post properly formatted text so here's a pic : P )
Error:
http://i48.tinypic.com/2uyk2ok.jpg
Sorry, if I haven't explained it properly and if this has already been asked (tried searching but didn't help).
You have run into an XML namespace problem. When you are just querying "Question", the string is translated into an XName with the default namespace. There are no elements in the default namespace in your XML, only elements in the urn:yahoo:answers namespace (see the top level element, where it says xmlns="urn:yahoo:answers").
You need to query the correct XML namespace, like this:
var ns = new XNameSpace("urn:yahoo:answers");
var resultQuery = from q in XElement.Parse(result).Elements(ns + "Question");
When picking out the individual properties, remember to add the namespace also.
XName is a class that represents an XML name, which might have a namespace defined by XNameSpace. These two classes has an implicit conversion operator implemented that allows you to implicitly convert from string to XName. This is the reason the calls work by just specifying a string name, but only when the elements are in the default namespace.
The implicitness of this makes it very easy easier to work with XML namespaces, but when one does not know the mechanism behind, it gets confusing very quickly. The XNameclass documentation has some excellent examples.
Two ways to fix it:
Add the root element was part since Elements only search one level - XElement.Parse(result).Root.Elements("Question")
Use the Descendants method since that will search the entire xml tree.
Related
I'm migrating code from .Net Framework to Core and without getting into the reason why I want to do this, I'll just keep it simple and focus on what I want to do.
Let's say I have a jsonString that contains "Name". I parse the string with:
JsonNode jn = JsonNode.Parse(jsonString);
And the get the value with:
string name = jn["Name"].ToString();
How can I override, extend, whatever so I don't have to use the .ToString() part and retrieve a JsonNode element value like:
string name = jn["Name"];
You can't and shouldn't for a reason: JsonNode can represent any type of value. You need to be explicit.
edit for clarity: implicit conversion is something like
string name = jn["Name"];
while explicit conversion is
string name = (string)jn["Name"];
JsonNode doesn't have an implicit to string conversion. But there is an explicit one.
Note: there is no way extend a class with an extra operator. You would need to edit the source (which is a bad idea). So what you want is not possible.
Might I suggest a more robust approach?
Define a type and deserialize that:
public record MyData(string Name);
var data = JsonSerializer.Deserialize<MyData>(jsonString);
var name = data.Name;
Or you can try the good-old "dictionary of strings" approach.
var data = JsonSerializer.Deserialize<Dictionary<string,string>>(jsonString);
var name = data["Name"];
If I have xml such as the following:
<ValidationResults>
<ValidationResult>
<Scheme>Scheme A</Scheme>
</ValidationResult>
<ValidationResult>
<Scheme>Scheme B</Scheme>
</ValidationResult>
I want to query it using Linq to Xml so that I return:
<ValidationResults>
<ValidationResult>
<Scheme>Scheme A</Scheme>
</ValidationResult>
</ValidationResults>
If I do something like this:
....Element("ValidationResults").Elements("ValidaitonResult")
.Where(x => x.Element("Scheme").Value == "Scheme A");
I return only:
<ValidationResult>
<Scheme>Scheme A</Scheme>
</ValidationResult>
Then I am currently creating the parent again and adding the children to it, which does not seem correct:
var parentElement = new XElement("ValidationResults");
var childElements = //get as above
parentElement.Add(childElements);
How do I return a queried subset of elements inside it's original parent?
Your current approach seems the simplest one to me, although it could be done in a single statement as:
var element = new XElement("ValidationResults",
doc.Element("ValidationResults")
.Elements("ValidationResult")
.Where(x => x.Element("Scheme").Value == "Scheme A")));
Assuming you don't want to modify your existing document, you either have to come up with a new "root" validation results element like this or you need to clone the existing one and remove anything you're not interested in. Just copying in the elements you do want seems simpler than pruning out what you don't want.
Of course, you could avoid the duplication of the string literal pretty easily - extract that as a constant somewhere, so you can make sure that you're creating a new element with the same name as the old one.
I am still working on a project and I am enjoying it greatly.
I wanted to see if I could implement a live updating feed using XML
at the moment I dont even know how to parse this particular type of XML as all the tutorials I have found are for parsing node values etc
but I was thinking something along the lines of this
<Object name="ObjectName" type="ObjectType" size="ObjectSize" image="ObjectImage" />
if you guys could help me understand how to access the inner elements of from that node that would be amazing, and if it is not too much to ask just a small explanation so I understand. I know how to parse XML that looks like this using XElement
<Object>
<Name>ObjectName</Name>
<Type>ObjectType</Type>
<Size>ObjectSize</Size>
<Image>ObjectImage</Image>
</Object>
I just cant seem to parse the example at the top, I dont mind if its Linq as long as it is in C#, maybe tell me why you would chose one over the other? Also have you got any idea on how to perhaps check if the file has changed, so I could implement a live update?
Thanks for your Help
John
The example at the top uses attributes instead of sub-elements but it's just as easy to work with:
XElement element = XElement.Parse(xml);
string name = (string) element.Attribute("name");
string type = (string) element.Attribute("type");
string size = (string) element.Attribute("size");
string image = (string) element.Attribute("image");
I usually prefer to use the explicit string conversion instead of the Value property as if you perform the conversion on a null reference, you just end up with a null string reference instead of a NullReferenceException. Of course, if it's a programming error for an attribute to be missing, then an exception is more appropriate and the Value property is fine. (The same logic applies to converting XElement values as well, by the way.)
If you have a domain object that represents your document (usually the case), then the XmlSerializer is quite easy to use.
[XmlRoot("Object")
public class Item
{
public string Name { get; set; }
public string Type { get; set; }
public string Size { get; set; }
public string Image { get; set; }
}
Usage:
XmlSerializer ser = new XmlSerializer(typeof(Item));
Item item = (Item)ser.Deserialize(someXmlStream);
I find using this approach easier than manual parsing when an entire document represents a domain object of some kind.
Use can also use XEelment.FirstAttribute to get the first attribute on the element and then XAttribute.NextAttribute to loop through them all. This doesn't rely on you knowing that the attribute is present.
XAttribute attribute = element.FirstAttribute;
while (attribute != null)
{
// Do stuff
attribute = attribute.NextAttribute`
}
I have this XML file that I parse into its elements and create a list of a custom object Module.
XDocument kobra = XDocument.Load(new StringReader(results.OuterXml));
XNamespace ns = "#RowsetSchema";
var kobraNodeList = from s in kobra.Descendants(ns + "row")
select new Module
{
id = s.Attribute("ows_ID").Value,
name = s.Attribute("ows_Title").Value,
sourceFile = s.Attribute("ows_Source_x0020_Message_x0020_File_").Value,
scope = Scope.KOBRA,
component = string.Empty
};
and here's my Module struct:
public struct Module
{
public string name;
public Scope scope;
public string component;
public int wordCound;
public string id;
public string sourceFile;
}
The code works fine, but things get weird when I try to convert the var kobraNodeList into a list of Modules, I get a System.NullReferenceException at the AddRange line:
this.moduleList = new List<Module>;
this.moduleList.AddRange(kobraNodeList);
When trying to debug, I notice that although kobraNodeList.Count() also returns System.NullReferenceException, a kobraNodeList.Any() returns true, and kobraNodeList.First() returns a perfectly valid and correct Module struct with the desired data.
The XML file is valid, and if I replace the linq query with this:
var kobraNodeList = from s in kobra.Descendants(ns + "row")
select s;
I get a valid list of XElement, which I can Count() ok.
Can someone explain me what's wrong? BTW, I'm using .NET 3.5.
That looks like one (or more) of kobra.Descendants has ows_ID, ows_Title or ows_Source_x0020_Message_x0020_File_ attribute missing.
Linq uses deferred execution, so it won't try to build the sequence until you ask for the items. When you call Any() or First(), it only needs the first item in the sequence to work, which tells me that the first item in kobra.Descendants does have all of the required nodes.
However, one of the items after the first is probably missing at least one of those attributes - so you end up asking for the Value of a NULL attribute.
Inside
select new Module
{
// properties...
}
You could be running into a NullReferenceException as you access .Value on elements that might not exist in the XML document. Your first object in the collection is likely fine, hence your results when using Any() or First(). Subsequent items could be missing elements/attributes you are trying to use.
Try this as a replacement instead of using .Value directly.
id = (string)s.Attribute("whatever") // etc.
One of your lines such as s.Attribute("ows_Source_x0020_Message_x0020_File_") will be returning null for one of the records so s.Attribute("ows_Source_x0020_Message_x0020_File_").Value would cause the null reference exception.
i have a coode that are trying to get the id of xml file where url exist.
My code
public static string GetPageIdByUrl(string url)
{
string folder = HttpContext.Current.Server.MapPath("/App_Data/Pages/" + Path.DirectorySeparatorChar);
foreach (string file in Directory.GetFiles(folder, "*.xml", SearchOption.TopDirectoryOnly))
{
var xml = XDocument.Load(file);
var q = from f in xml.Descendants("Page")
where (string)f.Element("Url").Value == url
select (string)f.Attribute("Id").Value;
if (q.Count() != 0)
{
return q.SingleOrDefault();
}
}
return null;
}
I get this error:
Object reference not set to an instance of an object.
Line: select f.Attribute("Id").Value;
if "test" exist in a xml file <Url>test</Url> i want it to return the attribute ID. But that sems only work for the first xml file.
Hope you get the problem.
Try posting some sample XML.
I'm guessing your Url element doesn't have an "Id" (exact spelling) attribute. Therefore the XNode is null and cannot provide a Value.
Is Id an attribute on the Url element?
<Pages>
<Page><Url Id="6428">http://www.example.com/</Url></Page>
</Pages>
Then you need something like this:
var q = from f in xml.Descendants("Page")
where (string)f.Element("Url") == url
select (string)f.Element("Url").Attribute("Id");
(Note that you can leave out .Value when using casts as XElement and XAttribute define casts to various base types. This is often a source of unexpected NullReferenceExceptions.)
I mabye tirred today,the problem was that in the xml file the attribute was calld "id" and not "Id".
Have you considered using XPath instead?
It's been a while since I've used the syntax, but the query should look something similar to this:
/Page[Url='THE_STRING']
Using SelectNodes(), you should get a list of nodes that fit to your query, and from whom you could get the attribute's value.