Understaning recursion in xslt - xslt

I am trying to understand recorsion in xslt. Can anybody explain what's happening in this code.
<xsl:template name="factorial">
<xsl:param name="number" select="1"/>
<xsl:choose>
<xsl:when test="$number <= 1">
<xsl:value-of select="1"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="recursive_result">
<xsl:call-template name="factorial">
<xsl:with-param name="number" select="$number - 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$number * $recursive_result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I can't understand why we wrap factorial template with <xsl:variable name="recursive_result">.
If there is more clear example is available, please guide me to that. I am lack of knowledge in recursion.

The call-template element is wrapped with the variable element in order to assign the result of calling it to the variable recursive_result.
This is done so that it can then be multiplied by number on the following line, to produce the final result.

You can't declare global variables in XSLT that are changeable from other parts of the script. If you need a result from a template call or a recursion is the only way to "print out" the generated result in a variable.
The "print out" is done with the <xsl:value-of ... statement.

In XSLT, we are using recursion instead of Looping. Recursion is nothing but a particular type of function that calls itself as many times when required to find the final solution. So,
input the number variable as '1'
The given value if it is less than 1 then it simply print the value of $number
otherwise, it is move to call-template as input for the variable number with help of with-param
here, it is calling the same templates again and pass value to same variable named as number
Then the result value will be assigned to the variable recursive_result
Hope would be understand.

Related

xsl:param used without being assigned a value

I am looking at this xslt template:
<xsl:template match="row">
<xsl:param name="spans"/>
<xsl:param name="browserows"/>
<xsl:choose>
<xsl:when test="contains($spans, '0')">
<xsl:call-template name="normal-row">
<xsl:with-param name="spans" select="$spans"/>
<xsl:with-param name="browserows" select="$browserows"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
I am trying to understand where the value of $spans is coming from at the test statement on line 6, but it looks like the value was never assigned.
I cannot find spans as a global param anywhere.
Am I missing something?
It would come from the calling code that executed the <xsl:apply-templates> that matched against that row - because the <xsl:template> declares that parameter in <xsl:param name="spans"/>. If you didn't specify a value via <xsl:with-param>, then it is presumably nil.

Accessing a variable outside the for loop in xsl

I am setting a variable as shown below..
<xsl:variable name="FoundFloating"> <xsl:value-of select="'no'" />
</xsl:variable>
Now I am doing some processing as shown below ..
<xsl:if test="$abcid=$def_id">
<xsl:for-each "$abcd">
<xsl:variable name="abcRate"> <xsl:value-of select="./def_Period/"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$abcdf !=$abcRate">
<xsl:variable name="$FoundFloating"> <xsl:value-of select="yes" />
</xsl:variable>
</xsl:when>
</xsl:choose>
</xsl:for-each>
Now after this xsl for I am evaluating as shown below.. But my query is that whether foundfloating variable is accessible as the for loop is already ended..
<xsl:choose>
<xsl:when test="$FoundFloating='yes'"> <xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BBBA'" />
</xsl:otherwise>
</xsl:choose>
Now after this xsl for I am evaluating as shown below.. But my query is that whether foundfloating variable is accessible as the for loop is already ended..
Please advise on this as I have updated the post
The first thing to note is that variables are immutable in XSLT. This means once declared, they cannot be changed. What is actually happening in your XSLT sample is you are declaring a whole new variable, with the same name.
The FoundFloating variable you are declaring within the for loop will not be accessible outside the for loop, as it will only be local in scope. In fact, it will only be accessible inside the xsl:when statement it is defined in. It is a different variable to the global one you defined, and only exists in the loop.
You don't really need the loop here. You can combine the condition in the xsl:for-each and the condition in the xsl:when into a single variable declaration.
<xsl:if test="$abcid=$def_id">
<xsl:variable name="FoundFloating">
<xsl:if test="$abcd[def_Period != $abcdf]">yes</xsl:if>
</xsl:variable>
<xsl:choose>
<xsl:when test="$FoundFloating='yes'">
(This replaces the xsl:for-each statement entirely)
In fact, this can be simplified even more, by simply setting FoundFloating to the node itself (if there is one), rather than "yes"
<xsl:if test="$abcid=$def_id">
<xsl:variable name="FoundFloating" select="$abcd[def_Period != $abcdf]" />
<xsl:choose>
<xsl:when test="$FoundFloating">
This works because the essence of the test is whether a certain node exists matching the condition. Rather than setting a variable to "Yes" or "No" if it exists or not, the variable is set to the node itself. Then, if it does the exist the statement <xsl:when test="$FoundFloating"> returns true, but false if it doesn't.
So, you don't need the xsl:for-each loop, and you only need to declare the FoundFloating variable once.

Unable to cast from XRTreeFrag into XNodeSet

