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

125,153

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>
Share:
125,153

Related videos on Youtube

kanso
Author by

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, 2021

Comments

  • kanso
    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
      James Conigliaro about 15 years
      Is 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
      Michael Petrotta about 15 years
      I find the w3schools tutorials easy to work with. You might start there. w3schools.com/Xpath
    • Noldorin
      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
      Vadzim almost 10 years
  • Rob Kennedy
    Rob Kennedy about 15 years
    I 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
    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
    kanso about 15 years
    I'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
    George Birbilis almost 10 years
    With 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
    George Birbilis almost 10 years
    maybe 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
    LuckyLikey over 8 years
    the becker method. I was looking for the ? operator of c#.
  • Tomalak
    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
    LuckyLikey over 8 years
    @Tomalak Be sure, im glad I found this solution. Thanks for the hint with XPath 2.0+
  • ADJenks
    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/…