Say my markup is :
<html>
<body>
<div>
<div>blbaba</div>
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
<div id="a1">blbaba</div>
<p>e</p>
<p>f</p>
<p>g</p>
<p>h</p>
<div>blbaba</div>
</div>
</body>
</html>
How do I select an element relative to a particular element , that is exactly n
nodes before (in the document can be parent or sibling that is before ) .
Example:
I want to select the 2nd <p>
element before the div with @id ="a1"
, so my expected element is <p>c</p>
Say my markup is :
<html>
<body>
<div>
<div>blbaba</div>
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
<div id="a1">blbaba</div>
<p>e</p>
<p>f</p>
<p>g</p>
<p>h</p>
<div>blbaba</div>
</div>
</body>
</html>
How do I select an element relative to a particular element , that is exactly n
nodes before (in the document can be parent or sibling that is before ) .
Example:
I want to select the 2nd <p>
element before the div with @id ="a1"
, so my expected element is <p>c</p>
2 Answers
Reset to default 4The preceding::
and ancestor::
axes are non-intersecting, therefore in the general case you will not be able to select the wanted element, unless you use the elements selected by both these axes.
To quote the W3C XPath 1.0 Specification:
•the preceding axis contains all nodes in the same document as the context node that are before the context node in document order, excluding any ancestors and excluding attribute nodes and namespace nodes
Use:
(//p[@id='a1']/ancestor::div | //p[@id='a1']/preceding::div)
[last() -$n +1]
This selects the $n-th div
element (backwards in document order) belonging to the union of the preceding and ancestor div
elements of the p
element whose id
attribute's value is the string "a1
". Here we assume that the id
attribute identifies uniquely the element to which it belongs.
For example, if we have this XML document (I made the example more realistic, because div
s can be nested, while p
s cannot):
<html>
<body>
<div>
<p>a</p>
<p>b</p>
<p>c</p>
<div id="y">
<p>d</p>
<div>blbaba</div>
<p id="a1">e</p>
<p>f</p>
<p>g</p>
<p>h</p>
<div>blbaba</div>
</div>
</div>
</body>
</html>
And we want to get the 2nd div
before (in document order) the p
with id
"a1
", then this XPath expression selects the wanted div
element:
(//p[@id='a1']/ancestor::div | //p[@id='a1']/preceding::div)
[last() -1]
And this short XSLT transformation proves that the XPath expression above selects exactly the wanted div
element (by evaluating the XPath expression and copying the whole selected div
element):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select=
"(//p[@id='a1']/ancestor::div | //p[@id='a1']/preceding::div)
[last() -1]"/>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on the above source XML document, it selects the wanted div
element and copies it to the output:
<div id="y">
<p>d</p>
<div>blbaba</div>
<p id="a1">e</p>
<p>f</p>
<p>g</p>
<p>h</p>
<div>blbaba</div>
</div>
Combing both doesn't work in Chrome (for instance), you can check first if it has sibling, if it is null then get the ancestor by a second XPath.
An explanation of this behavior of or
is here.
<html><head><script>
function doTest() {
testElement('a1');
testElement('a2');
}
function testElement(id) {
var ancestor = getPath("//div[@id='" + id +"']/ancestor::*[2]");
alert('ancestor ' + ancestor);
var sibling = getPath("//div[@id='" + id + "']/preceding-sibling::*[2]");
alert('sibling ' + sibling);
var both = getPath("//div[@id='" + id + "']/preceding-sibling::*[2]|//div[@id='" + id +"']/ancestor::*[2]");
alert('both ' + both);
}
function getPath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
</script></head>
<body onload='doTest()'>
<div>
<div id='a1'>blbaba</div>
<p>a</p>
<p>b</p>
<p id='me'>c</p>
<p>d</p>
<div id="a2">blbaba</div>
<p>e</p>
<p>f</p>
<p>g</p>
<p>h</p>
<div>blbaba</div>
</div>
</body>