XSLT: Set multiple variables depending on condition - xslt

I want to assign multiple variables depending on one condition environment. I know how to do that for only one variable:
<xsl:variable name="foo">
<xsl:choose>
<xsl:when test="$someCondition">
<xsl:value-of select="3"/>
<xsl:when>
<xsl:otherwise>
<xsl:value-of select="4711"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
But what if I want to assign two variables depending on the same condition $someCondition?
I don't want to write the same xsl:choose statement again, because it is somewhat lengthy and computation intensive in the real example.
The environment in question is libxslt (xslt 1.0) with exslt extensions.
EDIT: What i want is a behaviour similar to
if (condition) {
foo = 1;
bar = "Fred";
}
else if (...) {
foo = 12;
bar = "ASDD";
}
(... more else ifs...)
else {
foo = ...;
bar = "...";
}

What you could is have your main variable return a list of elements; one for each variable you want to set
<xsl:variable name="all">
<xsl:choose>
<xsl:when test="a = 1">
<a>
<xsl:value-of select="1"/>
</a>
<b>
<xsl:value-of select="2"/>
</b>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:value-of select="3"/>
</a>
<b>
<xsl:value-of select="4"/>
</b>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Then, using the exslt function, you can convert this to a 'node set' which can then be used to set your individual variables
<xsl:variable name="a" select="exsl:node-set($all)/a"/>
<xsl:variable name="b" select="exsl:node-set($all)/b"/>
Don't forget you'll need to declare the namepsace for the exslt functions in the XSLT for this to work.

But what if I want to assign two variables depending on the same
condition $someCondition?
I don't want to write the same xsl:choose statement again, because it
is somewhat lengthy and computation intensive in the real example.
Assuming the values of the variables are not nodes, this code doesn't use any extension function to define them:
<xsl:variable name=vAllVars>
<xsl:choose>
<xsl:when test="$someCondition">
<xsl:value-of select="1|Fred"/>
<xsl:when>
<xsl:when test="$someCondition2">
<xsl:value-of select="12|ASDD"/>
<xsl:when>
<xsl:otherwise>
<xsl:value-of select="4711|PQR" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="foo" select="substring-before($vAllVars, '|')"/>
<xsl:variable name="bar" select="substring-after($vAllVars, '|')"/>

Related

idiomatic alternative to choose -> test -> value-of (XSLT 1.0)

In the work I do I seem to see a lot of code liek this..
<xsl:choose>
<xsl:when test="long_xpath_to_optional/#value1">
<xsl:value-of select="long_xpath_to_optional/#value"/>
</xsl:when>
<xsl:when test="another_long_xpath_to_optional/#value">
<xsl:value-of select="another_long_xpath_to_optional/#value"/>
</xsl:when>
<etc>
</etc>
<otherwise>
<xsl:value-of select="default_long_xpath_to_value"/>
</otherwise>
</xsl:choose>
its very long and very repetitive.
When I'm were working in some other (psuedo) language I would go
let values = concat(list(long_xpath_to_optional_value),list(another_long_xpath_to_optional_value))
let answer = tryhead(values,default_long_xpath_to_value)
i.e. create a list of values in priority order, and then take the head.
I only evaluate each path once
how would you do something similar in XSLT 1.0 (we can use node-sets).
I was wondering if you can create a node-set somehow
You can - but it's not going to be any shorter:
<xsl:variable name="values">
<xsl:apply-templates select="long_xpath_to_optional/#value" mode="values"/>
<xsl:apply-templates select="another_long_xpath_to_optional/#value" mode="values"/>
<xsl:apply-templates select="default_long_xpath_to_value/#value" mode="values"/>
</xsl:variable>
<xsl:value-of select="exsl:node-set($values)/value[1]" xmlns:exsl="http://exslt.org/common"/>
and then:
<xsl:template match="#value" mode="values">
<value>
<xsl:value-of select="."/>
</value>
</xsl:template>
But at least the repetition is eliminated.
Alternatively, you could do:
<xsl:template match="#value" mode="values">
<xsl:value-of select="."/>
<xsl:text>|</xsl:text>
</xsl:template>
and then:
<xsl:value-of select="substring-before($values, '|')"/>
To use variables you write
<xsl:variable name="value1" select="long_xpath_to_optional/#value1"/>
<xsl:variable name="value2" select="another_long_xpath_to_optional/#value"/>
<xsl:variable name="value3" select="default_long_xpath_to_value"/>
and then in XPath 2 or 3 all you would need is ($value1, $value2, $value3)[1] or head(($value1, $value2, $value3)) but in XSLT 1 with XPath 1 all you can write as a single expression is ($value1 | $value2 | $value3)[1] which sorts in document order so unless the document order is the same as your test order this wouldn't work to check the values; rather you would need to maintain the
<xsl:choose>
<xsl:when test="$value1">
<xsl:value-of select="$value1"/>
</xsl:when>
<xsl:when test="$value2">
<xsl:value-of select="$value2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value3"/>
</xsl:otherwise>
</xsl:choose>
Of course in XPath 2 you wouldn't really need the variables and could use (long_xpath_to_optional/#value1, another_long_xpath_to_optional/#value, default_long_xpath_to_value)[1] as well directly.

