[xpath] 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?

This question is related to xpath if-statement

The answer is


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> 

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.


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)

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.


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>