Convert LinqToXml expression to XPath - c#

I have this linq expression that I need to convert to XPath select element:
XElement transIdElement = (from criteria in searchCriteria.Descendants("CRITERIA")
where criteria.Element("DataPointName).Value == "TransactionNumber"
select criteria.Element("CriteriaComparisonValue")).FirstOrDefault();
and I tried with this:
var transIdElement = searchCriteria.XPathSelectElement("//CRITERIA/DataPointName[text() = 'TransactionNumber']/CriteriaComparisonValue");
but I get null as result.
XML sample is:
<SEARCH>
<ADVANCED_CRITERIA>
<CRITERIA>
<DataPointName>TransactionNumber</DataPointName>
<CriteriaComparisonValue>12457845</CriteriaComparisonValue>
</CRITERIA>
</ADVANCED_CRITERIA>
</SEARCH>

void Main()
{
string xml = #"<SEARCH>
<ADVANCED_CRITERIA>
<CRITERIA>
<DataPointName>TransactionNumber</DataPointName>
<CriteriaComparisonValue>12457845</CriteriaComparisonValue>
</CRITERIA>
</ADVANCED_CRITERIA>
</SEARCH>";
var searchCriteria = XElement.Parse(xml);
searchCriteria.XPathSelectElements("//CRITERIA[DataPointName='TransactionNumber']/CriteriaComparisonValue").Dump();
}

Use .. to access parent::node() and try
//CRITERIA/DataPointName[text() = 'TransactionNumber']/../CriteriaComparisonValue
quick test in LINQpad:
var f = #"c:\temp\x\a.xml";
var searchCrit = XDocument.Load(f);
searchCrit.XPathSelectElement("//CRITERIA/DataPointName[text() = 'TransactionNumber']/../CriteriaComparisonValue").Value.Dump();
produces
12457845

A more accurate XPath translation of your LINQ-to-XML query would be as follow :
//CRITERIA[DataPointName = 'TransactionNumber']/CriteriaComparisonValue
Here is a glimpse of how the above predicate expression (expression in square brackets) works.
DataPointName references child element named "DataPointName" of the context element. In this particular predicate, the context element is CRITERIA.
'TransactionNumber' with quotes surround means literal-string in XPath. When an element compared to a string in XPath, the element is translated into string by calling XPath string() function.

Related

Ignoring case in SelectSingleNode Xpath not working.

I am trying the below sample to select a node by ignoring the case and the select single node retuns null.
XmlDocument doc = new XmlDocument();
doc.LoadXml("<root><CHILD1>c1</CHILD1><CHILD2>c2</CHILD2></root>");
var node = doc.SelectSingleNode("root");
string nodeXpath = string.Format("//*[translate(#key, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '{0}']","child1");
node = node.SelectSingleNode(nodeXpath);
string innertext = node.InnerText;
Can someone help.
#key in XPath means a reference to an attribute named key. There is no such attribute in your XML. If you meant to match by element name then you're supposed to use name() or local-name() instead :
...
string xpath = "//*[translate(name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '{0}']";
string nodeXpath = string.Format(xpath,"child1");
...
You can use LINQ to Xml in little bid more readable way
XDocument doc = XDocument.Parse("<root><CHILD1>c1</CHILD1><CHILD2>c2</CHILD2></root>");
var singleNode =
doc.Root
.Elements()
.FirstOrDefault(element => element.Name.ToString().ToLower().Equals("child1"));
But notice that XML support different nodes where name can be case sensitive(for example "Node" and "node") and "searching" elements in "ignore case" way can lead to problems in the future.
I was working through this today and I used your solution. I just wrapped it in a function and call it whenever I need to match on an element name that's under the root. Works like a charm. Thanks!
private string GetNodeXpathCaseInsensitive(string value)
{
string xpath = String.Format("//*[translate(name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '{0}']", value.ToLower());
return xpath;
}

How to retrieve values using XPathNodeIterator

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();

Xpath to find first occurrence of two different elements

Using the example below, I would like to use xPath to find the first occurence of two different elements. For example, I want to figure out if b or d appears first. We can obviously tell that b appears before d (looking top-down, and not at the tree level). But, how can I solve this using xpath?
<a>
<b>
</b>
</a>
<c>
</c>
<d>
</d>
Right now, I find the node (b and d in this case) by getting the first element in the nodeset, which I find using the following code:
String xPathExpression = "//*[local-name()='b']";
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(xPathExpression);
and
String xPathExpression = "//*[local-name()='d']";
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(xPathExpression);
Now using xpath, I just can't figure out which comes first, b or d.
You want to scan the tree in document order (the order the elements occur). As though by chance this is the default search order, and all you've got to do is select the first element which is a <b/> or <d/> node:
//*[local-name() = 'b' or local-name() = 'd'][1]
If you want the name, add another local-name(...) call:
local-name(//*[local-name() = 'b' or local-name() = 'd'][1])
If you wanted to use LINQ to XML to solve the same solution you can try the following:
XDocument xmlDoc = new XDocument(filepath);
XElement first = (from x in xmlDoc.Descendants()
where x.Name == "b" || x.Name == "d"
select x).FirstOrDefault();
Now you can run a simple if statement to determine if "b" or "d" was the first element found that matches our criteria.
if(first.Name.Equals("b")
//code for b being first
else if(first.Name.Equals("d")
//code for d being first
Per a commentator's suggestion your code would is cleaner to use a lambda expression instead of a full LINQ query, but this can sometimes be confusing if you are new to LINQ. The following is the exact same query for my XElement assignment above:
XElement first = xmlDoc.Descendants().FirstOrDefault(x => x.Name == "b" || x.Name == "d");
I hope that's what you're looking for if you were open to not using xpath.
This XPath expressions would work:
//*[local-name()='b' or local-name()='d'][1]
Or for a shorter solution, you could try this:
(//b|//d)[1]
Both expressions will select either b elements or d elements, in the order in which they appear, and only select the first element from the result set.

what's wrong in this LINQ query

I have the following XML
<School Version="30">
<Math>
<Lesson1 Type="Active">Introduction</Reset_mode>
<Lesson2 Type="Active">Fundamentals</Reset_mode>
</Math>
</School>
I want to get the subelements lesson1, lesson2
I load the XML in XDocument
I have to question - what's wrong in this query
var nodes = from C in document.Element("School").Elements()
where document.Element("School").Elements().Contains(t => t.Name == "Math")
select C ; //shortcutsXMLDocument.SelectNodes(Query);
and it raise and error.
Also can I use XPath with XDocument?
You are looking for a ShortcutList element :
Element("ShortcutList")
Which does not exists.
You should write this to get subLessons :
var items = document.Element("School").Element("Math").Elements();
foreach(var item in items)
{
DoSomething(item);
}
LINQ to XML:
from subject in XDocument.Load(xml).Element("School").Elemens()
where subject.Name == "Math"
select subject.Elements();
XPath using XmlDocument:
var doc = new XmlDocument();
doc.LoadXml(xml);
var nodes = doc.SelectNodes("School/Math/Lesson1 or School/Math/Lesson2");
document.Decendants("Math");
yields you a IEnumrable with your two elements.
it's equal to the xpath //Math
Xpath case used on XElement/XDocument from this namespace System.Xml.XPath.
here is your query for path...
var nodes = from C in document.XPathSelectElements("./Math")
select C; //shortcutsXMLDocument.SelectNodes(Query);

Most elegant way to query XML string using XPath

I'm wondering what the most elegant way is in C# to query a STRING that is valid xml using XPath?
Currently, I am doing this (using LINQ):
var el = XElement.Parse(xmlString);
var h2 = el.XPathSelectElement("//h2");
Simple example using Linq to XML :
XDocument doc = XDocument.Parse(someStringContainingXml);
var cats = from node in doc.Descendants("Animal")
where node.Attribute("Species").Value == "Cat"
select node.Attribute("Name").Value;
Much clearer than XPath IMHO...
Just for the record, I did not want to go with Linq2XML but XPath and found this way:
var xPathDoc = new XPathDocument(new StringReader("your XML string goes here"));

Categories