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

22,061

Solution 1

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"/>

Solution 2

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.

Share:
22,061
Alp
Author by

Alp

I am passionate for all topics related to web development. In particular i enjoy writing code in Python and JavaScript. I love opportunities to learn about state-of-the-art techniques, developing useful features with pleasant UX and establishing stable build and deploy chains.

Updated on July 09, 2022

Comments

  • Alp
    Alp almost 2 years

    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?

  • Alp
    Alp about 13 years
    Got the same error for //*[not(self::input)][@*[ends-with(., 'Copyright')]].
  • Martin Honnen
    Martin Honnen about 13 years
    Are you sure Selenium supports an XPath 2.0 function like ends-with?
  • Martin Honnen
    Martin Honnen about 13 years
    As far as I understand XPath the expressions are fine. However I don't know Selenium so I don't know why it complains and can't help with that. Hopefully someone else comes along and can tell you what is wrong.
  • Alp
    Alp about 13 years
    Anyway thanks for your help. I think i have to ask this in the Selenium usergroup.
  • Alp
    Alp about 13 years
    Thank you, i will check this later.
  • Dimitre Novatchev
    Dimitre Novatchev about 13 years
    @Martin Honnen: [Re: I don't see any reason why //*[not(input)][ends-with(@*, 'Copyright')] is not accepted as a legal expression] See my answer for explanation why this should produce (as it does) a runtime error.
  • Alp
    Alp about 13 years
    Sadly not working too: The expression is not a legal expression: xpath=//*[not(self::input)][@*[substring(., string-length()-8) = 'Copyright')]]
  • Dimitre Novatchev
    Dimitre Novatchev about 13 years
    @Alp: Seems you are not working with a compliant XPath processor -- if so, your question will be better adressed in a more specific tag, not in the "xpath" tag, which assumes that the question is an XPath question.
  • Dimitre Novatchev
    Dimitre Novatchev about 13 years
    @Alp: I edited my answer and it now includes a complete XSLT-based verification of the second XPath expression. You may just run this verification yourself and see that the wanted result is produced.
  • Adam
    Adam almost 13 years
    Selenium uses the browser's XPath engine so it is likely a complete implementation. I've had issues with frames or multiple windows and Selenium focus for XPaths. Are you using any of those features?
  • Dimitre Novatchev
    Dimitre Novatchev almost 13 years
    Please, note, @Adam, that "complete" isn't a synonym for "compliant" or "correct". There is a W3C test suite -- run Selenium on it.
  • Adam
    Adam almost 13 years
    @Dimitre - I believe we've had this discussion in other comment threads. :)
  • yegor256
    yegor256 almost 11 years
    Yes, starts-with is also from XPath 2.0
  • Michael Kay
    Michael Kay almost 11 years
    No, starts-with was available in 1.0. ends-with was new in 2.0.