1

How to reference the node on which the XPath is evaluted from within an XPath?

E.g.:

<Root> <Foo> <Bar><Elem>X</Elem><Ref>1</Ref></Bar> <Bar><Elem>Y</Elem><Ref>2</Ref></Bar> <Ref>2</Ref> </Foo> 

XPath executed on Node=/Root/Foo/Ref:

var myNode = GetNodeRootFooRef(); var xpath = "../Bar[Ref=.]/Elem"; myNode.SelectSingleNode(xpath); // does not work, see below 

Unfortunately the . in the conditional references the Barelement instead of the original Ref node on which I executed the XPath. How to reference the original context node from within an XPath?

1
  • what output do you need? Commented Feb 3, 2017 at 20:43

4 Answers 4

4

You need at least XPath 2.0 to solve it with a single, pure XPath expression: for $current in . return ../Bar[Ref=$current]/Elem.

Microsoft does not have XPath 2.0 support but there are third party XPath 2 implementations that plug into the existing .NET architecture and provide extension methods on e.g. XmlNode (needs using Wmhelp.XPath2; and the NuGet package https://www.nuget.org/packages/XPath2/1.0.2/):

 string xml = @"<Root> <Foo> <Bar><Elem>X</Elem><Ref>1</Ref></Bar> <Bar><Elem>Y</Elem><Ref>2</Ref></Bar> <Ref>2</Ref> </Foo> </Root>"; XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); XmlNode refEl = doc.SelectSingleNode("/Root/Foo/Ref"); XmlNode elem = refEl.XPath2SelectSingleNode("for $current in . return ../Bar[Ref=$current]/Elem"); Console.WriteLine(elem.OuterXml); 

Or you can do it with XPath 3.0 or later with a single, pure XPath expression using let to bind a variable: let $c := . return ../Bar[Ref=$c]/Elem.

If you want to use that on System.Xml in the .NET framework then you can for instance install and use XmlPrime, it offers extension methods (http://www.xmlprime.com/xmlprime/doc/4.0/using-xpath.htm#extension):

 string xml = @"<Root> <Foo> <Bar><Elem>X</Elem><Ref>1</Ref></Bar> <Bar><Elem>Y</Elem><Ref>2</Ref></Bar> <Ref>2</Ref> </Foo> </Root>"; XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); XmlNode refEl = doc.SelectSingleNode("/Root/Foo/Ref"); XmlNode elem = refEl.XPathSelectSingleNode("let $c := . return ../Bar[Ref=$c]/Elem"); Console.WriteLine(elem.OuterXml); 

outputs <Elem>Y</Elem>.

If you want to have variable resolution within the .NET framework XPath APIs then the second argument to SelectSingleNode/SelectNodes is an XmlNamespaceManager which is a base class of XsltContext which has a method ResolveVariable https://msdn.microsoft.com/en-us/library/system.xml.xsl.xsltcontext.resolvevariable(v=vs.110).aspx. There is project on Codeplex that implements that XsltContext for variable resolution in the public class DynamicContext : XsltContext so you could use that:

 XmlDocument doc = new XmlDocument(); doc.LoadXml(@"<Root> <Foo> <Bar><Elem>X</Elem><Ref>1</Ref></Bar> <Bar><Elem>Y</Elem><Ref>2</Ref></Bar> <Ref>2</Ref> </Foo> </Root>"); XmlNode refEl = doc.SelectSingleNode("Root/Foo/Ref"); DynamicContext context = new DynamicContext(); context.AddVariable("current", refEl); XmlNode elem = refEl.SelectSingleNode("../Bar[Ref = $current]/Elem", context); Console.WriteLine(elem.OuterXml); 

See the documentation in https://weblogs.asp.net/cazzu/30888.

Sign up to request clarification or add additional context in comments.

5 Comments

I'm using the .NET 4.6.2 framework's System.Xml component - are there any alternatives compatible with the suported XPath standard of this library?
Thanks for the pointer, upvoted your answer! Cannot accept it though, as it isn't helping my (open source) project ... can't afford such a library. Thank you anyways for your support!
As for doing it with the SelectSingleNode, it has an overload msdn.microsoft.com/en-us/library/h0hw012b(v=vs.110).aspx taking an XmlNamespaceManager and that is a base class of msdn.microsoft.com/en-us/library/… you could extend and implement to provide your on function and/or variable resolution to make something like current() or $current work. That is some considerable effort however.
Thanks for the pointer, I have to talk to my peers if we want to spend so much more energy on the problem ... would report our solution back here, if we are going to implement your idea! Thanks!
@D.R., my initial answer was not quite right as XPath 2.0 instead of 3.0 suffices. And for XPath 2.0 there is a NuGet package that might better suit your needs.
2

If you're using XPath 1.0, then your best bet is probably to bind a variable, something like the following, depending on your API:

var myNode = GetNodeRootFooRef(); var xpath = "../Bar[Ref=$current]/Elem"; XPath x = new XPath(): x.bindVariable("current", myNode); x.evaluate(xpath, myNode); 

But you've hit the limits of XPath 1.0 so it's time to move forward. There's a variety of perfectly good XPath 2.0 and 3.0 engines available on .NET.

3 Comments

new XPath() does not work with System.Xml in .NET -> do you know if they have some kind of API which supports variable binding? I cannot find one...
No, I can't either. Pretty fundamental omission - perhaps people weren't so worried about injection attacks in the dark days when these APIs were defined.
@D.R., as said in the comments to my answer, the API is (hidden) in the argument of an XmlNamespaceManager being a base class of XsltContext which allows variable resolution/binding. If you need to use that look into weblogs.asp.net/cazzu/30888 and the sources of the mvpxml.codeplex.com/releases/view/4894 project, it has an implementation of a public class DynamicContext : XsltContext.
1

If the structure of the Foo element is always the same then the following query should suffice:

../Bar[Ref=(../Ref)]/Elem 

(tried on http://xpather.com/vjXDytGy - this xpath online tester supports XPath 2.0)

I would hope that it works in XPath 1.0 too.

Comments

0
../Bar[Ref=/Root/Foo/Ref]/Elem 

change . to the original xpath

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.