issue selecting descendants in LINQ to XML - c#

This is driving me a little crazy. I am pulling an XML string from a database column and successfully creating an XDocument using XDocument.Parse. I've used linq to xml before to query xml trees but for some reason on this everything I am doing is returning null. Is it something to do with the namespace?
Here is a sampling of the text visualizer for my XDocument object:
// removed XML for privacy reasons
An example of the query I am trying:
XElement algorithmId = (from algoId in reportLogXml.Descendants(ALGORITHM_ID)
select algoId).FirstOrDefault();
I am using a constant for the string value and I have quadruple checked that the spelling matches as well as trying several different elements that are clearly in the document but they all return null. What am I doing wrong here?

Yes, it probably has to do with the namespace but also the <AlgorithmId> element has no descendants.
You can fix the ns problem like this:
//untested
XNameSpace ns0 = "http://schemas.datacontract.org/2004/07/Adapters.Adapter";
var ns1 = reportLogXml.Root.GetDefaultNamespace();
// check: ns0 and ns1 should be equal here
... in reportLogXml.Descendants(ns1 + ALGORITHM_ID)
Note that this is a special + operator, follow the format exactly.

Related

Querying CIM XML in C# with LINQ

I am looking for a way to query CIM XML files with LINQ, in order to speed up my analysis of data and verify the exports from a database containing the elements of electrical network.
Since I am a newbie in C#, I guessed that it will be easy to load the CIM XML in console application and based on tag values filter some elements. However, all tutorials that I found so far are straight-forward where elements are named like "Student", "Purchase", etc.
My elements are named like "cim:LoadBreakSwitch", where "cim" is actual address defined in root node.
This way, when I try to select all elements named like "cim:LoadBreakSwitch", I get an exception thrown on Run Time because the name of a element cannot contain colon.
Example of element that I want to select from the CIM XML file:
<cim:LoadBreak rdf:ID="101">
<cim:ConductingEquipment.phases>A</cim:ConductingEquipment.phases>
<cim:IdentifiedObject.description>PoleMounted</cim:IdentifiedObject.description>
</cim:LoadBreak>
When I print in the console the names of all elements with
IEnumerable<XElement> elements = xmlDoc.Elements();
foreach (var item in elements)
{
Console.WriteLine(item.Name);
}
I get something like
"{http://[address from the root node]}LoadBreak".
I don't know if there is possibility to do it, but I am just curious did anyone who is experienced developer had need to do something similar.
Your XML is missing a root element with namespace declarations.
Here is a conceptual example for you. It shows how to handle namespaces and query XML with them.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<rdf:RDF xmlns:cim='http://iec.ch/TC57/2008/CIM-schema-cim13#'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<cim:LoadBreak rdf:ID='101'>
<cim:ConductingEquipment.phases>A</cim:ConductingEquipment.phases>
<cim:IdentifiedObject.description>PoleMounted</cim:IdentifiedObject.description>
</cim:LoadBreak>
</rdf:RDF>");
XNamespace cim = xdoc.Root.GetNamespaceOfPrefix("cim");
XNamespace rdf = xdoc.Root.GetNamespaceOfPrefix("rdf");
foreach (XElement xelem in xdoc.Descendants(cim + "LoadBreak").Elements())
{
Console.WriteLine("{0}: {1}"
, xelem.Name.LocalName
, xelem.Value);
}
}
Output
ConductingEquipment.phases: A
IdentifiedObject.description: PoleMounted

Extract data from a XML string using linq vs xmlDocument

