Is there an "if -then - else " statement in XPath?
Solution 1
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.
Solution 2
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.
Solution 3
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
.
Solution 4
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>
Solution 5
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>
Related videos on Youtube
kanso
I've had my B.Sc in software engineering at 2008, and since then had the privlige to work at big corportaes (Intel Research, Thomson Reuters), build the backbones of amazing startups (Outbrain, Ginger), complete my thesis, and keep learning new stuff all the time. I've done research in c and Java, backend in Java and Node, NLP in Groovy, scala and some C# (Hate it, sorry), some web in Angular, toyed around with native android apps and was a part of building an ionic cross-platform app. I love tera-incognita, and rather walk the bleeding cutting edge. As Plutarch said – “The mind is a fire to be kindled , not a vessel to be filled.” . So far , I’ve never been cold.
Updated on August 07, 2021Comments
-
kanso almost 3 years
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?
-
James Conigliaro about 15 yearsIs there more context to the problem? XPath is usually used to query information, not as a manipulation tool. Manipulation is usually left to something like an XSL template or a higher level language
-
Michael Petrotta about 15 yearsI find the w3schools tutorials easy to work with. You might start there. w3schools.com/Xpath
-
Noldorin about 15 years@James: This is actually a query. Don't let the if-then-else syntax fool you - it's technically a "conditional expression" and not an "if statement" - more akin to the ternary conditional operator in many programming languages.
-
Vadzim almost 10 yearsSimilar question: stackoverflow.com/questions/8833225/…
-
-
Rob Kennedy about 15 yearsI don't think it suggests you shouldn't use parentheses, only that you don't need to use them. If it's complaining about "no such function," then I suspect he's not using XPath 2. The spec requires parentheses around the conditional.
-
Noldorin about 15 years@Rob: It's quite possible, but then I do state that it may not necessarily be the fix. Always worth following the example if you're trying to get something working, however. :)
-
kanso about 15 yearsI'll probably won't use this (I'll just upgrade the xpath engine) but this is a great trick and a great tip :)
-
George Birbilis almost 10 yearsWith numeric expressions (it you don't need to select any string) it's simpler since you can do X * number(condition) + Y * number(condition) etc., where number(condition) gives you 0 for false and 1 for true ("Boolean True is converted to 1; Boolean False is converted to 0." msdn.microsoft.com/en-us/library/aa926043.aspx)
-
George Birbilis almost 10 yearsmaybe a +1 is needed in those expressions, I had to add such at: <xsl:template match="i:item"> <xsl:param name="selection"/> <xsl:variable name="selected" select="@id=$selection"/> <li class="menuItem" id="{substring('current', number(not($selected)) * (string-length('current')+1))}"> ...
-
LuckyLikey over 8 yearsthe becker method. I was looking for the
?
operator of c#. -
Tomalak over 8 years@LuckyLikey Well, we can't all get what we want, can we? There is a nicer way to do it in XPath 2.0+.
-
LuckyLikey over 8 years@Tomalak Be sure, im glad I found this solution. Thanks for the hint with XPath 2.0+
-
ADJenks over 5 years@Tomalak The link is broken, here's another link to a page discussing the method: blog.alessio.marchetti.name/post/2011/02/12/…