Xpath matches with single quotes? - xpath

How can I assert an xpath match that contains single quotes within the string to be asserted?
This is my string with value '40' to be asserted.
I assumed to escape the single quote characters with \' but that does not work.
matches( //faultstring[1]/text(), 'This is my string with value \'40\' to be asserted.' )
How is this done properly?

Try this
//faultstring[matches(text(),''')]
or
//faultstring[matches(text(),''')]
or
//faultstring[matches(text(),''')]
For a more elegant solution see this post

My understanding of this question is that the problem is caused by the need to use nested quotes if the XPath expression is within an XML document.
If this is the case, one can use this XPath expression:
$yourString = "This is my string with value '40' to be asserted."
XSLT - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:copy-of select=
"/* = "This is my string with value &apos;40&apos; to be asserted.""/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>This is my string with value '40' to be asserted.</t>
the XPath expression is evaluated and the result of this evaluation is copied to the output:
true

Related

Split methods on XPath 1.0

I use 'XPath', how I can simulate split method?
I read documentation and I know that XPath version 1.0 not have this method.
I have document contains this tags:
<TestCategoryModule>
<ItemCategories>
<![CDATA[Birthday Travel,Travel]]>
</ItemCategories>
</TestCategoryModule>
<TestCategoryModule2>
<ItemCategories>
<![CDATA[Travel]]>
</ItemCategories>
</TestCategoryModule2>
I want filter item by 'ItemCategories', but when I filtered by world 'Travel', return 2 item. I use this filter "ItemCategories[contains(text(), 'Travel')]".
I want that I filter by "Travel" return only second item. How can do it?
Use:
/*/*/*[contains(concat(',', ., ','), ',Travel,')]
Here is XSLT-based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/*/*[contains(concat(',', ., ','), ',Travel,')]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document (essentially the provided XML fragment, extended with one more test case and made a well-formed XML document:
<t>
<TestCategoryModule>
<ItemCategories>Birthday Travel,Travel</ItemCategories>
</TestCategoryModule>
<TestCategoryModule2>
<ItemCategories>Birthday Travel</ItemCategories>
</TestCategoryModule2>
<TestCategoryModule2>
<ItemCategories>Travel</ItemCategories>
</TestCategoryModule2>
</t>
The wanted, correct result is produced:
<ItemCategories>Birthday Travel,Travel</ItemCategories>
<ItemCategories>Travel</ItemCategories>
I was a little wrong, or poorly described problumu. The problem is that the categories are stored as a string. I have three items, the first one contains categories: (Birthday Travel,Travel), second: (Birthday Travel), third: (Travel). When I request filtering for the word "Travel", I need to get the first and third items, but I get all three items, because all items contain world "Travel".
You actually don't need split() for the problem that you've described. If you want to match Travel but not Travel,Travel you want = instead of contains(). To deal with the whitespace around your CDATA sections, wrap it in normalize-space().
All put together, try ItemCategories[normalize-space(text()) = 'Travel'].

How to write the Regular expression for the Below to use in Webdriver?

I have a requirement for webdriver to use xpath using Regular expression.I have a list of id's with different values.How can i write a expression for the below type of values.
//*[#id="js_1"]
//*[#id="js_2"]
//*[#id="js_3"]
//*[#id="js_4"]
//*[#id="js_5"]
//*[#id="js_6"]
I have to write the regrular expression for that above xpath format using webdriver?
I have tried with the below
Listnames=box.findElements(By.xpath("//div[contains(#id, 'js_*')]"));
But it wont work for me.How can i write a expression.Please help me.
Thanks & Regards,
Shiva Oleti
If you use js_* as standard regular expression it matches js, js_, js__, js___ ...
The correct regular expression would be js_\d+
However, the XPath contains function does not use regular expressions, so you can just use js_ (although it won't check for numbers).
Or better
`//div[starts-with(#id, 'js_')]`
AFAIK, Webdriver supports XQuery (such as using XQUIB), therefore full XPath 2.0 is supported.
Use:
//*[matches(#id, '^js_\d+$')]
XSLT-2.0 - based verification:
<xsl:stylesheet version="2.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:sequence select="//*[matches(#id, '^js_\d+$')]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<t>
<x id="js_1"/>
<y id="a1"/>
<z id="js_2008"/>
</t>
the above XPath expression is evaluated and the result of this evaluation is copied to the output:
<x id="js_1"/>
<z id="js_2008"/>
Explanation:
Proper use of the XPath 2.0 function matches() and RegEx.
Else you can use a loop to add elements to your list ...
List <WebElement> ls = null;
i=1;
while(i<5)
{
ls.add(we.findElement(By.xpath("html/body/div[1]/div/div[2]/section/nav/div[2]/ul/li["+i+
"]")));
i++;
}

Return full text element (including child/descendant elements)

I'm trying to get the text from the first occurrence on the page of div/p, and only the first p. The <p> contains other tags (<b>, <a href>) and the returned text from <p> stops at any other tag. Is there a way to get this line to return all the text between <p> and </p>, even between embedded tags?
puts doc.xpath('html/body/div/p[1]/text()').first
Use:
string((//div/p)[1])
When this XPath expression is evaluated the result is the string value of the first p in the document that is a child of a div.
By definition the string value of an element is the concatenation (in document order) of all of its text-node descendents.
Therefore, you get exactly all the text in the subtree rooted by this p element, with any other nodes (elements, comments, PIs) skipped.
XSLT - based verification:
<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="string(p)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (no such provided!):
<p>
Hello <b>
XML
World!</b>
</p>
the result of the evaluated XPath expression is output:
Hello XML
World!
Using Nokogiri as an alternative for more XPath you can use Nokogiri::XML::Node#inner_text:
puts doc.xpath('html/body/div/p[1]').inner_text

XPath to return default value if node not present

Say I have a pair of XML documents
<Foo>
<Bar/>
<Baz>mystring</Baz>
</Foo>
and
<Foo>
<Bar/>
</Foo>
I want an XPath (Version 1.0 only) that returns "mystring" for the first document and "not-found" for the second. I tried
(string('not-found') | //Baz)[last()]
but the left hand side of the union isn't a node-set
In XPath 1.0, use:
concat(/Foo/Baz,
substring('not-found', 1 div not(/Foo/Baz)))
If you want to handle the posible empty Baz element, use:
concat(/Foo/Baz,
substring('not-found', 1 div not(/Foo/Baz[node()])))
With this input:
<Foo>
<Baz/>
</Foo>
Result: not-found string data type.
Special case:
If you want to get 0 if numeric node is missing or empty, use sum(/Foo/Baz) function
#Alejandro provided the best XPath 1.0 answer, which has been known for years, since first used by Jeni Tennison almost ten years ago.
The only problem with this expression is its shiny elegance, which makes it difficult to understand by not only novice programmers.
In a hosted XPath 1.0 (and every XPath is hosted!) one can use more understandable expressions:
string((/Foo/Baz | $vDefaults[not(/Foo/Baz/text())]/Foo/Baz)[last())
Here the variable $vDefaults is a separate document that has the same structure as the primary XML document, and whose text nodes contain default values.
Or, if XSLT is the hosting language, one can use the document() function:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:defaults>
<Foo>
<Bar/>
<Baz>not-found</Baz>
</Foo>
</my:defaults>
<xsl:template match="/">
<xsl:value-of select=
"concat(/Foo/Baz,
document('')[not(current()/Foo/Baz/text())]
/*/my:defaults/Foo/Baz
)"/>
</xsl:template>
</xsl:stylesheet>
Or, not using concat():
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:defaults>
<Foo>
<Bar/>
<Baz>not-found</Baz>
</Foo>
</my:defaults>
<xsl:variable name="vDefaults" select="document('')/*/my:defaults"/>
<xsl:template match="/">
<xsl:value-of select=
"(/Foo/Baz
| $vDefaults/Foo/Baz[not(current()/Foo/Baz/text())]
)
[last()]"/>
</xsl:template>
</xsl:stylesheet>
/Foo/(Baz/string(), 'not-found')[1]
If you are okay with printing an empty string instead of 'not-found' message then use:
/Foo/concat(Baz/text(), '')
Later, you can replace the empty strings with 'not-found'.