I have done the below many times using a xmlDocument approach, but I wanted to use the more powerful linq to xml approach.
However, I seem to have run into a wall.
I am getting data back from a restful API from twillio / crmText.
Here is a link to their site where their docs live:
http://crmtext.com/api/docs
Here is my XML string:
<response op="getcustomerinfo" status="200" message="ok" version="1.0">
<customer>
<totalMsg>3</totalMsg>
<custId>9008281</custId>
<custName></custName>
<timestamp>2015-04-30 16:17:19</timestamp>
<optinStatus>3</optinStatus>
<custMobile>6185551212</custMobile>
<subacct>1st Choice Courier</subacct>
</customer>
</response>
I need to find out the optinStatus. It should return 3.
I am using the below, which return the above xml
XDocument xdoc = XDocument.Parse(result1);
I have tried about 4000 different things, including:
IEnumerable<XElement> otinStatus = from el in xdoc.Elements("customer") select el;
IEnumerable<XElement> otinStatus2 = from el in xdoc.Elements("cusotmer.optinStatus") select el;
IEnumerable<XElement> otinStatus3 = from el in xdoc.Elements("optinStatus") select el;
All of which returns no results.
Please help, I know this is something simple I am missing.
Thank you in advance -- Joe
Retrieve an Element's Value
var status = xDoc
.Descendants("optinStatus") // get the optinStatus element
.Single() // we're expecting a single result
.Value; // get the XElement's value
Example
Here is a working Fiddle for you. You can see it running live here. The output is 3.
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
public class Program
{
public static void Main()
{
var xDoc = XDocument.Parse(xmlString);
var status = xDoc.Descendants("optinStatus").Single();
Console.WriteLine(status.Value);
}
private static string xmlString = #"
<response op=""getcustomerinfo"" status=""200"" message=""ok"" version=""1.0"">
<customer>
<totalMsg>3</totalMsg>
<custId>9008281</custId>
<custName></custName>
<timestamp>2015-04-30 16:17:19</timestamp>
<optinStatus>3</optinStatus>
<custMobile>6185312349</custMobile>
<subacct>1st Choice Courier</subacct>
</customer>
</response>
";
}
Explanation
Descendents() is an instance axes method (or just axes in shorthand). It returns an IEnumerable<XElement> of all matching descendents. On its results, we call Single(). It is a Linq method that returns the only element of a sequence. If there is more than one element, it throws an error. We're left with a single XElement. This represent an entire XML element. Since we only want its value not the entire element, we call the Value property. Bingo, we're done.
A Bit More Detail
Axes come in two kinds:
Instance axes methods, which MSDN lists here, are invokable members of the XElement, XDocument, and XNode classes.
Extension axes methods, which MSDN lists here, are invokable on collections.
With one exception, an axes method returns a collection of type IEnumerable<T>. The exception is Element(), which returns the first matching child object. That what AmatuerDev used and, as in your question, if you are only expecting a single result, it is a just as good if not better approach that is Descendants().
Retrieve an Attribute Value
Once we have an XElement, we can retrieve one of its attributes instead of its value. We do that by calling the Attributes() method. It returns the matching XAttribute. Since we only want the attribute value, we call the Value property. Voila.
// for attribute
var response = xDoc.Descendants("response").Single();
var attr = response.Attribute("status");
General Approach
Using Linq to XML is a two step process.
Call an axes method to obtain an IEnumerable<T> result.
Use Linq to query that.
See Also
Here is some relevant MSDN documentation:
How to: Retrieve the Value of an Element (LINQ to XML)
LINQ to XML Axes
Assuming xDoc being the XDocument. Have you tried..
var customer = xDoc.Root.Element("customer");
var optinStatus = customer.Element("optinStatus");
var optinStatusValue = optinStatus.Value;

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

Reading specific values from XML response generated by google maps api

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.)

reading an XML string using LINQ

I am calling a sharepoint service /_vti_bin/usergroup.asmx from my silverlight app. In that the method GetAllUserCollectionFromWeb() returns the XML string. I need to iterate through that XML string to get the required data. But the LINQ to XML in this scenario is not working, as it is working when loading the XML file and getting the req data. How to do the similar functionality of LINQ to SQL with an XML string?
Sample code:
string str = #"<LanguageDetails>
<UserNode>
<Lang>
English
</Lang>
</UserNode>
</LanguageDetails>";
Need to handle the similar string and iterate to read the value using LINQ to XML.
You mean something like this?
string str = #"<LanguageDetails>
<UserNode>
<Lang>
English
</Lang>
</UserNode>
</LanguageDetails>";
XElement xLanguageDetails = XElement.Parse(str);
foreach (XElement xUserNode in xLanguageDetails.Elements("UserNode"))
{
}
In almost all cases where you return no rows when doing LINQ to XML queries, the reason is because there is a namespace in your XML. Check the root nodes to see if there are any namespaces and include them in your LINQ queries.

Categories