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

2020-03-02 04:21发布

问题:

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?

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


回答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.



回答3:

Most likely explanation is that you are using an XPath 1.0 processors. The ends-with() function requires XPath 2.0 support.



回答4:

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

May be small correction with string-length(.)

Now, it may work.