49

I want to grab a node from my XML file, the node has a prefix such as "latest_", but this may change and I'm keen for my XSLT to be as fluid as possible. Here's the XPath I want to use:

/data/stats/*_cost

This should match latest_cost, newest_cost, anything_cost, is there a way of doing this?

Cheers :-)

Ben Everard
  • 13,556
  • 12
  • 65
  • 96

4 Answers4

70

This is the correct XPath 1.0 expression which selects an element with the last 5 character of name equal to "_cost" in any namespace.

/data/stats/*[substring(name(), string-length(name()) - 4) = '_cost']
David Gardiner
  • 16,541
  • 18
  • 73
  • 116
  • Fair point, I've input this into my application and it does what I need it to without the other disadvantages, see my comment to reqsquare. Thanks. – Ben Everard Nov 17 '10 at 14:14
  • +1 and thanks to @user357812 - this has been very helpful to my similar use case (matching attribute suffixes). How to do this is [here](http://blog.jondh.me.uk/2011/10/simplexml-xpath-selector-attribute-not-having-suffix/) if anyone is interested. – halfer Oct 08 '11 at 12:18
  • I tried this: `` I get the error: ` XPTY0020: Required item type of the context item for the child axis is node(); supplied value has item type xs:boolean` – Si8 Oct 28 '15 at 14:00
  • For completeness, what if the user wants to search for a prefix 'cost_' instead of suffix? How would the above XPath 1.0 expression change? I'm guessing: `/data/stats/*[substring(name(), 4) = 'cost_']` – Dalmazio Jun 16 '18 at 01:08
9

With XPath 1.0 you can use /data/stats/*[substring-after(name(), '_cost') = ''] pattern. That checks if the element's name ends with the _cost suffix.

In XPath 2.0 there is fn:ends-with(str, str) and your corresponding expression will be *[ends-with(name(), '_cost')].

dan-gph
  • 15,521
  • 12
  • 59
  • 77
Alex Nikolaenkov
  • 2,477
  • 20
  • 27
  • Thanks for your answer, contains worked well for me as it checked if the string exists as well. Also my environment doesn't appear to have XPath 2. Cheers anyway :-) – Ben Everard Nov 17 '10 at 10:40
  • 2
    as @ILMV points out, this will select an element without "_cost" in its name. Check my answer. –  Nov 17 '10 at 12:35
  • 2
    [substring-after(name(), '_cost') = ''] This is also true for any name() that does not contain '_cost', -1 – kletnoe Jul 29 '13 at 15:56
8

You could also use contains

e.g

/data/stats[contains(.,'_cost')] 
redsquare
  • 77,122
  • 20
  • 149
  • 156
  • `.` stands for all text content and this expression will match all the `stats` nodes which text content contains `_const` substring. No guarantees about postion and node names. – Alex Nikolaenkov Nov 17 '10 at 10:09
  • 2
    I went with this in the end `[contains(local-name(),'_cost')]`, it doesn't discriminate against position which is a shame, but in the context of its use that doesn't really matter. – Ben Everard Nov 17 '10 at 10:39
  • 3
    This is wrong. This selects a `stats` element cointaining "_cost" in its string value. Check my answer. –  Nov 17 '10 at 12:38
7

The above did not work for me. I had to "slightly" modify that as follows:

/data/stats/*[contains(name(),'_cost')]
David W.
  • 165
  • 2
  • 5