I'm not sure I understand the "'. $text .'"
part of your answer... I guess that means some sample text, not an intended reference to a variable named text?
Anyway, when you use contains(., "foo")
you are asking whether the current node's string value contains "foo". The current node's string value is the concatenation of all descendant text nodes' string values. That is why //*[contains(., "foo")]
returns a list of nodes: it matches every ancestor of every text node containing "foo". (And it can be very inefficient because you're doing that concatenation function on every node in the tree.)
The reason your starts-with()
answer worked (sometimes) is that you got lucky: the parent node of the text node had other preceding siblings with their own text, so the grandparent node's text value started with something else. Also very inefficient...
If the text you're looking for will only be in one text node -- i.e. it will not be split up across multiple elements / comments / etc. -- then you can efficiently and accurately match only the element containing the text node, using [edited]:
//*[text()[contains(., "foo")]]
(similar to what @biziclop said).
If the text you're looking might be split up across multiple elements / comments / etc. -- then you can use this [edited, twice]:
//*[contains(., "foo") and not(*[contains(., "foo")])]
But that's fairly inefficient. The following is not guaranteed to work:
//*[contains(., "foo")][1]
It will give you [edited, twice] every element that is a first child of its parent that (is an ancestor of one that) contains the text. (Or an empty nodeset, if "foo" is not found.) I'm trusting @Alejandro on this one... I still have not internalized how to tell when [position() = x] applies to the most recent location step only. Regardless, this XPath expression is not guaranteed to give you the right result.