50

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?

bluish
  • 26,356
  • 27
  • 122
  • 180
Yossale
  • 14,165
  • 22
  • 82
  • 109
  • 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 – James Conigliaro Jun 09 '09 at 16:19
  • I find the w3schools tutorials easy to work with. You might start there. http://www.w3schools.com/Xpath/ – Michael Petrotta Jun 09 '09 at 16:23
  • @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. – Noldorin Jun 09 '09 at 16:32
  • Similar question: http://stackoverflow.com/questions/8833225/xslt-1-0-idiom-for-ternary-if – Vadzim Sep 11 '14 at 07:08

7 Answers7

66

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.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • I'll probably won't use this (I'll just upgrade the xpath engine) but this is a great trick and a great tip :) – Yossale Jun 10 '09 at 07:57
  • 1
    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." http://msdn.microsoft.com/en-us/library/aa926043.aspx) – George Birbilis Sep 26 '14 at 02:11
  • 1
    maybe a +1 is needed in those expressions, I had to add such at:
  • – George Birbilis Sep 26 '14 at 09:37
  • the becker method. I was looking for the `?` operator of c#. – LuckyLikey Feb 03 '16 at 08:36
  • @LuckyLikey Well, we can't all get what we want, can we? There is a nicer way to do it in XPath 2.0+. – Tomalak Feb 03 '16 at 10:39
  • @Tomalak Be sure, im glad I found this solution. Thanks for the hint with XPath 2.0+ – LuckyLikey Feb 03 '16 at 10:46
  • 1
    @Tomalak The link is broken, here's another link to a page discussing the method: http://blog.alessio.marchetti.name/post/2011/02/12/the-Oliver-Becker-s-XPath-method – ADJenks Mar 29 '19 at 18:40