javascript - nth preceding element in XPATH (sibling or ancestor ) - Stack Overflow

admin2025-04-19  0

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>

Share Improve this question asked May 22, 2015 at 9:49 user000111181user000111181 4237 silver badges20 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

The 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 divs can be nested, while ps 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>
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745078149a283691.html

最新回复(0)