xpath how access nodes by position - xpath

I have such xml file
<ce:MarkInfo>
<ce:boxpos>
<ce:boxnumber>box-00112</ce:boxnumber>
<ce:amclist>
<ce:amc>12</ce:amc>
<ce:amc>22</ce:amc>
</ce:amclist>
</ce:boxpos>
<ce:boxpos>
<ce:boxnumber>box-00113</ce:boxnumber>
<ce:amclist>
<ce:amc>32</ce:amc>
<ce:amc>42</ce:amc>
<ce:amc>52</ce:amc>
<ce:amc>62</ce:amc>
</ce:amclist>
</ce:boxpos>
</ce:MarkInfo>
and xpath expression
xDoc.selectNodes("/ns:Documents/ns:Document/ns:WayBill_v3/wb:Content/wb:Position[1]/wb:InformF2/ce:MarkInfo//ce:amc").length = 6
/ns:Documents/ns:Document/ns:WayBill_v3/wb:Content/wb:Position[1]/wb:InformF2/ce:MarkInfo//ce:amc[1]
returns AMC value begins with 12
/ns:Documents/ns:Document/ns:WayBill_v3/wb:Content/wb:Position[1]/wb:InformF2/ce:MarkInfo//ce:amc[3]
returns AMC value begins with 52
/ns:Documents/ns:Document/ns:WayBill_v3/wb:Content/wb:Position[1]/wb:InformF2/ce:MarkInfo//ce:amc[5]
returns null
how can i access nodes by they absolute position, not by position in ce:amclist?

