Ignoring case in SelectSingleNode Xpath not working. - c#

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;
}

Related

Make selectnode insensitive to uppercase or lowercase with namespaces

I have the following XML:
<myfile:bookstore>
<myfile:books>
<myfile:book> Book 1</myfile:book>
<myfile:book> Book 2</myfile:book>
</myfile:books>
</myfile:bookstore>
And the following code to select the <myfile:books> node:
XmlNamespaceManager nsmgr = new
XmlNamespaceManager(el.OwnerDocument.NameTable);
nsmgr.AddNamespace("myfile",
el.OwnerDocument.DocumentElement.NamespaceURI);
var node = el.SelectSingleNode(#"/myfile:bookstore/myfile:books", nsmgr);
How to make this work until the node name is myfile:boOkS or myfile:BOOKS insensitive to upper case and lower case?
Another question is right my namespaceManager ? Can it be more simple ?
You can use the local-name() and namespace-uri() functions to return element names and namespaces in an XPath query, and then use the translate() function to lower-case the local name.
Thus the following should work:
var elementName = "books"; // Or whatever
var nameSpaceUri = el.OwnerDocument.DocumentElement.NamespaceURI;
var xpathQuery = string.Format(#"/myfile:bookstore/*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')='{0}' and namespace-uri()='{1}']", elementName, nameSpaceUri);
var node = el.SelectSingleNode(xpathQuery, nsmgr);
Sample working .Net fiddle.
Note that the lowercasing the return value of the name() function should be avoided in cases like this. E.g. you might be tempted to do the following:
var node = el.SelectSingleNode(#"/myfile:bookstore/*[translate(name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = 'myfile:books']", nsmgr);
However, this should not be used because it hardcodes the namespace prefix myfile: inside the string literal 'myfile:books'. If later the input XML were modified to use a different prefix -- or no prefix at all, such as:
<bookstore xmlns="http://MyRootNameSpace">
<Books>
<Book> Book 1</Book>
<Book> Book 2</Book>
</Books>
</bookstore>
Then your XPath query would break despite the fact that the new XML is semantically identical to the old.

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

Get value from node with same name

I'd like to retrieve information from an XML file, however the way it's formatted is pretty strange. Here it is...
<?xml version="1.0"?>
<Careers>
<CareerList>
<CareerName></CareerName>
<CareerDescription></CareerDescription>
</CareerList>
<CareerList>
<CareerName>Cook</CareerName>
<CareerDescription>Cooks food for people</CareerDescription>
</CareerList>
</Careers>
I'd like to get the 2nd value, which would be Cook and the description which is Cooks food for people, but instead I'm getting only the empty node. For example...
public string CareerDescription(string CareerFile)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(CareerFile);
string Description = xmlDoc.SelectSingleNode("Careers/CareerList/CareerDescription").InnerText;
return Description;
}
How would I select the second node instead of the first?
You can use an index in your XPath expression:
xmlDoc.SelectSingleNode("Careers/CareerList[2]/CareerDescription").InnerText
Personally I'd use LINQ to XML instead, mind you:
var doc = XDocument.Load(CareerFile);
return doc.Root
.Elements("CareerList")
.ElementAt(1) // 0-based
.Element("CareerDescription")
.Value;
Instead of SelectSingleNode you should use SelectNodes: it will return XmlNodeList nodeList. Then you should take the InnerText of the element from that node list with index [1];
public string CareerDescription(string CareerFile)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(CareerFile);
string Description = xmlDoc.SelectNodes("Careers/CareerList/CareerDescription")[1].InnerText;
return Description;
}
Refer to the documentation on this method on MSDN for more details: http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.selectnodes%28v=vs.71%29.aspx
Just a straight way of LINQ to XML routine (and because it is LINQ, I prefer this way much more than the "standard" usage of XmlDocument with the support of XPath):
return XDocument.Load(CareerFile)
.Descendants("CareerDescription").Skip(1).First().Value;

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

Get values from XML, that are noted as "Value=xx" not boxed in

I have a XML file, that looks like this inside:
<Data>
<INFO>
..
...
<JOB_NAME value="filename.pdf"/>
<useless_info value="some_info"/>
<TIMESTAMP value="20120210075304"/>
<more_useless_info value="012345"/>
...
..
</INFO>
<INFO>
..
...
<JOB_NAME value="filename2.pdf"/>
<useless_info value="some_info"/>
<TIMESTAMP value="20120210073487"/>
<more_useless_info value="012345"/>
...
..
</INFO>
</Data>
What i want to do, is write specific info to strings, so that later on i can write these to a text file or new XML file.
I found this example here: http://www.csharp-examples.net/xml-nodes-by-name/
And i have this code a little bit working.
Not fully, because it it not getting the values.
my code looks like this:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
XmlNodeList xnList = xmlDoc.SelectNodes("/Data/INFO");
foreach (XmlNode xn in xnList)
{
string jobName = xn["JOB_NAME"].InnerText;
string timeStamp = xn["TIMESTAMP"].InnerText;
MessageBox.Show(timeStamp + jobName); //for testing
}
I think this has to do with the fact that the info that i want to get, is not boxed in like <box>info</box>
What i cant find is how i could get the info in my caseout of the xml file now.
Could someone lent me a hand?
thank you!
You need to select the JOB_NAME and TIMESTAMP child nodes of each INFO node, then get their attributes, then get the Value of the "value" attribute.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
XmlNodeList xnList = xmlDoc.SelectNodes("/Data/INFO");
foreach (XmlNode xn in xnList)
{
string jobName = xn.SelectSingleNode("JOB_NAME").Attributes["value"].Value;
string timeStamp = xn.SelectSingleNode("TIMESTAMP").Attributes["value"].Value;
MessageBox.Show(timeStamp + jobName); //for testing
}
Be careful with this though as you are likely to get a NullReferenceException if any of the INFO nodes don't contain both a JOB_NAME and TIMESTAMP Node, and also if either of those do not have an attribute "value".
To answer your comment below:
string vendorName = xn.SelectSingleNode("JOB_NAME").Attributes["vendor-name"].Value;
string mediaName = xn.SelectSingleNode("JOB_NAME").Attributes["media-name"].Value;
You're correct in that innerText won't work because in your example 'value' is an attribute.
string jobName = xn["JOB_NAME"].Attributes["value"].Value;
string timeStamp = xn["TIMESTAMP"].Attributes["value"].Value;
Corrected and tested. This method or the SelectSingleNode should work fine.
If you want to iterate over all INFO elements and read the contained JOB_NAME and TIMESTAMPs valueattributes then Tobsey's answer is correct and will give you the desired result.
If you intend to query specific values from your Xml document you should use XPath queries instead of iterating over a set of nodes:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
string jobName = xmlDoc.SelectSingleNode("//JOB_NAME/#value").Value;
string timeStamp = xmlDoc.SelectSingleNode("//TIMESTAMP/#value").Value;
Update
If you have repeating INFO nodes but just are interested in the first one you could change your query to just look at the first one:
string jobName = xmlDoc.SelectSingleNode("//INFO[1]/JOB_NAME/#value").Value;
string timeStamp = xmlDoc.SelectSingleNode("//INFO[1]/TIMESTAMP/#value").Value;
The Xpath query //INFO[1]/JOB_NAME/#value is a shorthand expression for //INFO[position() = 1]/JOB_NAME/#value and will examine the position of the node in your Xml document.
Beware! The simplified example above will fail if .SelectSingleNode returns null!
You should get the Value property of the attribute named "value" in your nodes:
string jobName = xn.SelectSingleNode("JOB_NAME").Attributes["value"].Value;

Categories