Xpath sibling filter based on value of element in current node

Viewed 30

Is there an Xpath to find a cousin node that has an element that matches the value of an element in the current node?

Please see below - I am iterating over each "Order" node and want to return the value of LocationID from the Collection node that has the same OrderLoadRef value as the order. For the first order it should return "AAA", for the second it should return "BBB".

The XPath works if I change the value of the OrderLoadRef manually, but how to I set it to be the value of the OrderLoadRef in the current Order Element? I've tried using the self axis, but think by the time we get to the condition, "self" is the collection node, not the order?

I can't hard code relative collection / order node positions as there could be a variable number of these nodes in the XML that my parser receives.

XDocument xDoc = XDocument.Parse(@"<DocRoot>
                <Load>
                    <Collections>
                        <Collection>
                            <OrderLoadRef>1</OrderLoadRef>
                            <LocationID>AAA</LocationID>
                        </Collection>
                        <Collection>
                            <OrderLoadRef>2</OrderLoadRef>
                            <LocationID>BBB</LocationID>
                        </Collection>
                    </Collections>
                     <Orders>
                        <Order>
                            <OrderRef>1521505</OrderRef>
                            <OrderLoadRef>1</OrderLoadRef>
                        </Order>
                        <Order>
                            <OrderRef>1521505_2</OrderRef>
                            <OrderLoadRef>2</OrderLoadRef>
                        </Order>
                    </Orders>
                </Load>
            </DocRoot>");

        List<XElement> orders = xDoc.XPathSelectElements("//Order").ToList();

        foreach(XElement order in orders)
        {
            string locationId = order.XPathSelectElement("parent::Orders/parent::Load/Collections/Collection[OrderLoadRef = {OrderLoadRef from current order element}]/LocationID").Value;
        }

Edited to add: I need this to be a purely XPath solution as I'm not able to alter the C# code in the parser. More than happy to be told it's not possible, but wanted to make sure before I relayed the message!

2 Answers

As Mads said, XPath 3 and later (i.e. the current version 3.1) allows you to use a let expression so e.g.

for $order in /DocRoot/Load/Orders/Order
return 
let $col := /DocRoot/Load/Collections/Collection[OrderLoadRef = $order/OrderLoadRef]/LocationID
return $col

is pure XPath 3 and returns (for your sample) the two LocationID elements:

<LocationID>AAA</LocationID>
<LocationID>BBB</LocationID>

In the .NET framework XmlPrime and Saxon.NET support XPath 3.1 and XQuery 3.1 although only XmlPrime has extension methods for C# to work against XDocument, I think, Saxon.NET does allow XPath 3.1 against its XDM tree model or against System.Xml.XmlDocument.

XPath 3.0 (and greater) supports let expressions, which would allow you to do what you want. You could let a variable with the OrderLoadRef from the context node and use it within a predicate selecting the desired Collection by it's OrderLoadRef.

For a static XPath 1.0 expression, I don't think you can achieve what you want. You would need to construct the XPath using the context node information.

Inside your for loop, create a variable for the Order's OrderLoadRef value. Use that value to construct the XPath that you want to evaluate to then select the locationId

foreach(XElement order in orders)
{
  string orderLoadRef = order.XPathSelectElement("OrderLoadRef").Value;
  string locationId = order.XPathSelectElement("ancestor::Load/Collections/Collection[OrderLoadRef = " + orderLoadRef + "]/LocationID").Value;
  //do something with the locationId
}
Related