Split methods on XPath 1.0 - xpath

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'].

Related

xpath 1.0 set the value of non existent attribute

I have a xml document
<?xml version="1.0" encoding="UTF-16"?>
<APIDATA xmlns="api-com">
<ORDER EngineID="1" OrderID="66" OtherInfo="100"><INSTSPECIFIER InstID="27" SeqID="17"/>
</ORDER>
<ORDER EngineID="2" OrderID="67" OtherInfo="200"><INSTSPECIFIER InstID="28" SeqID="18"/>
</ORDER>
<ORDER EngineID="3" OrderID="68"><INSTSPECIFIER InstID="29" SeqID="19"/></ORDER>
</APIDATA>
How do i get the value of OtherInfo attribute using xpath
but when it does not exist i want Null to be returned
If i used the following xpath /APIDATA/ORDER/#OtherInfo i get the output as
100
200
But since for OrderID 68 the OtherInfo is missing i want the output to be
100
200
0
There is a post here which is close to my solution but i somehow cant get it to work
Can I create a value for a missing tag in XPath?
Unfortunately, the approach in the answer to the linked question only work if there is only one value to be returned from a given XML document (see below for a demo on this point). So given the XML sample posted in this question, short answer would be this can't be done using pure XPath 1.0.
If the XPath is used within a programming language, one possible approach would be, to use XPath expression that always return a value, for example /APIDATA/ORDER. Then, for each <ORDER> element returned, usually there are plenty of options to get OtherInfo attribute and provide default value in case the attribute is not found.
Applying the linked post approach to your case would results in the following XPath expression :
substring(concat("0", //ORDER[3]/#OtherInfo), 2 * number(boolean(//ORDER[3]/#OtherInfo)))
The XPath successfully return 0 when applied to the 3rd <ORDER> element which doesn't have attribute OtherInfo, see the demo* : xpathtester, xpatheval
default namespace has been removed in the demo for the sake of simplifying the XPath
Implementation of the same approach in XSLT 1.0, as requested :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:d="api-com">
<xsl:output method="text" indent="yes"/>
<xsl:template match="d:ORDER">
<xsl:value-of select="substring(concat('0', #OtherInfo), 2 * number(boolean(#OtherInfo)))"/>
</xsl:template>
</xsl:stylesheet>
output :
100
200
0
Demo : xpathtester

xpath with multiple predicates equivalence

I was told that the following are not the same:
a[1][#attr="foo"]
a[#attr="foo"][1]
Can someone explain why that is the case?
Think of XPath expressions as defining a result set1 - a set of nodes that fulfil all the requirements stated in the XPath expression. The predicates of XPath expressions (the parts inside []) either have no effect on the result set or they incrementally narrow it.
Put another way, in the following expression:
//xyz[#abc="yes"]
[#abc="yes"] reduces the result set defined to the left of it, by //xyz.
Note that, as Michael Kay has suggested, all that is said below only applies to XPath expressions with at least one positional predicate. Positional predicates are either a number: [1] or evaluate to a number, or contain position() or last().
If no positional predicate is present, the order of predicates in XPath expressions is not significant.
Consider the following simple input document:
<root>
<a attr="other"/>
<a attr="foo"/>
<a attr="other"/>
<a attr="foo"/>
</root>
As you can see, a[#attr = 'foo'] is not the first child element of root. If we apply
//a[1]
to this document, this will of course result in
<a attr="other"/>
Now, crucially, if we add another predicate to the expression, like so:
//a[1][#attr="foo"]
Then, [#attr="foo"] can only influence the result set defined by //a[1] already. In this result set, there is no a[#attr="foo"] - and the final result is empty.
On the other hand, if we start out with
//a[#attr="foo"]
the result will be
<a attr="foo"/>
-----------------------
<a attr="foo"/>
and in this case, if we add a second predicate:
//a[#attr="foo"][1]
the second predicate [1] can narrow down the result set of //a[#attr="foo"] to only contain the first of those nodes.
If you know XSLT, you might find an XSLT (and XPath 2.0) proof of this helpful:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<result1>
<xsl:copy-of select="//a[1][#attr='foo']"/>
</result1>
<result2>
<xsl:copy-of select="//a[#attr='foo'][1]"/>
</result2>
</xsl:template>
</xsl:transform>
And the result will be
<result1/>
<result2>
<a attr="foo"/>
</result2>
1 Technically speaking, only XPath 1.0 calls this result a node-set. In XPath 2.0, all sets have become true sequences of nodes.

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++;
}

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