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,652
  • 14
  • 67
  • 96

4 Answers4

71

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,892
  • 20
  • 80
  • 117
  • 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
  • 16,301
  • 12
  • 61
  • 79
Alex Nikolaenkov
  • 2,505
  • 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
  • 78,161
  • 20
  • 151
  • 159
  • `.` 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
8

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

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