1

I have a html code like this:

<div  class='one two three and etc.'>
    <div>
           <div>
                <div class='my_target'>
               </div>
            </div>
    </div>
</div>

and I need to find div with class="my_target" whose great-grandfather has class containing a word "two", for example. I tried an expression like this:

//div[../../../[contains(@class,'two')]]

but it doesn't work! Can somebody help me?

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
Alex Rixon
  • 59
  • 1
  • 6

3 Answers3

3

You were close. You needed to use this:

//div[contains(@class, 'my_target'][contains(../../../@class,'two')]

Note the parentheses around the ../../.. and the lack of a slash after that part.

Or since there can only be

But I would recommend this alternative:

//*[contains(@class, 'two')]/*/*/div[contains(@class, 'my_target')]

Note how it doesn't have go down the tree and climb back up again.

Side note: As Phrogz correctly points out, a more reliable match for class name is to surround the class attribute and the target class name in spaces:

//*[contains(concat(' ', normalize-space(@class), ' '), ' two ')]/*/*
    /div[contains(concat(' ', normalize-space(@class), ' '), ' my_target ')]
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • I have never seen such expression and it works. Thanx for your answer JLRishe – Alex Rixon Jan 21 '15 at 14:13
  • Note that this will incorrectly match `class="two-fold"`; the standard way of using XPath to match a word inside a space-delimited list is to [wrap the string in spaces and then test for the word surrounded by spaces](http://stackoverflow.com/questions/1390568/how-to-match-attributes-that-contain-a-certain-string), e.g. `contains( concat(' ',@class,' '), ' two ' )`. – Phrogz Jan 22 '15 at 16:35
  • 1
    @Phrogz Quite right. I'm usually more diligent about that, but I guess I was off my game yesterday. :) To be perfectly correct, you'll also need a `normalize-space()`. – JLRishe Jan 22 '15 at 16:39
1

Better to approach the problem from another direction. Use the following expression:

//div[contains(@class, 'two')]/*/*/div[@class = 'my_target']

which means

//div[contains(@class, 'two')]     Select `div` elements anywhere in the document, if
                                   their `class` attribute contains "two"
/*/*/div[@class = 'my_target']     look for any grand-grandchild named `div` whose
                                   `class` attribute value is "my_target"

and which will return

<div class="my_target"></div>

EDIT

If there is reason to believe that "two" will occur in other contexts where it should not be matched (e.g. <div class='two-fold'/>), then use

contains( concat(' ', normalize-space(@class) ,' '), ' two ' )

Thanks to Phrogz for this suggestion.

Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
0

Try

//div[contains(../../../@class,'two')]

or to get class="my_target" elements only

//div[contains(@class,'my_target') and contains(../../../@class,'two')]
Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
  • Only answers half of the question and there are already two very similar answers, one of them explaining why `../../../` might be more costly. – Mathias Müller Jan 21 '15 at 14:14