bili
bili

Reputation: 610

xslt recursive multiplication

The xml document below represents 3 number, 2, 2 and 2. A node <s> is counted as a number and ended with <zero/>.

    <?xml version="1.0" encoding="UTF-8"?>
    <nat xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="nat.xsd">
      <s>
        <s>
          <zero/>
        </s>
      </s>
      <s>
        <s>
          <zero/>
        </s>
      </s>
      <s>
        <s>
          <zero/>
        </s>
      </s>
    </nat>

I just started learning with xslt and this is one of the exercise for recursion. I could do plus recursively for adding up all numbers but this multiplying more than two number just blows my mind. I have no idea how to do it.

The expected answer for above xml doc is 8s(ignore the format) :

<s><s><s><s><s><s><s><s><zero/></s></s></s></s></s></s></s></s> My idea was this, I can have a template to do multiplication for two number by adding. So for this 2x2x2, I would do the 2nd 2 times the 3rd 2 that returns 4 and finally do 2*4. But call template don't return value in xslt unlike java or scheme so I appreciate any hints/helps.

Update: I got my answer by adding in a print template to Dimitre's answer. Here it is:

    <xsl:template name="print">
    <xsl:param name="pAccum"/>
        <xsl:choose>
            <xsl:when test="$pAccum > 0">
                <s>
                    <xsl:call-template name="print">
                        <xsl:with-param name="pAccum" select="$pAccum - 1"/>
                    </xsl:call-template>
                </s>
             </xsl:when>
             <xsl:otherwise>
                <zero/>
            </xsl:otherwise>    
        </xsl:choose>
</xsl:template>

Upvotes: 1

Views: 241

Answers (2)

Michael Kay
Michael Kay

Reputation: 163458

In XSLT 2.0 I would start with a pair of functions:

<xsl:function name="f:toNumber" as="xs:integer">
  <xsl:param name="z" as="element(zero)"/>
  <xsl:sequence select="count($z/ancestor::*)"/>
</xsl:function>

<xsl:function name="f:fromNumber" as="element()>
  <xsl:param name="z" as="xs:integer"/>
  <xsl:choose>
   <xsl:when test="$z=0"><zero/></xsl:when>
   <xsl:otherwise><s><xsl:sequence select="f:fromNumber($z - 1)"/>
</xsl:function>

That tackles the weirdness of your numeric representation.

Now you just need a function that computes the product of a sequence of numbers:

<xsl:function name="f:product" as="xs:integer">
  <xsl:param name="in" as="xs:integer"/>
  <xsl:sequence select="if (count($in) = 1) then $in[1] else $in * f:product($in[position()>1])"/>
</xsl:function>

and the rest is child's play...

Upvotes: 1

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243529

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:call-template name="product">
   <xsl:with-param name="pArgs" select="//zero"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="product">
  <xsl:param name="pAccum" select="1"/>
  <xsl:param name="pArgs" select="/.."/>

  <xsl:choose>
   <xsl:when test="not($pArgs)">
    <xsl:value-of select="$pAccum"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="product">
     <xsl:with-param name="pAccum"
          select="$pAccum * count($pArgs[1]/ancestor::s)"/>
     <xsl:with-param name="pArgs" select="$pArgs[position() > 1]"/>
    </xsl:call-template>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<nat>
    <s>
        <s>
            <zero/>
        </s>
    </s>
    <s>
        <s>
            <zero/>
        </s>
    </s>
    <s>
        <s>
            <zero/>
        </s>
    </s>
</nat>

produces the wanted, correct result:

8

Explanation:

Primitive recursion with stop condition -- empty argument node-set and an accumulator - parameter for passing the currently accumulated result to the next recursive call.

Upvotes: 1

Related Questions