Get the full path to the current node

If I have an XPathNavigator located on a node, how can I get an XPath expression that represents the path to that node from the root?

For example, if XML:

 <data> <class name='dogs'> <item name='doberman /> <item name='husky' /> </class> <class name='cats'> <item name='persian' /> <item name='tabby' /> </class> </data> </data> 

... then the path to the Persian cat can be expressed as /data/class[2]/item[1]

I can list the ancestors of node in the SelectAncestors() question (or I could iteratively go up in parental relation with SelectParent() ), but this does not give me positional information.

Should I evaluate XPath using position() for each ancestor, or is there a better way to do this?

+4
source share
3 answers

Assuming you are only interested in xpath of xml elements, I applied brute force algorithm (i.e. moving XML structure) as extension methods on XmlElement . This is very similar to @Zenexer's answer, although I already started my own version when he posted it.

Also, intrigued by Alexei about performance advice, I created a kind of test case using a somewhat complex XML file here. Then I implemented two versions of the same algorithm; one that depends on PreviousSibling, and another that iterates through the nodes sequentially. The third version was based on the XPath position() function, but it did not work as expected and was dropped.

While you have to check it yourself, on my machine the results showed a significant performance advantage for the iterative version - 1.7s versus the 21st, clogged by the version of siblings.

Importart: these extension methods are declared inside the static class XmlElementExtension .

Previous Version>

  public static string GetXPath_UsingPreviousSiblings(this XmlElement element) { string path = "/" + element.Name; XmlElement parentElement = element.ParentNode as XmlElement; if (parentElement != null) { // Gets the position within the parent element, based on previous siblings of the same name. // However, this position is irrelevant if the element is unique under its parent: XPathNavigator navigator = parentElement.CreateNavigator(); int count = Convert.ToInt32(navigator.Evaluate("count(" + element.Name + ")")); if (count > 1) // There more than 1 element with the same name { int position = 1; XmlElement previousSibling = element.PreviousSibling as XmlElement; while (previousSibling != null) { if (previousSibling.Name == element.Name) position++; previousSibling = previousSibling.PreviousSibling as XmlElement; } path = path + "[" + position + "]"; } // Climbing up to the parent elements: path = parentElement.GetXPath_UsingPreviousSiblings() + path; } return path; } 

Iterative version

  public static string GetXPath_SequentialIteration(this XmlElement element) { string path = "/" + element.Name; XmlElement parentElement = element.ParentNode as XmlElement; if (parentElement != null) { // Gets the position within the parent element. // However, this position is irrelevant if the element is unique under its parent: XmlNodeList siblings = parentElement.SelectNodes(element.Name); if (siblings != null && siblings.Count > 1) // There more than 1 element with the same name { int position = 1; foreach (XmlElement sibling in siblings) { if (sibling == element) break; position++; } path = path + "[" + position + "]"; } // Climbing up to the parent elements: path = parentElement.GetXPath_SequentialIteration() + path; } return path; } 

Test case

  private static void Measure(string functionName, int iterations, Action implementation) { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < iterations; i++) { implementation(); } watch.Stop(); Console.WriteLine("{0}: {1}ms", functionName, watch.ElapsedMilliseconds); } private static void Main(string[] args) { XmlDocument doc = new XmlDocument(); doc.Load(@"location of some large and complex XML file"); string referenceXPath = "/vps/vendorProductSets/vendorProductSet/product[100]/prodName/locName"; Measure("UsingPreviousSiblings", 10000, () => { XmlElement target = doc.SelectSingleNode(referenceXPath) as XmlElement; Debug.Assert(referenceXPath == target.GetXPath_UsingPreviousSiblings()); }); Measure("SequentialIteration", 10000, () => { XmlElement target = doc.SelectSingleNode(referenceXPath) as XmlElement; Debug.Assert(referenceXPath == target.GetXPath_SequentialIteration()); }); } 
+6
source

untested; only works with XPathNavigator objects created from XmlDocument objects:

 private static string GetPath(this XPathNavigator navigator) { StringBuilder path = new StringBuilder(); for (XmlNode node = navigator.UnderlyingObject as XmlNode; node != null; node = node.ParentNode) { string append = "/" + path; if (node.ParentNode != null && node.ParentNode.ChildNodes.Count > 1) { append += "["; int index = 1; while (node.PreviousSibling != null) { index++; } append += "]"; } path.Insert(0, append); } return path.ToString(); } 

Here's how you use it:

 XPathNavigator navigator = /* ... */; string path = navigator.GetPath(); 

But...

XPathNavigator objects are usually placed at the root of the node. Once they are created, their positions cannot be changed, although you can use them to select descendants. Perhaps there is a way to avoid this problem? For example, if you just want to use the current node, you can use XPathNavigator.UnderlyingObject , as in the example.

+2
source

Simple solution with ParentNode. Just go up until you reach the root of the node and remember every node name you go through. Tested!

  // Get the node full path static string getPath(XmlNode node) { string path = node.Name; XmlNode search = null; // Get up until ROOT while ((search = node.ParentNode).NodeType != XmlNodeType.Document) { path = search.Name + "/" + path; // Add to path node = search; } return "//"+path; } 
+2
source

Source: https://habr.com/ru/post/1395701/


All Articles