Reputation: 21490
I have work out the example for factorial of given number in xml by using xsl.
My XML code is,fact.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="fact.xsl"?>
<numbers>
<number> 5
</number>
</numbers>
and my XSL code is,fact.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates>
</xsl:apply-templates>
</xsl:template>
<xsl:template name="factorial">
<xsl:param name="number" select="$number"/>
<xsl:param name="result" select="1"/>
<xsl:if test="$number > 1">
<xsl:call-template name="factorial">
<xsl:with-param name="number" select="$number - 1"/>
<xsl:with-param name="result">
<xsl:value-of select="$number * $result"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:if test="$number = 1">
<xsl:value-of select= "$result"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
But, When I run this fact.xml, the factorial output shows only 5
instead of 120
.
So what is the error in the code, either in XML or XSL?
Thanks in advance.
Upvotes: 2
Views: 516
Reputation: 243529
There are/were a number of errors in the provided code and a compliant XSLT processor should raise them and not produce any result. Some/all of these errors may no longer be visible, because the OP edited his code in the meantime...).
<xsl:apply-templates match="factorial">
The <xsl:apply-templates>
instruction doesn't have a match attribute -- an error is raised here.
<xsl:param name="number" select="$number"/>
This raises an error, because $number
isn't defined at this point and cannot be used in specifying a default value for the parameter $number
.
A more important error is that the named template factorial
is never called.
Here is the corrected code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="number[. > 1]" name="factorial">
<xsl:param name="pNumber" select="."/>
<xsl:param name="pResult" select="1"/>
<xsl:choose>
<xsl:when test="$pNumber > 1">
<xsl:call-template name="factorial">
<xsl:with-param name="pNumber"
select="$pNumber -1"/>
<xsl:with-param name="pResult"
select="$pNumber * $pResult"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pNumber = 1">
<xsl:value-of select= "$pResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">Error</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when it is applied in the provided XML document:
<numbers>
<number> 5 </number>
</numbers>
the wanted, correct result is produced:
120
Upvotes: 1
Reputation: 52331
Your first template matches numbers
node, but does nothing.
Your second template doesn't match anything, and is never called by any other template. In other words, it is never executed.
That's why the output is 5.
Let's see what happens. In XSLT, templates can be called in two ways.
/numbers
node; in your revision, it matches any root element (which is, in this case, the same thing).call-template
. That's what you are doing in your second template, calling itself recursively.The problem is that the second template is called only from... guess... the second template itself. So since it's never called, it will never execute.
Now, we will add a call to this template from the outside, and more precisely from the first template.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="numbers">
<output>
<!-- Here's a missing call to the second template -->
<xsl:call-template name="factorial">
<xsl:with-param name="number" select="number"/>
<xsl:with-param name="result" select="1"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="factorial">
<xsl:param name="number" />
<xsl:param name="result" />
<intermediary number="{$number}">
<xsl:value-of select="$result"/>
</intermediary>
<xsl:if test="$number > 1">
<xsl:call-template name="factorial">
<xsl:with-param name="number" select="$number - 1"/>
<xsl:with-param name="result" select="$number * $result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$number = 1">
<result>
<xsl:value-of select="$result"/>
</result>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
We also want to be sure that the template is executing properly, so we output the intermediary states.
<?xml version="1.0" encoding="utf-8"?>
<output>
<intermediary number="
 5
 ">1</intermediary>
<intermediary number="4">5</intermediary>
<intermediary number="3">20</intermediary>
<intermediary number="2">60</intermediary>
<intermediary number="1">120</intermediary>
<result>120</result>
</output>
The final result is correct, but the first intermediary state is still weird. It shows that the number 5
was treated as a string, with leading and trailing whitespace.
To get rid of that, the number
element innerXML must be converted to a real number. To do that, you can use the number()
method:
<xsl:call-template name="factorial">
<xsl:with-param name="number" select="number(number)"/>
<xsl:with-param name="result" select="1"/>
</xsl:call-template>
Now, the output is correct from the start to the end, and intermediary output can be removed.
Upvotes: 1