Selecting Siblings Between Two Nodes Using Xpath

Selecting siblings between two nodes using XPath

I believe this XPath expression selects the nodes you want:

//table[@class="header_completed"]/
following-sibling::table[@align="center"][1]/
preceding-sibling::table[
preceding-sibling::table[@class="header_completed"]
]

First I navigate to the table with @class="header_completed".

From there I select the first following sibling table with @align="center".

From there I select all preceding sibling tables that have a preceding sibling which is the table with @class="header_completed".

xpath select elements between two nodes

The xpath is going to be a long one so brace yourself:

count(//tr[preceding-sibling::tr/td[@class = 'd2']][count(.|//tr[following-sibling::tr/td[@class = 'd2']])=count(//tr[following-sibling::tr/td[@class = 'd2']])])

To select the actual nodes and not have just the count, simply remove the first count :

//tr[preceding-sibling::tr/td[@class = 'd2']][count(.|//tr[following-sibling::tr/td[@class = 'd2']])=count(//tr[following-sibling::tr/td[@class = 'd2']])]

There are various things happening here notably:

  • Select start node by selecting preceding sibling's child node tr/td with id='2'
  • Select end node by selecting following sibling's child node tr/td with id='2'
  • Use kaycian method : http://www.dpawson.co.uk/xsl/sect2/muench.html#d9940e108 to get the intersection between the two nodes.

XPath: Select multiple 'following-sibling'

The question appears to silently assume that it is not desirable to include child id values in the XPath expression. This solution is along the lines of what you are trying:

//child[@name = 'alpha']/(following-sibling::rogue | following-sibling::child)

The parentheses construct a sequence. The | is the union operator. Thus this is forming a sequence of all of the following sibling rogue and child elements.

Another working expression that is similar to the initial attempts in the question. This selects all following siblings, but uses a predicate to restrict to only rogue and child.

//child[@name = 'alpha']/following-sibling::*[self::rogue | self::child]

XPath - Select first group of siblings between two nodes

Now, what I'm trying to do is to get only those elements that are
between the B and C nodes

Use this single XPath expression:

   /*/*/tr[.='B']
/following-sibling::*
[count(.|/*/*/tr[. ='C']/preceding-sibling::*)
=
count(/*/*/tr[. ='C']/preceding-sibling::*)
]

Here is an XSLT - based verification:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/">
<xsl:copy-of select=
"/*/*/tr[.='B']
/following-sibling::*
[count(.|/*/*/tr[. ='C']/preceding-sibling::*)
=
count(/*/*/tr[. ='C']/preceding-sibling::*)
]
"/>
</xsl:template>
</xsl:stylesheet>

when this transformation is applied on the first provided XML document:

<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr class="theClass">C</tr>
<tr class="theClass">D</tr>
<tr>6</tr>
<tr>7</tr>
<tr>8</tr>
<tr>9</tr>
<tr>10</tr>
<tr>11</tr>
<tr>12</tr>
<tr>13</tr>
<tr>14</tr>
<tr>15</tr>
<tr class="theClass">E</tr>
<tr class="theClass">F</tr>
<tr>16</tr>
<tr>17</tr>
<tr>18</tr>
<tr>19</tr>
<tr>20</tr>
<tr>21</tr>
<tr>22</tr>
</tbody>
</table>

the XPath expression is evaluated and the selected nodes are copied to the output:

<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>

Explanation:

Here we simply use the Kayessian formula for node-set intersection:

$ns1[count(.|$ns2) = count($ns2)]

where we substituted $ns1 with:

 /*/*/tr[.='B']
/following-sibling::*

and we substituted $ns2 with:

/*/*/tr[. ='C']/preceding-sibling::*

The second problem:

My problem starts now. I want to write a single expression that will
return only the first group of elements that come after a <td
class="theClass"></td>
tag, no matter how many more groups are
following it.

Again a single XPath expression selecting those elements exists:

   /*/*/tr[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
][1]
/following-sibling::tr
[not(@*)
and
count(preceding-sibling::tr
[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
]
)
= 1
]

Explanation:

This selects all following siblings tr elements (that satisfy a number of conditions) of the first */*/tr element whose class attribute has string value "theClass" and whose first following element sibling is a tr that has no attributes.

The conditions that these selected tr elements also satisfy are two: 1) they don't have any attributes; and 2) they have only one preceding sibling tr element, whose class attribute has string value "theClass".

And here is the XSLT - based verification:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/">
<xsl:copy-of select=
"/*/*/tr[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
][1]
/following-sibling::tr
[not(@*)
and
count(preceding-sibling::tr
[@class='theClass'
and
following-sibling::*[1][self::tr[not(@*)] ]
]
)
= 1
]
"/>
</xsl:template>
</xsl:stylesheet>

when applied on the second provided XML document:

<table id="theTable">
<tbody>
<tr class="theClass">A</tr>
<tr class="theClass">B</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr>6</tr>
</tbody>
</table>

again the wanted and correctly selected elements are output:

<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
<tr>4</tr>
<tr>5</tr>
<tr>6</tr>

Selecting (siblings) between two tags using XPath (in .NET)

This can easily be done with XPath 2.0 or with XPath 1.0 hosted by XSLT.

With XPath 1.0 hosted by .NET this can be achieved in several steps:

  1. Make the appropriate "p" node the current node.

  2. Find the number of all <br /> children of the current "p" node:

    count(br)

  3. if N is the count, determined in step 2. for $k in 0 to N do:

    3.1 Find all nodes that are preceded by $k <br /> elements:

    node()[not(self::br) and count(preceding::br) = $k]

    3.2 For every such node found, get its string value

    3.3 Concatenate all string values obtained in step 3.2. The result of this concatenation is all the text contained in the given paragraph.

Note: In order to substitute what should stand for $k in step 3.1 it is necessary to dynamically construct this expression.

XPath: Select following siblings until certain class

Good question!

The following expression will give you 1..2, 3..5 or 6..7, depending on input X + 1, where X is the set you want (2 gives 1-2, 3 gives 3-.5 etc). In the example, I select the third set, hence it has [4]:

/table/tr[1]
/td[not(@class = 'foo')]
[
generate-id(../td[@class='foo'][4])
= generate-id(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1])
]

