Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xpath error with not() and ends-with()

I have the following Xpath expression:

//*[not(input)][ends-with(@*, 'Copyright')]

I expect it to give me all elements - except input - with any attribute value which ends with "Copyright".

I execute it in the Selenium 2 Java API with webDriver.findElements(By.xpath(expression)) and get the following error:

The expression is not a legal expression

But these expressions work without trouble:

//*[not(input)][starts-with(@*, 'Copyright')]
//*[ends-with(@*, 'Copyright')]

Any ideas?

like image 652
Alp Avatar asked May 29 '11 11:05

Alp


People also ask

What is text () function in XPath?

XPath text() function is a built-in function of the Selenium web driver that locates items based on their text. It aids in the identification of certain text elements as well as the location of those components within a set of text nodes. The elements that need to be found should be in string format.

Which of the following ends with same as XPath ends with?

This is the equivalent to the XPath ends-with. Therefore, $= is the correct answer.

What is the difference between '/' and in XPath?

Difference between “/” and “//” in XPathSingle slash is used to create absolute XPath whereas Double slash is used to create relative XPath. 2. Single slash selects an element from the root node. For example, /html will select the root HTML element.


2 Answers

I have the following Xpath expression:

//*[not(input)][ends-with(@*, 'Copyright')]

I expect it to give me all elements - except input - with any attribute value which ends with "Copyright".

There are a few issues here:

  1. ends-with() is a standard XPath 2.0 function only, so the chances are you are using an XPath 1.0 engine and it correctly raises an error because it doesn't know about a function called ends-with().

  2. Even if you are working with an XPath 2.0 processor, the expression ends-with(@*, 'Copyright') results in error in the general case, because the ends-with() function is defined to accept atmost a single string (xs:string?) as both of its operands -- however @* produces a sequence of more than one string in the case when the element has more than one attribute.

  3. //*[not(input)] doesn't mean "select all elements that are not named input. The real meaning is: "Select all elements that dont have a child element named "input".

Solution:

  1. Use this XPath 2.0 expression: //*[not(self::input)][@*[ends-with(.,'Copyright')]]

  2. In the case of XPath 1.0 use this expression:

....

  //*[not(self::input)]
        [@*[substring(., string-length() -8) = 'Copyright']]

Here is a short and complete verification of the last XPath expression, using XSLT:

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

 <xsl:template match="/*">
     <xsl:copy-of select=
     "//*[not(self::input)]
           [@*[substring(., string-length() -8)
              = 'Copyright'
              ]
          ]"/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<html>
 <input/>
 <a x="Copyright not"/>
 <a y="This is a Copyright"/>
</html>

the wanted, correct result is produced:

<a y="This is a Copyright"/>

In the case of the XML document being in a default namespace:

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

 <xsl:template match="/*">
     <xsl:copy-of select=
     "//*[not(self::x:input)]
           [@*[substring(., string-length() -8)
              = 'Copyright'
              ]
          ]"/>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<html xmlns="http://www.w3.org/1999/xhtml">
 <input z="This is a Copyright"/>
 <a x="Copyright not"/>
 <a y="This is a Copyright"/>
</html>

the wanted, correct result is produced:

<a xmlns="http://www.w3.org/1999/xhtml" y="This is a Copyright"/>
like image 139
Dimitre Novatchev Avatar answered Sep 23 '22 05:09

Dimitre Novatchev


I don't know Selenium but if //*[not(input)][starts-with(@*, 'Copyright')] is parsed successfully and if additionally the XPath 2.0 function ends-with is supported then I don't see any reason why //*[not(input)][ends-with(@*, 'Copyright')] is not accepted as a legal expression. Your verbal description however sounds as if you want //*[not(self::input)][@*[ends-with(., 'Copyright')]].

//*[not(input)] selects any elements not having any input child element while //*[not(self::input)] selects any elements not being themselves input elements. As for comparing [@*[ends-with(., 'Copyright')]] with what you have, my suggestion is true as long as there is any attribute node which ends with 'Copyright' while your test would only work if there is a single attribute which ends with 'Copyright', as ends-with http://www.w3.org/TR/xquery-operators/#func-ends-with allow a sequence with a single item as its first argument or an empty sequence but not several items.

like image 27
Martin Honnen Avatar answered Sep 26 '22 05:09

Martin Honnen