8
votes

Here's something really simple (at least I guess), I just do not get the clue.

I have to parse a large XML document to get a specific node, identified by one of its subnode values. Thats easy so far. But when I try to parse onward from that node relatively upward, selecting the preceding-siblings of its ancestor by using a predicate I get a list of nodes, from that on I have to walk downward again.

In Theorie, that is a table, with 5 columns and two rows (in the shown example below). I get just the id element of one field, and need to find the name given in the first field of the row. The first field is always of type 'Link' and has a name subnode with text - which is the thing to get.

In other words, I need to move from any node with an <id>XXX_X</i> to the next preceding-sibling cell with a control of xsi:type='Label' and a name node. From the node <id>MyItemId_1</> I need to get the second preceding-sibling, from the node <id>MyItemId_4</id> I need to get the 5th preceding-sibling.

This is a sample xml piece:

<cell>
    <control xsi:type="Label">
        <id>1234</id>
        <name>MyOtherItemName</name>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Label">
        <id>MyOtherItemId_0</id>
        <name/>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Label">
        <id>MyOtherItemId_1</id>
        <name/>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyOtherItemId_2</id>
        <name>552</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyOtherItemId_3</id>
        <name>432</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyOtherItemId_4</id>
        <name>33</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Label">
        <id>1234</id>
        <name>MyItemName</name>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Label">
        <id>MyItemId_0</id>
        <name/>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Label">
        <id>MyItemId_1</id>
        <name/>
        <message/>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyItemId_2</id>
        <name>552</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyItemId_3</id>
        <name>432</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>
<cell>
    <control xsi:type="Button">
        <id>MyItemId_4</id>
        <name>33</name>
        <message/>
        <type>Link</type>
        <selected>false</selected>
    </control>
    <selected>false</selected>
    <style>Odd</style>
</cell>

I do get the item i have to get with this xpath:

//cell[control[type='Link']]/control[type='Link' and selected='false' and id='MyItemId_3']/id

That selects the id of the control of the cell, namely the 4th column in the second row, of the rendered table.

From that node on I try moving to the first cell in the row by following this path:

../../preceding-sibling::cell[control[@xsi:type='Label' and name[node()]]]/control[name[node()]]/name

That gives me the two correct cells of the first column of the table.

<name>MyOtherItemName</name>
* * * * * * * * * *
<name>MyItemName</name>

Now it breaks my back since I can't get it to just give me back the last one of the two selected.

I tried this:

../../preceding-sibling::cell[control[@xsi:type='Label' and name[node()]]][1]/control[name[node()]]/name

which is a preceding-sibling selection with a predicate to exactly the sort of siblings I search for, but it seems I can not combine that predicate with a [1] selector. Instead of selecting the desired first preceding sibling "MyItemName" it selects the first sibling from all preceding ones "MyOtherItemName".

I need help, hope someone here has a clue and can pinpoint me in the right direction.

Exactly what I set up to get this work is copying the xml into http://www.bit-101.com/xpath/ and working with the concatenated xpathes on it to simulate what the software should do:

//cell[control[type='Link']]/control[type='Link' and selected='false' and id='MyItemId_3']/id/../../preceding-sibling::cell[control[@xsi:type='Label' and name[node()]]]/control[name[node()]]/name
3
So you're trying to select MyItemName? How is that the first cell in the row? It sure doesn't look like it.Wayne
I lost it somewhere between the 5 column table and first field of type Link. Do you think you can rephrase your requirements without the table analogy?forty-two
Agree with @forty-two. It's often good to show what you've tried, so I thank you for that work, but in this case the what-I've-tried is obscuring your original requirements. You forgot to tell us exactly and precisely what you were trying to do in the first place.Wayne
Thank you for your thoughts. @iwburk, it is the first cell in a row, but only after rendering, the xml itself does not really or clearly state it. But the combination of xsi:type='Label' and the name-node with text is the exactly pattern on that the software behind renders the cell as first column in the row.Oliver Friedrich
I tried the xpath with bit-101.com/xpath as well as with the software I use. In both cases adding "[1]" to the preceding-sibling gives me the wrong on of the two selected.Oliver Friedrich

3 Answers

18
votes

I do not understand what the problem exactly is, but preceding-siblings are sorted from the node itself towards the beginning of the document, i.e. the other way round than in the document. To get the nearest preceding sibling, use preceding-sibling[1], to get the farthest one (i.e. the first one in the document order), use preceding-sibling[last()].

2
votes

After reading your update, wouldn't this work:

//cell[control/id="MyItemId_4"]/preceding-sibling::cell[control[@xsi:type='Label']  and not(control/name='')][1]

I'm a bit unsure about the name node: do you want to test for existence of text in the the name node or just existence of the name node itself?

2
votes

YourWebElement.FindElement(By.XPath("preceding-sibling::*[1]"));

Here 1 indicates just the sibling above the selected node and then you can do recursion in order to get all the preceding siblings from bottom to top.