I am writing a C# function where I need to fetch the value of a node using Xpath and if the fetched values matches a given string, I need to pass the Xpath to an API which replaces the current value of the node with value stored in Database.
The problem is there are multiple matches for the given Xpath, I filter those out with the string matching criteria and am able to figure out the node, however, I am not getting how to capture the exact Xpath of matching node and pass it to the API for it to work.
Lets take this XML as an example
<GrandParent>
<Parent>
<Child1>John
</Child1>
<Child2>Emily
</Child2>
</Parent>
<Parent>
<Child1>Frank
</Child1>
<Child2>Niki
</Child2>
</Parent>
<Parent>
<Child1>Mia
</Child1>
<Child2>Noah
</Child2>
</Parent>
</GrandParent>
Now I will have to fetch the node with Xpath /GrandParent/Parent/Child1 whose value would be John.
I am doing that in C# using XPathNavigator and XPathIterator
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(requestXML);
XPathNavigator nav;
nav = xmlDoc.CreateNavigator();
XPathNodeIterator allMatchingNodes = nav.Select(SourceXPath);
int countNodes = 1;
foreach (XPathNavigator node in allMatchingNodes)
{
if(node.Value.Equals("John"))
{
Xpath = SourceXPath + "[" + countNodes + "]";
break;
}
}
However, this would be an incorrect approach as it will create xpath as /GrandParent/Parent/Child1[1] and hence the subsequeent API replaces is incorrectly.
I would want xpath as /GrandParent/Parent[1]/Child1 is there someway of doing that without using multiple foreach?
Related
I have the following xml file
<root xmlns="http://mynamespace">
<parent>
<first>text</first>
<second>more</second>
</parent>
<parent>
<first>2</first>
<second>3</second>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>
I'm trying to get first and second children of parent.
C# seems to have problems with the following code (the error is on the last line):
var rawXml = #"<root xmlns=""http://mynamespace"">
<parent>
<first>text</first>
<second>more</second>
<third>hello</third>
</parent>
<parent>
<first>2</first>
<second>3</second>
<parent>
<first>a</first>
<second>b</second>
</parent>
</parent>
<parent>
<first>aa</first>
<second>bb</second>
</parent>
</root>";
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(rawXml);
var ns = new XmlNamespaceManager(xmlDoc.NameTable);
ns.AddNamespace("m", "http://mynamespace");
var nav = xmlDoc.CreateNavigator();
var parents = nav.Select("//m:parent", ns);
Console.Write($"Got {parents.Count} parents.");
// this does not work
// error: Expression must evaluate to a node-set.
//var siblings = nav.Select("//m:parent/(m:first|m:second)", ns);
// but this does
var siblings = nav.Select("//m:parent/m:first|//m:parent/m:second", ns);
Console.Write($"Got {siblings.Count} children.");
Am I missing something? Is the first XPath expression wrong?
Is the first XPath expression wrong?
Yes, it's not valid XPath 1.0 syntax. You can't have a ( after a / in XPath 1.0.
You can achieve what you're trying to do, without repeating any node names, by using this path:
/m:root/m:parent/*[self::m:first or self::m:second]
Side note: avoid using // unless you have a specific reason to use it. It's bad for performance.
I have "XML" as below:
<ParentNode>
<ChildNode id="1" Display_Name="ABC"/>
<ChildNode id="2" Display_Name="DEF"/>
<ChildNode id="3" Display_Name="DAX"/>
<ChildNode id="4" Display_Name="LAM"/>
<ChildNode id="5" Display_Name="PKR"/>
<ChildNode id="6" Display_Name="UYA"/>
</ParentNode>
I want to get list of all the Nodes in XMLNodeList in C# using xPath having "A" [regardless of capitals or small] in Display_Name attribute.
What I've tried is:
root.SelectNodes("descendant-or-self::*[contains(#DISPLAY_NAME,'end')]")
Here, root is containing my XML and it is an object of XMLDocument.
Also, how can I make this filter by ignoring either Display_Name is in small letters or capital letters.
"I want to get list of all the Nodes in XMLNodeList in C# using xPath having "A" [regardless of capitals or small] in Display_Name attribute. "
Nature of XML and XPath is case-sensitive. There is no pretty way to do case-insensitive matching using XPath (at least in XPath 1.0, version that is supported by .NET). One known way is using translate() to convert Display_Name value to lower-case before doing further comparison, something like this (see related post) :
var xpath = #"//*[
contains(
translate(#Display_Name
,'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
,'abcdefghijklmnopqrstuvwxyz'
)
,'a'
)
]";
var result = root.SelectNodes(xpath);
Try with below XPath
/ParentNode/ChildNode/#Display_Name
To get result for both
Above XPath will return you all results of ChildNode. Now iterate this XPath to extract all results
Hope it will help you :)
Use OuterXml method.
Try this:
//Load Data
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
//Go the xPath
XmlNode titleNode = xmlDoc.SelectSingleNode(xPath);
//Get the OutXml (You dont need to use a new variable)
string nodeValue = titleNode.OuterXml;
//Load this string as a new XmlDocument and use the second xPath
XmlDocument xmlDoc2 = new XmlDocument();
xmlDoc2.LoadXml(nodeValue);
titleNode = xmlDoc.SelectSingleNode(xPath2);
I have XML String:
<GroupBy Collapse=\"TRUE\" GroupLimit=\"30\">
<FieldRef Name=\"Department\" />
</GroupBy>
<OrderBy>
<FieldRef Name=\"Width\" />
</OrderBy>
I am new in C#. I tried to read the Name attribute of the FieldRef element for both elements but I could not. I used XMLElement , is there any way to pick these two values?
Despite the posting of invalid XML (no root node), an easy way to iterate through the <FieldRef> elements is to use the XmlReader.ReadToFollowing method:
//Keep reading until there are no more FieldRef elements
while (reader.ReadToFollowing("FieldRef"))
{
//Extract the value of the Name attribute
string value = reader.GetAttribute("Name");
}
Of course a more flexible and fluent interface is provided by LINQ to XML, perhaps it would be easier to use that if available within the .NET framework you are targeting? The code then becomes:
using System.Xml.Linq;
//Reference to your document
XDocument doc = {document};
/*The collection will contain the attribute values (will only work if the elements
are descendants and are not direct children of the root element*/
IEnumerable<string> names = doc.Root.Descendants("FieldRef").Select(e => e.Attribute("Name").Value);
try this:
string xml = "<GroupBy Collapse=\"TRUE\" GroupLimit=\"30\"><FieldRef Name=\"Department\" /></GroupBy><OrderBy> <FieldRef Name=\"Width\" /></OrderBy>";
xml = "<root>" + xml + "</root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
foreach (XmlNode node in doc.GetElementsByTagName("FieldRef"))
Console.WriteLine(node.Attributes["Name"].Value);
I have set of XML's ( varies between 2 and 6) that needs to be processed(traversed and checked for certain data and relations within) - The XML's have some "Recursive Data"
here is a simple example involving a test data for explanation - 2 files considered as example
File1.xml:
<some root------standard header not entered for the example----->
<parent>
<ID>AB-1234</ID>
<Description>Good book</Description>
<Date_Created>08-10-2011</Date_Created>
<child>
<ID>BC-0001</ID>
<Description>Nice</Description>
</child>
</parent>
<parent>
<ID>BC-0001</ID>
<Description>Work Together</Description>
<Date_Created>08-10-2011</Date_Created>
<child>
<ID>DC-0011</ID>
<Description>Happy</Description>
</child>
</parent>
File2.xml:
<some root------standard header not entered for the example----->
<parent>
<ID>DC-0011</ID>
<Description> book</Description>
<Date_Created>08-10-2011</Date_Created>
<child>
<ID>EF-0001</ID>
<Description>Nice</Description>
</child>
</parent>
<parent>
<ID>EF-0001</ID>
<Description>Work Together</Description>
<Date_Created>08-10-2011</Date_Created>
<child>
<ID>PQ-0011</ID>
<Description>Happy</Description>
</child>
</parent>
code I am using involves 1) loading both the XML files and combining them
XDocument test1doc = XDocument.Load(#"d:\File1.xml");
XDocument test2doc = XDocument.Load(#"d:\File2.xml");
IEnumerable<XElement> testElist1 = test1doc.decendants("parent");
IEnumerable<XElement> testElist2 = test2doc.decendants("parent");
IEnumerable<XElement> testElistcombo = testElist1.union(testElist2);
2) use the testElistcombo to navigate the elements using foreach - 2 foreach loops (one for the parent and second for the child)
3) while traversing use an if condition to check whether parent ID and Child ID are equal.
I am able to build the hierarchy - no problem with that.
I was able to print the hierarchy along with the level value of the hierarchy.by including a counter in each of the foreach loops.
my output looks like
AB-1234[level-0]
>>BC-0001[level-1]
>>DC-0011[level-3]
..... and so on.
as i said no problem with that. -
Following is the area where i would like some help:
1) when the number of files increases to more than 2 to a max 6, i am using a union in the following manner
XDocument test1doc = XDocument.Load(#"d:\File1.xml");
XDocument test2doc = XDocument.Load(#"d:\File2.xml");
XDocument test3doc = XDocument.Load(#"d:\File3.xml");
XDocument test4doc = XDocument.Load(#"d:\File4.xml");
XDocument test5doc = XDocument.Load(#"d:\File5.xml");
XDocument test6doc = XDocument.Load(#"d:\File6.xml");
IEnumerable<XElement> testElist1 = test1doc.decendants("parent");
IEnumerable<XElement> testElist2 = test2doc.decendants("parent");
IEnumerable<XElement> testElist3 = test3doc.decendants("parent");
IEnumerable<XElement> testElist4 = test4doc.decendants("parent");
IEnumerable<XElement> testElist5 = test5doc.decendants("parent");
IEnumerable<XElement> testElist6 = test6doc.decendants("parent");
IEnumerable<XElement> testElistcombo1 = testElist1.union(testElist2);
IEnumerable<XElement> testElistcombo2 = testElistcombo1.union(testElist3);
IEnumerable<XElement> testElistcombo3 = testElistcombo2.union(testElist4);
IEnumerable<XElement> testElistcombo4 = testElistcombo3.union(testElist5);
IEnumerable<XElement> testElistcombo5 = testElistcombo4.union(testElist6);
and use the testElistcombo5.for processing.
help required: an alternative way to load and combine the XML's to for processing.
2) The process is resource intensive and take a fair bit of time to complete the hierarchy building
help required: is there an alternative way to process the xml's for building hierarchy in Recursive Data.
Question 1: you can do this using the Enumerable.Aggregate function to aggregate the elements for each document into one set of elements:
IEnumerable<string> filenames = { "filename1.xml", "filename2.xml" };
IEnumerable<XDocument> documents = filenames.Select(XDocument.Load);
IEnumerable<IEnumerable<XElement>> documentsElements = documents.Select(document => document.Descendants("parent"));
IEnumerable<XElement> elements = documentsElements.Aggregate((working, next) => working.Union(next));
I'm trying to select the second child node off the root and all it's children from XML that looks similar to this:
<root>
<SET>
<element>
<element>
</SET>
<SET>
<element>
<element>
</SET>
<root>
I'm after all the tags in the second node, any help would be greatly appreciated!
I'm using C#. I tried the XPath /SET[1] however that didn't see to help!
Many thanks!
C
x/y[1] :
The first <y> child of each <x>. This is equivalent to the expression in the next row.
x/y[position() = 1] :The first <y> child of each <x>.
Try this :
string xpath = "/root/set[2]";
XmlNode locationNode = doc.SelectSingleNode(xpath);
or
string xpath = "/root/set[position() = 2]";
XmlNode locationNode = doc.SelectSingleNode(xpath);
XPath is not zero-index based, it's one-indexed.
You want: root/set[2]
Below is my solution:
XmlDocument doc = new XmlDocument();
doc.Load(#"C:\testing.xml");
XmlNodeList sets = doc.GetElementsByTagName("SET");
//Show the value of first set's first element
Console.WriteLine(sets[0].ChildNodes[0].InnerText);
//Show the value of second set's second element
Console.WriteLine(sets[1].ChildNodes[1].InnerText);