Choose with for-each inside? - xslt

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.

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.

Working on Variable in XSL loop to hold dynamic value

Working on the xsl and i am looking for variable inside a loop to reset on new record.
Fetching records from oracle table
<xsl:variable name="curr_temp_emp_no" select="'##'"/>
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != $curr_temp_emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
I am trying to compare variable "curr_temp_emp_no" if new value only it prints "emp_no - variable_desc" otherwise(if same emp_no) then print only "variable_desc".
I understood from google that Variables in XSLT are immutable, once we assign them a value, we can't change them.
Refence: Can you simulate a boolean flag in XSLT?
Can anyone please help me over here in writting this logic.
It looks like you want to compare the last two values. I could achieve this by:
<xsl:template match="/">
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="/data/test/loof/super_incompleted"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="helperTemplate">
<xsl:param name="nodes"/>
<xsl:param name="oldVal" select="''"/>
<xsl:if test="$nodes">
<xsl:variable name="curr_temp_emp_no" select="$nodes[1]/emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no != $oldVal">
<xsl:value-of select="$curr_temp_emp_no"/>
<fo:inline> - </fo:inline>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="oldVal" select="$nodes[1]/emp_no"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
If you want to have the comparison on all values(if there was no previous element with that content), you can sort them first and use the same code.
You're asking us to reverse engineer your requirements from non-working code, which is always a challenge, but you seem to be carrying over ideas from procedural programming languages which gives us some clues as to what you imagine you want this code to do.
Basically it looks like a "group-adjacent" problem. In XSLT 2.0 you would do
<xsl:for-each-group select="/data/test/loof/super_incompleted"
group-adjacent="emp_no">
...
</xsl:for-each-group>
If you're stuck with XSLT 1.0 then the usual approach is to process the sequence of nodes with a recursive named template rather than a for-each instruction: you pass the list of nodes as a parameter to the template, together with the current employee id, and then in the template you process the first node in the list, and call yourself recursively to process the remainder of the list.
#ChristianMosz has expanded this suggestion into working code.
Thanks for your reply.
I have used the "preceding-sibling::" which solved my problem. Now the code is something like this.
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != preceding-sibling::super_incompleted[1]/emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
Earlier the table was like this.
1- ABC,1- DFE,1- GFH
2- DFG,2- FGH,2- SDS,2- RTY
Now table looks like this.
1- ABC,DFE,GFH
2- DFG,FGH,SDS

Pass string into template parameter which expects node?

So,
I have an XSLT template which expects a node set as a parameter and uses this as display text. However, sometimes this node is empty in the XML and I want to pass default display text instead of the display text not showing up instead:
Works:
<xsl:call-template name="myTemplate">
<xsl:with-param name="parm1" select="//element">
</xsl:call-template>
Doesn't work:
<xsl:variable name="dispText">
<xsl:choose>
<xsl:when test="string-length(//element) = 0">
<xsl:value-of select="'Default Text'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//element" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:call-template name="myTemplate">
<xsl:with-param name="parm1" select="$dispText">
</xsl:call-template>
Any ideas as to how I could accomplish this? I've tried all sorts of things with no luck :(
It seems like all I need to do is create a new node with the display text I want, but I don't know if that is even possible?
Thanks
Implement the default handling in the template, because that's where it belongs. The calling side should be consistent and not have side-effects on the template behavior (i.e. you should not be able to "forget" passing in the default value).
<xsl:template name="myTemplate">
<xsl:param name="parm1" /><!-- node set expected! -->
<!-- actual value or default -->
<xsl:variable name="value1">
<xsl:choose>
<xsl:when test="not($parm1 = '')">
<xsl:value-of select="$parm1" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$default1" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- work with $value1 from this point on -->
</xsl:template>
I'm guessing //element is a nodeset and using string-length() on it might not be valid. Try converting it to a string() first?

XSLT: Apply templates with conditional parameters?

I'd like to apply a template with different parameters based on the result of a conditional. Something like this:
<xsl:choose>
<xsl:when test="#attribute1">
<xsl:apply-templates select='.' mode='custom_template'>
<xsl:with-param name="attribute_name" tunnel="yes">Attribute no. 1</xsl:with-param>
<xsl:with-param name="attribute_value" tunnel="yes"><xsl:value-of select="#attribute1"/></xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:when test="#attribute2">
<xsl:apply-templates select='.' mode='custom_template'>
<xsl:with-param name="attribute_name" tunnel="yes">Attribute no. 2</xsl:with-param>
<xsl:with-param name="attribute_value" tunnel="yes"><xsl:value-of select="#attribute1"/></xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select='.' mode='custom_template'>
<xsl:with-param name="attribute_name" tunnel="yes">Error</xsl:with-param>
<xsl:with-param name="attribute_value" tunnel="yes">No matching attribute </xsl:with-param>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
First of all, I suspect that this could be resolved in a much, much better way. (I'm entirely new to XSLT, so please suggest improvements and forgive the bloated code.)
Now for the question: how could I've set the parameters based on this conditional, and still used them in an xsl:apply-templates? I've tried to wrap the entire xsl:choose with a xsl:apply-templates start-/end-tag, but that's apparently not legal. Any clues?
An alternate method would be to put the xsl:choose statements within the xsl:param elements
<xsl:apply-templates select="." mode="custom_template">
<xsl:with-param name="attribute_name" tunnel="yes">
<xsl:choose>
<xsl:when test="#attribute1">Attribute no. 1</xsl:when>
<xsl:when test="#attribute2">Attribute no. 2</xsl:when>
<xsl:otherwise>Error</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
<xsl:with-param name="attribute_value" tunnel="yes">
<xsl:choose>
<xsl:when test="#attribute1"><xsl:value-of select="#attribute1"/></xsl:when>
<xsl:when test="#attribute2"><xsl:value-of select="#attribute1"/></xsl:when>
<xsl:otherwise>No matching attribute </xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:apply-templates>
Nothing wrong with your method, but you can also append your conditional into xsl:template match attribute. This will lead to just one xsl:apply-templates, but several xsl:template elements
You can get rid of all that logic and the modes by extracting your conditions into predicates. You don't say what the name of the element you're dealing with is, but assuming it's called foo then something like this should suffice:
<xsl:template match="foo[#attribute1]">
<!--
do stuff for the case when attribute1 is present
(and does not evaluate to false)
-->
</xsl:template>
<xsl:template match="foo[#attribute2]">
<!--
do stuff for the case when attribute2 is present
(and does not evaluate to false)
-->
</xsl:template>
<xsl:template match="foo">
<!--
do stuff for the general case
(when neither attribute1 nor attribute 2 are present)
-->
</xsl:template>