Venkat
Venkat

Reputation: 21490

Error in xslt or xml while running XSL code for factorial

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 &gt; 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

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

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

Arseni Mourzenko
Arseni Mourzenko

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.

  • First, they can match an element of an XML input. Your first template is doing exactly this thing: in your original question, it matched /numbers node; in your revision, it matches any root element (which is, in this case, the same thing).
  • Second, they can be called from a template through 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 &gt; 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="&#xA;    5&#xA;  ">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

Related Questions