Populate a variable with a subtree

in a version="2.0" stylesheet:
the following code produces the correct output
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:value-of select="/t:Flow/t:FHeader/t:Producer/t:Repository" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj"/>
but this one does not
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:value-of select="/t:Flow/t:FHeader/t:Producer" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj/t:Repository"/>
How can I get the second code to run as expected ?
If needed, is there a solution in v3 ?
this code does not run either
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:copy-of select="/t:Flow/t:FHeader/t:Producer" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj/t:Repository"/>
relevant xml input
<Flow>
<FHeader>
<Producer>
<Repository>tests.com</Repository>
</Producer>
</FHeader>
</Flow>
You can simply select <xsl:variable name="obj" select="/t:Flow/t:FHeader/t:Producer/t:Repository[current()/t:ReferencedObjectType='Asset']"/>. Or, as Tim already commented, use xsl:copy-of, also taking into account that you then later on need e.g. $obj/t:Producer/t:Repository to select the right level.
Or learn about the as attribute and use e.g. <xsl:variable name="obj" as="element()*">...<xsl:copy-of select="/t:Flow/t:FHeader/t:Producer"/> ...</xsl:variable>, then you later on can use e.g. $obj/t:Repository.
There is also xsl:sequence to select input nodes instead of copying them, in particular with xsl:variable if you use the as attribute. This might consume less memory.
Furthermore XPath 2 and later have if (condition-expression) then expression else expression conditional expressions at the expression level so you might not need XSLT with xsl:choose/xsl:when but could use the <xsl:variable name="obj" select="if (t:ReferencedObjectType='Asset']) then /t:Flow/t:FHeader/t:Producer else if (...) then ... else ()"/>, that way you would select e.g. an input t:Producer element anyway and if you use the variable you can directly select the t:Repository child.

Choose with for-each inside?

