How to retrieve values using XPathNodeIterator - c#

I am trying to select values from elements using the XPathNodeIterator. But I am not able to get the expected value from my XML file.
My XML file
<?xml version="1.0" encoding="utf-8"?>
<ns0:Entity xmlns:ns0="http://schemas.mycompany.com/1.0">
<ns0:Data>
<Address_Id xmlns:ns0="http://schemas.mycompany.com/1.0">7</Address_Id>
<Customer_Id xmlns:ns0="http://schemas.mycompany.com/1.0">67</Customer_Id>
<CustomerName xmlns:ns0="http://schemas.mycompany.com/1.0">My Customer 1</CustomerName>
</ns0:Data>
</ns0:Entity>
My methods to get the values
I have created two methods which both doesn't return the values which I would like to have.
private static string GetValue_Attempt1(XPathDocument xPathDocument, string xpathExpression)
{
var xpathNavigator = xPathDocument.CreateNavigator();
var xpathNodeIterator = xpathNavigator.Select(xpathExpression);
xpathNodeIterator.MoveNext();
return xpathNodeIterator.Current.Value;
}
private static string GetValue_Attempt2(XPathDocument xPathDocument, string xpathExpression)
{
var xpathNavigator = xPathDocument.CreateNavigator();
var xpathNodeIterator = xpathNavigator.Select(xpathExpression);
xpathNodeIterator.MoveNext();
var nodesNavigator = xpathNodeIterator.Current;
var nodesText = nodesNavigator.SelectDescendants(XPathNodeType.Text, false);
nodesText.MoveNext();
return nodesText.Current.Value;
}
My XPath
Get Address_Id
XPath: //Data/Address_Id/text()
Attempt1 returns: 767My Customer 1. Why are all values concatenated ???
Attempt2 returns: 7. Looks OK
Get Customer_Id
XPath: //Data/Customer_Id/text()
Attempt1 returns: 767My Customer 1. Why are all values concatenated ???
Attempt2 returns: 7. Why do I get the Address_Id??
Questions
Probably I am using an incorrect XPath. But also I don't understand the results. In order to understand whats going on. I would like to know:
Is there a better way (method) to get the value of the elements?
What is the XPath string which I need to use?
Why does method GetValue_Attempt1 return a concatinated string?
Why does method GetValue_Attempt2 return the value of only the first element?

Your XPath returns no matches as you're not taking the namespaces into account.
Your 'Attempt 1' calls MoveNext() once, so Value returns all the concatenated text in Data, and in 'Attempt 2' you call MoveNext() twice which positions you inside the Address_Id element.
In addition, Select, even it the XPath was valid, doesn't actually move the cursor.
If you wanted to get the value, you'd do something like this - note I'm including the namespace in the XPath and using the result of SelectSingleNode:
var nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("ns0", "http://schemas.mycompany.com/1.0");
var value = navigator.SelectSingleNode("//ns0:Data/Address_Id", nsm).Value;
However, do you have any reason to be using XPath and XPathDocument here? Using the much more expressive and (more) statically typed LINQ to XML would be far preferable, I'd think:
XNamespace ns = "http://schemas.mycompany.com/1.0";
var doc = XDocument.Parse(xml);
var addressId = (int)doc.Descendants(ns + "Data")
.Elements("Address_Id")
.Single();
var customerId = (int)doc.Descendants(ns + "Data")
.Elements("Customer_Id")
.Single();

Related

Pulling values from individual elements in xml