"how can i access nodes by they absolute position, not by position in ce:amclist?"
Wrap the entire XPath in parentheses, and add the position predicate outside :
(/ns:Documents/.....//ce:amc)[5]

Related

How to grab Index instead of relative position using xpath

Given the following xml:
<randomName>
<otherName>
<a>item1</a>
<a>item2</a>
<a>item3</a>
</otherName>
<lastName>
<a>item4</a>
<a>item5</a>
</lastName>
</randomName>
Running: '//a' Gives me an array of all 5 "a" elements, however '//a[1]' does not give me the first of those five elements (item1). It instead gives me an array containing (item1 and item 4).
I believe this is because they are both position 1 relatively. How can I grab any a element by its overall index?
I would like to be able to use a variable "x" to get itemX.
You can wrap it in parenthesis so it knows to apply the index to the entire result set
(//a)[1]

How can I locate items using xpath from below elements?

I've created some xpath expressions to locate the first item by it's "index" after "h4". However, I did something wrong that is why it doesn't work at all. I expect someone to take a look into it and give me a workaround.
I tried with:
//div[#id="schoolDetail"][1]/text() --For the Name
//div[#id="schoolDetail"]//br[0]/text() --For the PO Box
Elements within which items I would like the expression to locate is pasted below:
<div id="schoolDetail" style=""><h4>School Detail: Click here to go back to list</h4> GOLD DUST FLYING SERVICE, INC.<br>PO Box 75<br><br>TALLADEGA AL 36260<br> <br>Airport: TALLADEGA MUNICIPAL (ASN)<br>Manager: JEAN WAGNON<br>Phone: 2563620895<br>Email: golddustflyingse#bellsouth.net<br>Web: <br><br>View in AOPA Airports (Opens in new tab) <br><br></div>
By the way, the resulting values should be:
GOLD DUST FLYING SERVICE, INC.
PO Box 75
Try to locate required text nodes by appropriate index:
//div[#id="schoolDetail"]/text()[1] // For "GOLD DUST FLYING SERVICE, INC."
//div[#id="schoolDetail"]/text()[2] // For "PO Box 75"
Locator to get both elements:
//*[#id='schoolDetail']/text()[position()<3]
Explanation:
[x] - xPath could sort values using predicate in square brackets.
x - could be integer, in this case it will automatically be compared with element's position in this way [position()=x]:
//div[2] - searches for 2nd div, similar to div[position()=2]
In case predicate [x] is not an integer - it will be automatically converted to boolean value and will return only elements, where result of x is true, for example:
div[position() <= 4] - search for first four div elements, as 4 <= 4, but on the 5th and above element position will be more than 4
Important: please check following locators on this page:
https://www.w3schools.com/tags/ref_httpmessages.asp
//table//tr[1] - will return every 1st row in each table ! (12 found
elements, same as tables on the page)
(//table//tr)[1] - will return 1st row in the first found table (1 found element)

XPath :: running counter two levels

Using the count(preceding-sibling::*) XPath expression one can obtaining incrementing counters. However, can the same also be accomplished in a two-levels deep sequence?
example XML instance
<grandfather>
<father>
<child>a</child>
</father>
<father>
<child>b</child>
<child>c</child>
</father>
</grandfather>
code (with Saxon HE 9.4 jar on the CLASSPATH for XPath 2.0 features)
Trying to get an counter sequence of 1,2 and 3 for the three child nodes with different kinds of XPath expressions:
XPathExpression expr = xpath.compile("/grandfather/father/child");
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0 ; i < nodes.getLength() ; i++) {
Node node = nodes.item(i);
System.out.printf("child's index is: %s %s %s, name is: %s\n"
,xpath.compile("count(preceding-sibling::*)").evaluate(node)
,xpath.compile("count(preceding-sibling::child)").evaluate(node)
,xpath.compile("//child/position()").evaluate(doc)
,xpath.compile(".").evaluate(node));
}
The above code prints:
child's index is: 0 0 1, name is: a
child's index is: 0 0 1, name is: b
child's index is: 1 1 1, name is: c
None of the three XPaths I tried managed to produce the correct sequence: 1,2,3. Clearly it can trivially be done using the i loop variable but I want to accomplish it with XPath if possible. Also I need to keep the basic framework of evaluating an XPath expression to get all the nodes to visit and then iterating on that set since that's the way the real application I work on is structured. Basically I visit each node and then need to evaluate a number of XPath expressions on it (node) or on the document (doc); one of these XPAth expressions is supposed to produce this incrementing sequence.
Use the preceding axis with a name test instead.
count(preceding::child)
Using XPath 2.0, there is a much better way to do this. Fetch all <child/> nodes and use the position() function to get the index:
//child/concat("child's index is: ", position(), ", name is: ", text())
You don't say efficiency is important, but I really hate to see this done with O(n^2) code! Jens' solution shows how to do that if you can use the result in the form of a sequence of (position, name) pairs. You could also return an alternating sequence of strings and numbers using //child/(string(.), position()): though you would then want to use the s9api API rather than JAXP, because JAXP can only really handle the data types that arise in XPath 1.0.
If you need to compute the index of each node as part of other processing, it might still be worth computing the index for every node in a single initial pass, and then looking it up in a table. But if you're doing that, the simplest way is surely to iterate over the result of //child and build a map from nodes to the sequence number in the iteration.

How to get the last element of a sequence in XPath?

In Ruby we can access an array with negative numbers like array[-1] to get the last object in the array. How do I do this using XPath?
I can't do this:
result = node.xpath('.//ROOT/TAG[-1]/KEY_NAME')
I found a solution here on Stack Overflow, but that is a query that just changes the upper limit to get elements. This could return one last item or last item and prevous.
What if I want to get only the prevous element like array[-2] in Ruby?
You can access the last element in XPath using last() in a predicate.
node.xpath('.//ROOT/TAG[last()]/KEY_NAME')
And use [last()-1] for the second-to-last position.

xpath: decipher this xpath?

what does this xpath mean ? can someone decipher this ?
//h1[following-sibling::*[1][self::b]]
Select every h1 element (in the document of the context node) that is immediately followed by a b element (with no other intervening element, though there may be intervening text).
Breaking it down:
//h1
Select every h1 element that is a descendant of the root node of the document that contains the context node;
[...]
filter out any of these h1 elements that don't meet the following criteria:
[following-sibling::*[1]...]
such that the first following sibling element passes this test:
[self::b]
self is a b element. Literally, this last test means, "such that when I start from the context node and select the self (i.e. the context node) subject to the node test that filters out everything except elements named b, the result is a non-empty node set."

Resources