tags:

views:

745

answers:

2

For a stylesheet I'm writing (actually for a set of them, each generating a different output format), I have the need to evaluate whether a certain value is present in a list of values. In this case, the value being tested is taken from an element's attribute. The list it is to be tested against comes from the invocation of the stylesheet, and is taken as a top-level <xsl:param> (to be provided on the command-line when I call xsltproc or a Saxon equivalent invocation). For example, the input value may be:

v0_01,v0_10,v0_99

while the attribute values will each look very much like one such value. (Whether a comma is used to separate values, or a space, is not important-- I chose a comma for now because I plan on passing the value via command-line switch to xsltproc, and using a space would require quoting the argument, and I'm lazy-enough to not want to type the extra two characters.)

What I am looking for is something akin to Perl's grep, wherein I can see if the value I currently have is contained in the list. It can be done with sub-string tests, but this would have to be clever so as not to get a false-positive (v0_01 should not match a string that contains v0_011). It seems that the only non-scalar data-type that XSL/XSLT supports is a node-set. I suppose it's possible to convert the list into a set of text nodes, but that seems like over-kill, even compared to making a sub-string test with extra boundaries-checking to prevent false matches.

+1  A: 

Actually, using XPath string functions is the right way to do it. All you have to make sure is that you test for the delimters as well:

contains(concat(',' $list, ','), concat(',', $value, ','))

would returns a boolean value. Or you might use one of these:

substring-before(concat('|,' $list, ',|'), concat(',', $value, ','))

or

substring-after(concat('|,' $list, ',|'), concat(',', $value, ','))

If you get an empty string as the result, $value is not in the list.

EDIT:

As per Dimitre Novatchev's hint, substring-before() (or substring-after()) would also return an empty string if the found string is the first (or the last) in the list. To avoid that, I added something at the start and the end of the list. Still contains() is the recommended way of doing this.

Tomalak
Actually, for my purposes, it looks like the "contains" function will do (using the padding you suggest). What tripped me up, was that I was looking for this functionality within XSLT itself, when I should have been looking in XPath.
rjray
Added the contains() function, that one had escaped me. Thanks. :-)
Tomalak
@Tomalak: The substring-before() expression will evaluate to '' even if the value is contained in the list (when it is the first value). Similarly, the substring-after() expression evaluates to '' when the value is the last in the list. Therefore, only the contains() expression is correct
Dimitre Novatchev
@Tomalak: Therefore, these two expressions must be or-ed together, but this is much longer and probably significantly less efficient than the contains() expression.
Dimitre Novatchev
Okay, so I would change the concat() a bit to account for that.
Tomalak
+1  A: 

In addition to the XPath 1.0 solution provided by Tomalak,

Using XPath 2.0 one can tokenize the list of values:

    exists(tokenize($list, ',')[. = $value])

evaluates to true() if and only if $value is contained in the list of values $list

Dimitre Novatchev