I am working with a soap response form a wcf service and wish to extract the values form the individual elements. So far I am able to get the list of values from the soap envelope using:
XDocument xDoc = XDocument.Parse(ServiceResult);
List<XElement> ResultsView = xDoc.Descendants()
.Where(x => x.Name.LocalName == "ResultsView")
.ToList();
This gives me the results list:
<a:ResultsView>
<a:Duration>4032</a:Duration>
<a:Metres>41124</a:Metres>
<a:Status>Ok</a:Status>
</a:ResultsView>
I have not been able to get the individual results by querying the ResultsView I can get all the values in a single string which is of no use. Can you suggest a method that will get the values?
The full soap envelope returned is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetLocalDataResponse xmlns="http://tempuri.org/">
<GetLocalDataResult xmlns:a="http://schemas.datacontract.org/2004/07/LocalWcf"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:ResultsView>
<a:Duration>4032</a:Duration>
<a:Metres>41124</a:Metres>
<a:Status>Ok</a:Status>
</a:ResultsView>
</GetLocalDataResult></GetLocalDataResponse></s:Body></s:Envelope>
I have tried a few different methods to extract the values mainly using linq like:
var results = ResultsView.Select(x => new
{
ResultsView = (string)x.Element("Duration"),
duration = x.Element("Duration")
});
The problem is that you're asking for an element without the namespace. If you use the right namespace, you don't need to check for local names or anything like that:
XNamespace ns = "http://schemas.datacontract.org/2004/07/LoacalWcf";
XDocument doc = XDocument.Parse(ServiceResult);
XElement resultsView = doc.Descendants(ns + "ResultsView").Single();
XElement duration = resultsView.Element(ns + "Duration");
Note the use of the + operator to create an XName from an XNamespace and a string.
(It looks like you may well then want to cast duration to int rather than string to get the value in a semantically-useful form.)

C# XML XPath Query Not working

