XPath get element by name with highest number value - xpath

I have this xml source:
<element1>
<element2>
<element3>156</element3>
<element4>Test</element4>
<element5>descriptionxxxx</element5>
</element2>
<element2>
<element3>25</element3>
<element4>Top</element4>
<element5>descriptionyyyy</element5>
</element2>
<element2>
<element3>852</element3>
<element4>Test</element4>
<element5>descriptionzzzz</element5>
</element2>
</element1>
I would need to find all element2 where element4 = "Test", and return element5 for the max of element3.(in this case, It would return "descriptionzzzz")
I have tried:
To get all element2 with element4 = "Test" -> doc("xxx.xml")//element1/element2[element4="Test"]
But now I would need to get the element2 with the highest element3 of the previous list, and return element5 of that element2.
Thanks!

I have done something like this for my xml and it has worked for me: //element2[element4="Test"][element3=max(//element2[element4="Test"]/element3)]/element5

If you're able to use Saxon (PE or higher) there's an XPath extension function that does exactly what you need:
saxon:highest(//element2, function($e){number($e/element3)})
saxon:highest(S, F) finds the item(s) from S that have the highest value for the result of computing function F.

Related

Xpath sibling filter based on value of element in current node

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

xPath to get parent node position using count

My xml looks like the following. The focus is at the third level: <name>,<address> etc. I'd like to find the position of parent <customer>.
I use "count(../preceding-sibling::*) + 1" but it doesn't return a count when the node is empty. How do I fix this? Thanks.
<record>
<customer>
<name>Sue A</name> -- return 1
<address>123 Main St</address> -- return 1
<phone></phone> -- missing
<status>A</status> -- return 1
</customer>
<customer>
<name>John B</name> -- return 2
<address></address> -- missing
<phone>123-456-7890</phone> -- return 2
<status></status> -- missing
</customer>
…
</record>
While the question is lacking a lot of information, using the following xquery It looks like your logic is correct, however you are probably using the text nodes as current nodes instead of the element node.
This query returns the same wrong results, because some text nodes are missing:
let $x :=
for $grandchild in //customer/*/text()
return count($grandchild/../../preceding-sibling::*) + 1
return $x
This query returns correct results, because every element exists:
let $x :=
for $child in //customer/*
return count($child/../preceding-sibling::*) + 1
return $x
Selecting the element node as current node will probably fix any issue.

XPath: Default to "Master" node, select current node if specified (in third node)

My problem:
I need to select the value in the "Master_Node" only if the "Sub_Node_Checker" is set to "false".
If "Sub_Node_Checker" is set to "true" then the value must be set to "Sub_Node".
Current node is "Sub_Node"
I am using InfoPath 2010.
Here is my sample XML:
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
Refer to the following forum thread to download my XSN template.
Here is the XPath that I have been attempting to use, to no avail (line breaks added for legibility):
//my:Master_Node[../my:Sub_Node_Checker = "false"]
|
../my:Sub_Node[../my:Sub_Node_Checker = "true"]
This does not seem to return anything whatsoever, and I'm not sure why.
The following question accompanies "Sub_Node_Checker" in my XML form: "Does the sub node differ from the master node?"
If the user selects "Yes" (true) then the Sub_Node field should be set to its own value.
If the user selects "No" (false) then the Sub_Node field should default to the Master_Node.
Edit & Additional XML
My Repeater section repeats (as per the name) and seems to cause additional chaos with the XPath selectors.
<my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xml:lang="en-us">
<my:Master_Node>123123</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
</my:Repeater>
</my:myFields>
A much cleaner solution would be to move the predicate to the <my:myFields/> element.
/my:myFields[my:Repeater/my:Sub_Node_Checker = "false"]/my:Master_Node
If you insist on your approach, you're missing a / to jump over the <my:Repeater/> element or reference that:
//my:Master_Node[..//my:Sub_Node_Checker = "false"]
//my:Master_Node[../my:Repeater/my:Sub_Node_Checker = "false"]
Relating to your xpath question you may try something like this.
(self::*[../my:Sub_Node_Checker = "true"]
|
//my:Master_Node)[last()]
Which should work if Master_Node is always before Sub_Node (in document order).
Ok, I got this one worked out a bit differently than my original approach. The following article on MSDN has an example of using the substring() function to return different values based on outside conditions. Also have to thank Hilary Stoupa at InfoPath Dev for helping me come to the solution.
I will also mention that I could not use the "current" node and had to create a third node which housed my XPath expression and evaluated the conditions to return the appropriate value.
Here is the source XML roughly as InfoPath might interpret it (note the "location" of the xpath expression in the 3rd repeater group - this is how InfoPath evaluates default values):
<my:myFields xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2013-07-02T14:58:05" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003" xml:lang="en-us">
<my:Master_Node>123456</my:Master_Node>
<my:Repeater>
<my:Sub_Node_Checker>true</my:Sub_Node_Checker>
<my:Sub_Node>9870</my:Sub_Node>
<my:Sub_Node_Stored>9870</my:Sub_Node_Stored>
</my:Repeater>
<my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>123456</my:Sub_Node_Stored>
</my:Repeater><
my:Repeater>
<my:Sub_Node_Checker>false</my:Sub_Node_Checker>
<my:Sub_Node></my:Sub_Node>
<my:Sub_Node_Stored>concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))</my:Sub_Node_Stored>
</my:Repeater>
</my:myFields>
Note that the following XPath expression was instrumental in causing the appropriate node to be selected:
concat(substring(../../my:Master_Node, 1, (../my:Sub_Node_Checker != "true") * string-length(../../my:Master_Node)), substring(../my:Sub_Node, 1, (../my:Sub_Node_Checker != "false") * string-length(../my:Sub_Node)))
The substring function returns the number of characters from the target string as specified by the user. When a boolean value is evaluated inside the substring function (at the location given for number of characters to return) it returns either a "1" or a "0".
When multiplied by the length of the target string this boolean check causes any conditions that would exempt a particular target node from selection to return a length of "0" characters from that node. (0*X=0) This effectively allows for different default values within the node without the use of the | operator.