I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
If xsl were a procedural language where variables could be reassigned, I'd use something like this:
<xsl:variable name="copyAttrib" select="true()">
<xsl:for-each select="tokenize($ignoreAttributes,',')">
<xsl:if test="compare(., name()) != 0">
<xsl:variable name="copyAttrib" select="false()"/>
</xsl:if>
</xsl:for-each>
Unfortunately, I can't do that, because xsl is functional (so says this other answer). So variables can only be assigned once.
I think the solution would look something like:
<vsl:variable name="copyAttrib">
<xsl:choose>
<xsl:when>
<xsl:for-each select="tokenize($ignoreAttributes, ',')">
<xsl:if test="compare(., name()) != 0"/>
</xsl:for-each>
<xsl:otherwise>
<xsl:value-of select="false()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Obviously not exactly that (otherwise I wouldn't be asking.)
I know that I could bypass the tokenize and for-each loop by just using replaces on ignoreAttributes and changing all the , to | and then using matches, but I'd like to avoid that if possible because then I need to deal with the possibility that ignoreAttributes (which the user provides) might contain some special characters that will change the regex pattern and escape them all.
I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
That sounds to me like
<xsl:variable name="copyAttrib" as="xs:boolean"
select="tokenize($parameterignoreAttributes, ',') = name()"/>
You say:
Unfortunately, I can't do that, because xsl is functional
when what you mean is: "Fortunately, I don't need to do that, because XSLT is functional".
An XSLT-1.0 way of doing this is by using a recursive, named template:
<xsl:template name="copyAttrib">
<xsl:param name="attribs" />
<xsl:choose>
<xsl:when test="normalize-space(substring-before($attribs,',')) = normalize-space(name(.))">
<xsl:value-of select="'true'" />
</xsl:when>
<xsl:when test="normalize-space($attribs) = ''">
<xsl:value-of select="'false'" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="substring-after($attribs,',')" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Apply this template onto the current, the selected, node and wrap it in a <xsl:variable>:
<xsl:variable name="copyAttribResult">
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="'a,b,c,...commaSeparatedValues...'" />
</xsl:call-template>
</xsl:variable>
to get either true or false as a result.

How to store the current path in xsl?

I would like to store the path of the current node so I can reused it in an expression in XSLT. Is it possible?
<!-- . into $path? -->
<xsl:value-of select="$path" />
Hi, I would like to store the path of
the current node so I can reused it in
an expression in XSLT. Is it possible?
It is possible for any given node to construct an XPath expression that, when evaluated, selects exactly this node. In fact more than one XPath expression exists that selects the same node.
See this answer for the exact XSLT code that constructs such an XPath expression.
The problem is that this XPath expression cannot be evaluated during the same transformation in XSLT 1.0 or XSLT 2.0, unless the EXSLT extension function dyn:evaluate is used (and very few XSLT 1.0 processors implement dyn:evaluate() ).
What you want can be achieved in an easier way in XSLT using the <xsl:variable> instruction:
<xsl:variable name="theNode" select="."/>
This variable can be referenced anywhere in its scope as $theNode, and can be passed as parameter when applying or calling templates.
No, this is not possible with vanilla XSLT 1.0. There is no easy way to retrieve an XPath expression string for a given node, and there is definitely no way to evaluate a string that looks like XPath as if it was XPath.
There are extensions that support dynamic evaluation of XPath expressions, but these are not compatible with every XSLT processor.
In any case, if you provide more detail around what you are actually trying to do, there might be another way to do it.
As #Dimitre and #Tomalak have point out, I don't think it has some value in the same transformation to obtain a string representing an XPath expression for a given node, and then select the node "parsing" such string. I could see some value in performing those operations in different transformations.
Besides that, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:for-each select=".|//node()|//#*">
<xsl:variable name="vPath">
<xsl:apply-templates select="." mode="getPath"/>
</xsl:variable>
<xsl:value-of select="concat($vPath,'
')"/>
<xsl:call-template name="select">
<xsl:with-param name="pPath" select="$vPath"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="/|node()|#*" mode="getPath" name="getPath">
<xsl:apply-templates select="parent::*" mode="getPath"/>
<xsl:text>/</xsl:text>
<xsl:choose>
<xsl:when test="self::*">
<xsl:value-of select="concat(name(),'[',
count(preceding-sibling::*
[name() =
name(current())]) + 1,
']')"/>
</xsl:when>
<xsl:when test="count(.|../#*)=count(../#*)">
<xsl:value-of select="concat('#',name())"/>
</xsl:when>
<xsl:when test="self::text()">
<xsl:value-of
select="concat('text()[',
count(preceding-sibling::text()) + 1,
']')"/>
</xsl:when>
<xsl:when test="self::comment()">
<xsl:value-of
select="concat('comment()[',
count(preceding-sibling::comment()) + 1,
']')"/>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<xsl:value-of
select="concat('processing-instruction()[',
count(preceding-sibling::
processing-instruction()) + 1,
']')"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="select">
<xsl:param name="pPath"/>
<xsl:param name="pContext" select="/"/>
<xsl:param name="pInstruction" select="'value-of'"/>
<xsl:variable name="vPosition"
select="number(
substring-before(
substring-after($pPath,
'['),
']'))"/>
<xsl:variable name="vTest"
select="substring-before(
substring-after($pPath,
'/'),
'[')"/>
<xsl:variable name="vPath" select="substring-after($pPath,']')"/>
<xsl:choose>
<xsl:when test="$vPath">
<xsl:call-template name="select">
<xsl:with-param name="pPath" select="$vPath"/>
<xsl:with-param name="pContext"
select="$pContext/*[name()=$vTest]
[$vPosition]"/>
<xsl:with-param name="pInstruction"
select="$pInstruction"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vContext"
select="$pContext/node()
[self::*[name()=$vTest]|
self::comment()[$vTest='comment()']|
self::text()[$vTest='text()']|
self::processing-instruction()
[$vTest =
'processing-instruction()']]
[$vPosition]|
$pContext[$pPath='/']|
$pContext/#*[name() =
substring($pPath,3)]
[not($vTest)]"/>
<xsl:choose>
<xsl:when test="$pInstruction='value-of'">
<xsl:value-of select="$vContext"/>
</xsl:when>
<xsl:when test="$pInstruction='copy-of'">
<xsl:copy-of select="$vContext"/>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<?somePI pseudoAttributes?>
<root>
<!-- This is a comment -->
<node attribute="Value">text</node>
</root>
Output:
/
text
/processing-instruction()[1]
pseudoAttributes
/root[1]
text
/root[1]/comment()[1]
This is a comment
/root[1]/node[1]
text
/root[1]/node[1]/#attribute
Value
/root[1]/node[1]/text()[1]
text

