XPath: Search for HTML element within HTML element - xpath

With XPath, how would you search for elements that only contain another specific element? For example, what expression would result in getting all <p> tags that contain <strong> elements within them?
<p>This is some text that <strong>contains another HTML element</strong></p>

In XPath you use square brackets to filter. It is called the predicate. See I.e. this tutorial .
To select all p’s with a element strong you use
//p[strong]
If you want to find all p’s with only the element strong and no other elements, you add
//p[strong][count(*)=count(strong)]
The * stands for any element.
If, as in your example , you only interested in p’s with the strong element being the last child node you use
//p[strong[not(following-sibling::node())]]

Predicates are the way to go.
what expression would result in getting all p tags that contain elements within them?
If you only want to select p elements with direct strong children, you can use p[strong], if you're looking for any descendants, use p[descendant::strong]. In both cases the context node has to be at the level of the p elements.

Related

How to ignore non visible elements in xPath?

I want to ignore elements from my xpath query that are not visible, either because they or any of their ancestors are display:none.
I tried the below, but it's including elements which non displayable ancestors
//button[
not(contains(#style,'display:none'))
and
not(ancestor::div[contains(#style,'display:none')])
and
contains(translate(., 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ', 'abcdefghijklmnñopqrstuvwxyz'), 'acept')
]
"
Try to apply this
//*[not(ancestor-or-self::*[contains(#style,'display: none;')])]

Get element name by containing text

I'm looking through HTML documents for the text: "Required". What I need to find is the element that holds the text. For example:
<p>... Required<p>
I would get to element name = p
However, it might not be in a <p> tag. It could be in any kind of tag, which is where this question differs from some of the other search text Stack Overflow questions.
Right now I'm using:
page.at(':contains("Required")')
but this only get me the full HTML element
The problem you have is the :contains pseudo class matches any element that has the searched for text anywhere in its descendants. You need to find the innermost element that contains such text. Since html is the ancestor of all elements, if the page contains the text anywhere then html will contain, and so that will be the first matching element.
I’m not sure you can achieve this with CSS, but you can use XPath like this:
page.at_xpath('//*[text()[contains(., "Required")]]')
This finds the first element node that has a text() node as a child that contains Required. When you have that node (if it exists) you can then call name on it to give the name of the element.
For CSS you can do:
page.at('[text()*="Required"]')
It's not real CSS though, or even a jQuery extra.
You should use CSS selectors:
page.css('p').text

xpath: find a node whose content has a provided string

I have some HTML like this:
<div> Make </div>
And I want to match it based on the fact that the content of the node contains the text "Make".
Put another way "Make" is a substring of the div node's content and I want to make such a match on this node using XPath.
The obvious solution would be
//div[contains(., 'Make')]
but this will find all divs that contain the string "Make" anywhere within their content, so not only will it find the example you've given in the question but also any ancestor div of that one, or any divs where that substring is buried deep in a descendant element.
If you only want cases where that string is directly inside the div with no other intervening elements then you'd have to use the slightly more complex
//div[text()[contains(., 'Make')]]
This is subtly different from
//div[contains(text(), 'Make')]
which would look only in the first text node child of the div, so it would find <div>Make<br/>Break</div> but not <div>Break<br/>Make</div>
If you want to allow for intervening elements other than div, then try
//div[contains(., 'Make')][not(.//div[contains(., 'Make'])]
Seems like this is what you are looking for: //div[contains(text(),'Make')]
If this will not work you can try: //div[contains(.,'Make')]. This will find all divs, which contain 'Make' in any attribute.
To find that node anywhere in the document, you would need this:
//div[contains(text(), "Make")]

XPath intersection of two sets

I need to extract all links from a html document having text as the inner element and not a reference to an image. Basically I would like to do a doc.select("//a/attribute::href") for all elements in a tree where doc.select("//a/text()") returns anything. Thanks!
Well you can write conditions in XPath in a predicate in square brackets, e.g. //a[text()]/#href selects the href attributes of all link (a) elements that have at least one text node child. Or if you want to make sure there is no img child element in the link you can use e.g. //a[not(img)]/#href.

How to find the first link on the page containing this text?

If I have two links:
<div class="abc">
<a id="def1" href="/definitely">Definitely 1</a>
<a id="def2" href="/definitely">Definitely 2</a>
</div>
And I want to identify the first (def1), I thought this would work:
var linkXPath = "//div[#class='abc']//a[contains(#href,'def')][1]";
But it doesn't seem to.
What am I doing wrong?
It is a FAQ why
//someName[1]
doesn't select the first element of //someName.
Looking at the definition of the // abbreviation, one would realize that in fact
//someName[1]
is equivalent to:
/descendant-or-self::node()/someName[1]
and this selects every someName element that is the first someName child of its parent node.
Thus, if there are two or more someName elements that are the first someName child of their parent, all of them are selected.
Solution:
Instead of
//someName[1]
use:
(//someName)[1]
So, in your particular case use:
(//div[#class='abc']//a[contains(#href,'def')]) [1]
Apart from this, none of the above expressions would select any node, if in the actual XML document a default namespace was specified. Selecting nodes in a document with a default namespace is the biggest XPath FAQ. To find the solution just search for "default namespace" in this SO tag and anywhere on the Internet.
Your XPath expression selects the first a element (with the right href) of every div (that has the right class) that contains one. So if there were two divs that matched, each with multiple a elements that matched, you'd get a reault set containing two elements -- the first a in the first div, and the first a in the second div.
To select just the first element of the entire result set, use parentheses like so:
(//div[#class='abc']//a[contains(#href,'def')])[1]
Other than that, your expression works fine for me (tested here).

Resources