Search code examples
xmlxpathsiblings

XPath to return all elements on the same level


I am trying to work out the XPath from <a id='me'>match</a> that will match elements that are on the same level. IE. Siblings, Cousins, Second Cousins etc...

Using the following XML, an XPath expression from the first <a id='me'>match</a> that returns all of the elements on the same level with an id attribute of 'me':-

<alpha>
    <beta>
        <gamma1>
            <a id='me'>match</a>
            <a id='me'>match me too</a>
            <b id='me'>no match</b>
        </gamma1>
        <gamma2>
            <a id='notme'>no match</a>
            <a id='me'>match</a>
            </b>
        </gamma2>
    </beta>
    <delta>
        <epsilon>
            <a id='notme'>no match</a>
            <a id='me'>match</a>
            </b>
            <foo>
                <a id='me'>no match</a>
            </foo>
        </epsilon>
    </delta>
    <bar>
      <a id='me'>no match</a>
    </bar>
</alpha>

.\..\a[@id='me'] would return the two elements in <gamma1> and .\..\..\*\a[@id='me'] would return the matching elements in <gamma1> and <gamma2>. However, I'm trying to do an XPath that will return all the matching elements on the same level within document, irrelevant of their depth.

I could loop in code, going up and down by constructing dynamic xpaths but it wouldn't be very elegant and probably take a long time to run..

Anyone with any ideas?


Solution

  • The answer already given by Lingamurthy is perfectly valid, but I'd like to point out another way of looking at the problem. You explained that you are evaluating XPath expressions with a certain node as the starting point, /alpha/beta/gamma1/a[1]. Therefore, I assume you know the precise location of this node, even if nothing else is known about the document.

    If that holds true in your case, you could simply count the ancestors of that node (following the ancestor::* axis) and only select a elements with the same number of ancestors:

    //a[@id = 'me' and count(ancestor::*) = count(/alpha/beta/gamma1/a[1]/ancestor::*)]
    

    and the result will be (individual results separated by ---------):

    <a id="me">match</a>
    -----------------------
    <a id="me">match me too</a>
    -----------------------
    <a id="me">match
    </a>
    -----------------------
    <a id="me">match
    </a>
    

    Again, excluding the first result in this list with the technique shown in the other answer, (...)[position() != 1], if that matters to you.

    By the way: Your input XML is invalid, there are two a elements that are not properly closed.