I have the following test code... I am trying to pass a node-set as a param. After many hours, i finally was able to pass it to my template.
How I pass my node-set to the template:
<xsl:call-template name="listing">
<xsl:with-param name="customData">
<xsl:apply-templates select="exslt:node-set($data)"/>
</xsl:with-param>
</xsl:call-template>
How my template receives it:
<xsl:template name="listing">
<xsl:param name="customData" select="/.."/>
<xsl:variable name="data">
<xsl:choose>
<xsl:when test="not($customData)">
<xsl:value-of select="/data"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$customData"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<textarea><xsl:copy-of select="$data"></xsl:copy-of></textarea>
</xsl:call-template>
If I set the parameters with a one liner, then it would not complain... example:
<xsl:variable name="data" select="$customData"/>
But as soon as I try to set it like this, it breaks:
<xsl:variable name="data">
<xsl:value-of select="$customData"/>
</xsl:variable>
Getting this error message:
org.apache.xpath.objects.XRTreeFrag cannot be cast to org.apache.xpath.objects.XNodeSet
I was only been able to find another thread dated back in 2000, talk about this similar issue... I need to re-nodeset it back using something like node-set($customData)/* but I tried that, and it was a no go.
EDIT:
OK, I can confirm that I successfully passed the node-set inside my template. But I'm still unable to copy it over to my variable... It kept saying that it is still a RTF.
<xsl:template name="listing">
<xsl:param name="customData" as="node-set"/>
<!--<xsl:variable name="data" select="/data"/>-->
<xsl:variable name="data">
<xsl:choose>
<xsl:when test="count($customData) != 0">
<xsl:copy-of select="$customData"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="/data"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<textarea><xsl:value-of select="$customData/record[1]"/></textarea>
<textarea><xsl:value-of select="/data/record[1]"/></textarea>
<textarea><xsl:value-of select="$data/record[1]"/></textarea>
</xsl:template>
The above test, shows that I can access $customData and the original /data without any problem, they both show the record... but $data is messed up. So that means the copy from $customData to $data wasn't working...
I tried the following ways, none of them work:
<xsl:copy-of select="$customData"/>
<xsl:value-of select="$customData"/>
<xsl:apply-templates select="exslt:node-set($customData)"/>
<xsl:apply-templates select="exslt:node-set($customData)/data"/>
Any idea...?
This error message comes from Xalan, which is an XSLT 1.0 processor. If you are using Xalan, then you are probably using Java, which means there is really no reason at all not to move to XSLT 2.0 in the form of Saxon. You will find that XSLT 2.0 removes many of the restrictions of XSLT 1.0, of which this is one of the most irritating.
If there's a good reason why you can't move forward to XSLT 2.0 (and it's hard to think of one), there's a workaround in the form of the exslt:node-set() function, which converts a result-tree fragment (that is, a variable defined using child instructions) into a document node.
Got it working, basically rather than using apply-template, i need to pass the RTF as a parameter to the template. That is the only way I got it to work.
<xsl:with-param name="data" select="exslt:node-set($customData)"/>
Using this method, I was able to MODIFY data in XSL level. This is really cool, I basically manipulate the data I want, then i reconstruct the root /, and then I pass my customData to my template function.
So rather than reading data off the root, I read my own modified data (constructed inside XSL).
Use of exslt:node-set does indeed suppress the error message org.apache.xpath.objects.XRTreeFrag cannot be cast to org.apache.xpath.objects.XNodeSet
However, the node-set that is created for some reason cannot be used in subsequent XPath expressions; at least it doesn't seem to work with Xalan 2.6.0 / XSLT 1.0 which is the version many people are forced to use for one reason or another.
There is a simple solution: instead of setting the variable to a node-set, set it to the XPath expression instead. Then you can use the dyn:evaluate EXSLT function to evaluate the XPath expression held by the variable.
Your code would look something like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="dyn"
exclude-result-prefixes="dyn">
..
<xsl:variable name="data">
<xsl:choose>
<xsl:when test="count(.) != 0">
<xsl:text>.</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>/data</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<textarea>
<xsl:value-of select="dyn:evaluate($data)/record[1]"/>
</textarea>

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)

XSl:Variable - Condition to check whether value exist

Using XSLT 1.0, how do I check whether the value in the variable exists or not?
I am assigning the value to the variable initially from my XML data and then need to check whether it exits or not:
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./RootTag/*[1])"/>
</xsl:variable>
<xsl:if test="string($DOC_TYPE) = ''">
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./*[1])"/>
</xsl:variable>
</xsl:if>
The above is not working as expected. What I need is if <RootTag> exists in my data then the variable should contain the child node below the <RootTag>. If <RootTag> does not exist then the DOC_TYPE should be the first Tag in my XML data.
Thanks for your response.
You can't re-assign variables in XSLT. Variables a immutable, you can't change their value. Ever.
This means you must decide within the variable declaration what value it is going to have:
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="RootTag">
<xsl:value-of select="name(RootTag/*[1])" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="name(*[1])" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
A few other notes:
this: './RootTag' is redundant. Every XPath you don't start with a slash is relative by default, so saying 'RootTag' is enough
this: <xsl:value-of select="name(*[1])"/> already results in a string (names are strings by definition), so there is no need to do <xsl:if test="string($DOC_TYPE) = ''"> , a simple <xsl:if test="$DOC_TYPE = ''"> suffices
to check if a node exists simply select it via XPath in a test="..." expression - any non-empty node-set evaluates to true
XSLT has strict scoping rules. Variables are valid within their parent elements only. Your second variable (the one within the <xsl:if>) would go out of scope immediately(meaning right at the </xsl:if>).
Try this
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="/RootTag"><xsl:value-of select="name(/RootTag/*[1])"></xsl:value-of></xsl:when>
<xsl:otherwise><xsl:value-of select="name(/*[1])"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
It only exists if you have assigned it. There's no reason to test it for existence.
See also here