The beauty of this expression (imnsho) is that you can index by the given set (as opposed to index by relative position) and that is has only one place where you need to update the expression. If you want the sixth set, just type [7].

This expression works for any situation where you have siblings where you need the siblings between any two nodes of the same requirement (@class = 'foo'). I'll update with an explanation.

Replace the [4] in the expression with whatever set you need, plus 1. In oXygen, the above expression shows me the following selection:

Sample Image

Explanation

/table/tr[1]

Selects the first tr.

/td[not(@class = 'foo')]

Selects any td not foo

generate-id(../td[@class='foo'][4])

Gets the identity of the xth foo, in this case, this selects empty, and returns empty. In all other cases, it will return the identity of the next foo that we are interested in.

generate-id(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1])

Gets the identity of the first previous foo (counting backward from any non-foo element) and from there, the first following foo. In the case of node 7, this returns the identity of nothingness, resulting in true for our example case of [4]. In the case of node 3, this will result in c, which is not equal to nothingness, resulting in false.

If the example would have value [2], this last bit would return node b for nodes 1 and 2, which is equal to the identity of ../td[@class='foo'][2], returning true. For nodes 4 and 7 etc, this will return false.

Update, alternative #1

We can replace the generate-id function with a count-preceding-sibling function. Since the count of the siblings before the two foo nodes is different for each, this works as an alternative for generate-id.

By now it starts to grow just as wieldy as GSerg's answer, though:

/table/tr[1]
/td[not(@class = 'foo')]
[
count(../td[@class='foo'][4]/preceding-sibling::*)
= count(
preceding-sibling::td[@class='foo'][1]
/following-sibling::td[@class='foo'][1]/preceding-sibling::*)
]

The same "indexing" method applies. Where I write [4] above, replace it with the nth + 1 of the intersection position you are interested in.



Related Topics



Leave a reply



Submit