Is there an "if -then - else " statement in XPath?

It seems with all the rich amount of function in xpath that you could do an "if" . However , my engine keeps insisting "there is no such function" , and I hardly find any documentation on the web (I found some dubious sources , but the syntax they had didn't work)
I need to remove ':' from the end of a string (if exist), so I wanted to do this:
if (fn:ends-with(//div [#id='head']/text(),': '))
then (fn:substring-before(//div [#id='head']/text(),': ') )
else (//div [#id='head']/text())
Any advice?
Yes, there is a way to do it in XPath 1.0:
concat(
substring($s1, 1, number($condition) * string-length($s1)),
substring($s2, 1, number(not($condition)) * string-length($s2))
)
This relies on the concatenation of two mutually exclusive strings, the first one being empty if the condition is false (0 * string-length(...)), the second one being empty if the condition is true. This is called "Becker's method", attributed to Oliver Becker (original link is now dead, the web archive has a copy).
In your case:
concat(
substring(
substring-before(//div[#id='head']/text(), ': '),
1,
number(
ends-with(//div[#id='head']/text(), ': ')
)
* string-length(substring-before(//div [#id='head']/text(), ': '))
),
substring(
//div[#id='head']/text(),
1,
number(not(
ends-with(//div[#id='head']/text(), ': ')
))
* string-length(//div[#id='head']/text())
)
)
Though I would try to get rid of all the "//" before.
Also, there is the possibility that //div[#id='head'] returns more than one node.
Just be aware of that — using //div[#id='head'][1] is more defensive.
The official language specification for XPath 2.0 on W3.org details that the language does indeed support if statements. See Section 3.8 Conditional Expressions, in particular. Along with the syntax format and explanation, it gives the following example:
if ($widget1/unit-cost < $widget2/unit-cost)
then $widget1
else $widget2
This would suggest that you shouldn't have brackets surrounding your expressions (otherwise the syntax looks correct). I'm not wholly confident, but it's surely worth a try. So you'll want to change your query to look like this:
if (fn:ends-with(//div [#id='head']/text(),': '))
then fn:substring-before(//div [#id='head']/text(),': ')
else //div [#id='head']/text()
I do strongly suspect this may fix it however, as the fact that your XPath engine seems to be trying to interpret if as a function, where it is in fact a special construct of the language.
Finally, to point out the obvious, insure that your XPath engine does in fact support XPath 2.0 (as opposed to an earlier version)! I don't believe conditional expressions are part of previous versions of XPath.
How about using fn:replace(string,pattern,replace) instead?
XPATH is very often used in XSLTs and if you are in that situation and does not have XPATH 2.0 you could use:
<xsl:choose>
<xsl:when test="condition1">
condition1-statements
</xsl:when>
<xsl:when test="condition2">
condition2-statements
</xsl:when>
<xsl:otherwise>
otherwise-statements
</xsl:otherwise>
</xsl:choose>
according to pkarat's, law you can achieve conditional XPath in version 1.0.
For your case, follow the concept:
concat(substring-before(your-xpath[contains(.,':')],':'),your-xpath[not(contains(.,':'))])
This will definitely work. See how it works. Give two inputs
praba:
karan
For 1st input: it contains : so condition true, string before : will be the output, say praba is your output. 2nd condition will be false so no problems.
For 2nd input: it does not contain : so condition fails, coming to 2nd condition the string doesn't contain : so condition true... therefore output karan will be thrown.
Finally your output would be praba,karan.
Personally, I would use XSLT to transform the XML and remove the trailing colons. For example, suppose I have this input:
<?xml version="1.0" encoding="UTF-8"?>
<Document>
<Paragraph>This paragraph ends in a period.</Paragraph>
<Paragraph>This one ends in a colon:</Paragraph>
<Paragraph>This one has a : in the middle.</Paragraph>
</Document>
If I wanted to strip out trailing colons in my paragraphs, I would use this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
version="2.0">
<!-- identity -->
<xsl:template match="/|#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- strip out colons at the end of paragraphs -->
<xsl:template match="Paragraph">
<xsl:choose>
<!-- if it ends with a : -->
<xsl:when test="fn:ends-with(.,':')">
<xsl:copy>
<!-- copy everything but the last character -->
<xsl:value-of select="substring(., 1, string-length(.)-1)"></xsl:value-of>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Unfortunately the previous answers were no option for me so i researched for a while and found this solution:
http://blog.alessio.marchetti.name/post/2011/02/12/the-Oliver-Becker-s-XPath-method
I use it to output text if a certain Node exists. 4 is the length of the text foo. So i guess a more elegant solution would be the use of a variable.
substring('foo',number(not(normalize-space(/elements/the/element/)))*4)
Somewhat simpler XPath 1.0 solution, adapted from Tomalek's (posted here) and Dimitre's (here):
concat(substring($s1, 1 div number($cond)), substring($s2, 1 div number(not($cond))))
Note: I found an explicit number() was required to convert the bool to an int otherwise some XPath evaluators threw a type mismatch error. Depending on how strict your XPath processor is type-matching you may not need it.

Resources