0

I would like to construct a sequence in XSLT 2 where each item of the sequence is individually conditionally included.

Something like this (where the test expressions in reality are not this trivial):

<xsl:variable name="seq">
  <xsl:if test="true()">
    <xsl:value-of select="'foo'"/>
  </xsl:if>
  <xsl:if test="false()">
    <xsl:value-of select="'bar'"/>
  </xsl:if>
  <xsl:if test="true()">
    <xsl:value-of select="'baz'"/>
  </xsl:if>
</xsl:variable>

In this case, I would want the value seq variable to be the sequence ('foo', 'baz').

However, I seem to be missing something, because it's not working quite as expected. What I get returned for the above is a sequence with a single item 'foobar' (i.e. the string concatenation of what was supposed to be all the individual items). If all the conditions are false, I get a sequence with a single item, but that item appears to be the empty string.

Note: Confusingly, I don't believe the <xsl:sequence> item is necessarily related to the construction of a new sequence, and so is not relevant for this use case. It seems to be more about referencing existing items as opposed to copying them.

For context of the specific use case I am trying to solve now: I am trying to generate a sequence of strings representing HTML classes, each of which is included conditional on a different boolean expression. This will result in a list of variable length, between 0 and multiple strings. I am then planning to serialize this list to be a space separated string, as the HTML class attribute expects, like so: string-join($classes, ' ').

Anders Rabo Thorbeck
  • 1,126
  • 3
  • 18
  • 28
  • 1
    It's confusing to speak of "elements" when you are actually wanting to return strings. The word "element" has a rather specific meaning in the XML world... The spec always talks of the items in a sequence (and in 3.0, the members of an array). – Michael Kay Jan 23 '20 at 16:48
  • @MichaelKay A valid point. I am used to thinking of elements in a list, but I agree that this wording is ambiguous and confusing when in the XML context. I have changed the wording to use item instead, as per your suggestion. – Anders Rabo Thorbeck Jan 24 '20 at 07:44

2 Answers2

-1

When experimenting with this, I eventually found the solution. You need to explicitly specify the type of the variable to be a string sequence! Specifically, I had to add the attribute as="xs:string*".

The resulting XSLT becomes:

<xsl:variable name="seq" as="xs:string*">
  <xsl:if test="true()">
    <xsl:value-of select="'foo'"/>
  </xsl:if>
  <xsl:if test="false()">
    <xsl:value-of select="'bar'"/>
  </xsl:if>
  <xsl:if test="true()">
    <xsl:value-of select="'baz'"/>
  </xsl:if>
</xsl:variable>

With this, it worked as expected! I got the sequence ('foo', 'baz') in the case above. When changing all the conditions to false(), I got a sequence with zero elements.

Anders Rabo Thorbeck
  • 1,126
  • 3
  • 18
  • 28
  • 1
    In any case, whether in the context of a variable or not, in XSLT 2 and later, if you want to return the value you select with an XPath expression, I would strongly suggest to use `xsl:sequence select` and not `xsl:value-of select`, as the latter creates text node from the string value of the select expression while the former will return the value, thus if you select an `xs:date`, it will return that date, it you select a node, it will return the node. The `as` is necessary with the variable to not get a fragment node containing the constructed sequence. – Martin Honnen Jan 23 '20 at 15:00
  • There is `` to avoid the need of `as` if you deal with pure XPath. – Martin Honnen Jan 23 '20 at 15:01
  • @MartinHonnen: I agree with your comments but believe `if (true()) then 'foo' else (), if (false()) then 'bar' else (), if (true()) then 'baz' else ()` would be a better `if-then-else` XPath equivalent of OP's logic. I still prefer a [predicate approach](https://stackoverflow.com/a/59880723/290085), however. – kjhughes Jan 23 '20 at 15:57
  • 1
    @kjhughes, you are right, my `select` expression doesn't express the original logic correctly. – Martin Honnen Jan 23 '20 at 16:58
  • @MartinHonnen Ah, I didn't realize there was a difference in the type returned by `xsl:value-of` and `xsl:sequence`, thank you. I will use `xsl:sequence` instead. – Anders Rabo Thorbeck Jan 24 '20 at 08:42
-1

You might use a predicate to filter conditionally instead:

<xsl:variable name="seq" as="xs:string*"
              select="('foo', 'bar', 'baz')[position() = (1, 3)]"/>

Here the first and third elements of the sequence would be selected, for example, but the point is to leverage the concise, expressive power of XPath rather than XSLT statements to state the condition.

kjhughes
  • 106,133
  • 27
  • 181
  • 240
  • Agreed, that is certainly a viable approach. However, for more convoluted, real-world scenarios, I foresee that this would be a less readable approach than the XSLT-based one, due to how: 1) it partitions the values from the conditions, instead of co-locating each value with its corresponding condition, and 2) you have to keep track of indices. – Anders Rabo Thorbeck Jan 23 '20 at 14:47
  • 1) See mapping in many functional languages. 2) Positional test was merely an example; full XPath predicates are possible using this technique. – kjhughes Jan 23 '20 at 15:26