I cant get my Xpath query to work though on paper it should be right. I even tried to get a single node without the attribute and could not even get this ...
What am I doing wrong ?
var trxXml = new XmlDocument();
trxXml.Load(InputTrxFile);
XmlElement root = trxXml.DocumentElement;
var unitTestResult = trxXml.GetElementsByTagName("UnitTestResult");
foreach (XmlElement runinfo in unitTestResult)
{
// Find failed tests, works fine then...
string TestName = runinfo.GetAttribute("testName"); // works fine
// Want to find equivalent TestDefinitions/UnitTest
/* Tried
TestRun/TestDefinitions/UnitTest[#name='thetest']
/TestRun/TestDefinitions/UnitTest[#name='thetest']
TestDefinitions/UnitTest[#name='thetest']
/TestDefinitions/UnitTest[#name='thetest']
UnitTest[#name='thetest']
variations with no attribute test JUST to get a node
Example http://www.csharp-examples.net/xml-nodes-by-attribute-value/
*/
var xpath = string.Format(#"/TestRun/TestDefinitions/UnitTest[#name='{0}']", TestName);
XmlNode node = trxXml.SelectSingleNode(xpath);
XmlNode node2 = root.SelectSingleNode(xpath);
// These all return null
Any query that begins with a / represents an absolute path, i.e. it is from the root of the document. It seems your UnitTestResult element (at the very least) encloses your TestRun elements.
To have your query take into account the current context, you need to reference the current context. This can be selected using ..
Secondly, your XML elements all have a namespace, and this needs to make up part of your query. A prefix needs to be added to a namespace manager, used in your query and the manager passed into the query method.
So, taking these together, define the prefix:
var manager = new XmlNamespaceManager(new NameTable());
manager.AddNamespace("t", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
Change your query:
./t:TestRun/t:TestDefinitions/t:UnitTest[#name='{0}']
And pass the namespace manager to the method:
trxXml.SelectSingleNode(xpath, manager);
XPath and XmlDocument are pain, though. This would all be a lot more straightforward in LINQ to XML:
var doc = XDocument.Load(InputTrxFile);
var unitTestQuery =
from result in doc.Descendants(ns + "UnitTestResult")
let name = (string)result.Attribute("testName")
from unitTest in result.Descendants(ns + "UnitTest")
where (string)unitTest.Attribute("name") == name
select unitTest;
var unitTest = unitTestQuery.Single();

Selecting a node if the attribute is equal to a predefined string

I'm currently using a loop which gives me a variable, which then needs to be fed into an Xpath method to get me any nodes with an attribute equal to my variable. So far, I've learned that Xpath allows you to select a node from the XML document using
root.SelectNodes("Element[#Attribute='SpecificValue']")
However, I'd like to know if there's a way I can insert a predefined variable where the specific value, so I can grab a different set of nodes with each iteration of my loop.
For example something like this:
string attribValue= "test"
root.SelectNodes("Element[#Attribute = attribValue]")
Use string formatting:
string attribValue = "test";
string expression = String.Format("Element[#Attribute = '{0}']", attribValue);
root.SelectNodes(expression);
Using XML Linq
XDocument doc = new XDocument();
XElement root = (XElement)doc.FirstNode;
string attribValue= "test";
var results = root.Descendants("Element").Where(x => x.Attribute("Attribute").Value == attribValue).ToList();​

xPath to get sibling value for element with specific value

Given this XML:
<InitResponse>
<LottoToken>908ec70b308adf10d04db1478ef9b01b</LottoToken>
<GameInfoList>
<GameInfo>
<Draw>
<gameId>L649</gameId>
<draw>3035</draw>
</Draw>
</GameInfo>
<GameInfo>
<Draw>
<gameId>BC49</gameId>
<draw>2199</draw>
</Draw>
</GameInfo>
</GameInfoList>
</InitResponse>
I need to get the draw number based on a specific gameId. For example if I specify gameID L649 I need to get 3035.
The following works in several online evaluators, but not in C#. It says it cannot find it. Suggestions?
/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']
C# Code I've tried:
XmlNode node = xmlDoc.SelectSingleNode("/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']");
... where xmlDoc is an xmlDocument object loaded with the xml. the node variable ends up with a null value which seems to indicate there was no match found.
Here is xpath (with Linq)
var xdoc = XDocument.Load(path_to_xml);
string xpath = "/InitResponse/GameInfoList/GameInfo/Draw[gameId='L649']/draw";
var draw = xdoc.XPathSelectElement(xpath);
if (draw != null) // check if draw with gameId found in xml
value = (int)draw;
Also you can use pure Linq to Xml (but in this case xpath looks more compact):
var draw = xdoc.Descendants("GameInfo")
.SelectMany(g => g.Elements("Draw"))
.SingleOrDefault(d => (string)d.Element("gameId") == "L649");
if (draw != null)
value = (int)draw.Element("draw");
Using XmlDocument
I didn't saw something wrong in your XPath statement, look on the following:
(So i guess there is something else that is wrong)
XmlDocument myDoc = new XmlDocument();
String str = #"<InitResponse>
<LottoToken>908ec70b308adf10d04db1478ef9b01b</LottoToken>
<GameInfoList>
<GameInfo>
<Draw>
<gameId>L649</gameId>
<draw>3035</draw>
/Draw>
</GameInfo>
<GameInfo>
<Draw>
<gameId>BC49</gameId>
<draw>2199</draw>
</Draw>
</GameInfo>
</GameInfoList>
</InitResponse>";
myDoc.LoadXml(str);
XmlNode node =
myDoc.SelectSingleNode("/InitResponse/GameInfoList/GameInfo/Draw/draw[preceding-sibling::gameId='L649']");
The node which returns from the result is: 3035
Note: your first note have to be <InitResponse> otherwise it will returns null

How do I use XPath to get the value of an attribute in c#?

I'd like to pass in an xpath query and return the value of what I find.
I'm looking for the value of an attribute specifically.
_query = "//#RequestType";
I can get the node back, but I'm not sure how to get the string value out of it.
I want to query the following xml for its type attribute and get "xpath" back.
It would also be nice if I could just replace my query and also get the value "yes" back from nice.
<?xml version="1.0" ?>
<test type="xpath">
<nice>yes</nice>
</test>
c#
public string Locate(string message)
{
using (var stream = new MemoryStream(Encoding.GetBytes(message)))
{
var doc = new XPathDocument(stream);
var nav = doc.CreateNavigator();
var result = nav.Select(_query);
if (result != null)
{
return result
}
}
}
My xPath is weak at best but try the following:
var doc = new XPathDocument(stream);
var nav = doc.CreateNavigator();
foreach (XPathNavigator test in nav.Select("test"))
{
string type = test.SelectSingleNode("#type").Value;
string nice = test.SelectSingleNode("nice").Value;
Console.WriteLine(string.Format("Type: {0} - Nice: {1}", type, nice));
}
If you are getting the "test" node back in your query and it contains a child node called "nice" then you need to select the "nice" child node from the parent "test" node and return it's InnerText in order to get the string "yes" out of it.
I am not sure that answers your question because I don't think I completely followed what your were asking but hopefully that helps
XPathNavigator has a Value property, which will return the value of the current node as a string. You can also use ValueAsBoolean, ValueAsDateTime, etc. if you already know the type of your data.

Categories