Choose statement messing up my variable?

I've got a choose statement that should be setting my variable but for some reason the code, though it works elsewhere, does not work in this instance.
Here's my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="url-show"/>
<xsl:param name="url-min"/>
<xsl:param name="url-max"/>
<xsl:template match="data">
<xsl:variable name="show" select="$url-show"/>
<xsl:choose>
<!-- With Min/Max -->
<xsl:when test="$url-min != '' and $url-max != ''">
<xsl:variable name="total" select="timeshare-search-results/pagination/#total-entries"/>
</xsl:when>
<!-- Without Min/Max -->
<xsl:otherwise>
<xsl:variable name="total" select="timeshare-listings/pagination/#total-entries"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="default" select="$increment"/>
<xsl:choose>
<!-- With Show Variable -->
<xsl:when test="$show != ''">
<xsl:if test="$show < $total or $show = $total">
<!-- stuff -->
</xsl:if>
</xsl:when>
<!-- Without Show Variable -->
<xsl:otherwise>
<xsl:if test="$default < $total or $default = $total">
<!-- stuff -->
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I trimmed out the non-essential bits. The funny thing is that when I just have one variable or the other they work just fine. The problem is, they do not show the same data thus I need to choose one or the other based on two URL parameters.
Thing is, I do this elsewhere on this very same page—though I trimmed it out for the example—and it works perfectly fine!
Why isn't it working in this instance? Is there a way I can get around it?
Variables are immutable in XSLT, and they are scoped and visible from the point of instantiation forward to the end of their parent element.
<xsl:when ...>
<xsl:variable ...>
</xsl:when>
scopes the variable to just the <xsl:when> block. If there is a variable with that name defined before the <xsl:choose> block, it will appear to be 'restored' when the block is past, since it is no longer shadowed.
The proper way to set a variable is to wrap the <xsl:variable> definition around the <xsl:choose> block, like this:
<xsl:variable name="total">
<xsl:choose>
<!-- With Min/Max -->
<xsl:when test="$url-min != '' and $url-max != ''">
<xsl:value-of select="timeshare-search-results/pagination/#total-entries"/>
</xsl:when>
<!-- Without Min/Max -->
<xsl:otherwise>
<xsl:value-of select="timeshare-listings/pagination/#total-entries"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Now, $total will be scoped to the parent of the <xsl:variable> block, which is the <xsl:template match="data"> block.