Does xpath query has Limit option like mysql

I want to limit number of result I receive from xpath query.
For example:-
$info = $xml->xpath("//*[firstname='Sheila'] **LIMIT 0,100**");
You see that LIMIT 0,100.
You should be able to use "//*[firstname='Sheila' and position() <= 100]"
Edit:
Given the following XML:
<root>
<country.php desc="country.php" language="fr|pt|en|in" editable="Yes">
<en/>
<in>
<cityList desc="cityList" language="in" editable="Yes" type="Array" index="No">
<element0>Abu</element0>
<element1>Agartala</element1>
<element2>Agra</element2>
<element3>Ahmedabad</element3>
<element4> Ahmednagar</element4>
<element5>Aizwal</element5>
<element150>abcd</element150>
</cityList>
</in>
</country.php>
</root>
You can use the following XPath to get the first three cities:
//cityList/*[position()<=3]
Results:
Node element0 Abu
Node element1 Agartala
Node element2 Agra
If you want to limit this to nodes that start with element:
//cityList/*[substring(name(), 1, 7) = 'element' and position()<=3]
Note that this latter example works because you're selecting all the child nodes of cityList, so in this case Position() works to limit the results as expected. If there was a mix of other node names under the cityList node, you'd get undesirable results.
For example, changing the XML as follows:
<root>
<country.php desc="country.php" language="fr|pt|en|in" editable="Yes">
<en/>
<in>
<cityList desc="cityList" language="in" editable="Yes" type="Array" index="No">
<element0>Abu</element0>
<dog>Agartala</dog>
<cat>Agra</cat>
<element3>Ahmedabad</element3>
<element4> Ahmednagar</element4>
<element5>Aizwal</element5>
<element150>abcd</element150>
</cityList>
</in>
</country.php>
</root>
and using the above XPath expression, we now get
Node element0 Abu
Note that we're losing the second and third results, because the position() function is evaluating at a higher order of precedence - the same as requesting "give me the first three nodes, now out of those give me all the nodes that start with 'element'".
Ran into the same issue myself and had some issue with Geoffs answer as it, as he clearly describes, limits the number of elements returned before it performs the other parts of the query due to precedence.
My solution is to add the position() < 10 as an additional conditional after my other conditions have been applied e.g.:
//ElementsIWant[./ChildElementToFilterOn='ValueToSearchFor'][position() <= 10]/.
Notice that I'm using two separate conditional blocks.
This will first filter out elements that live up to my condition and secondly only take 10 of those.

Xpath: find an element value from a match of id attribute to id anchor

I would like to find the value of an element matched on id attribute for which I only have the ref - the bit with #, the anchor.
I am looking for the value of partyId:
< party id="partyA" >
< partyId >THEID< /partyId >
but to get there I only have the href from the following
< MyData >
< MyReference href="#partyA" />
Strip the # sign does not look good to me.
Any hints?
Because you haven't provided complete XML documents, I have to use // -- a practice I strongly recommend to avoid.
Suppose that
$vDataRef
is defined as
//MyData/MyReference/#href
and its string value is "#partyA", then one possible XPath expression that selects the wanted node is:
//party[#id=substring($vDataRef,2)]
In case the XML document has a DTD in which the id attribute of party is defined to be of type ID, then it is more convenient and efficient to use the standard XPath function id():
id(substring($vDataRef,2))
Assuming you have your ID as a variable already (lets say $myId), then try using:
//party[contains($myId, #id)]
The contains() function will look to see on each matching node whether or not the partyId attibute is in the value that you pass in.
Alternatively (as that could be considered 'ropey'), you can try:
//party[#id=substring($myId, 2, 1 div 0)]
the substring() function should be a little more precise.

Resources