DashaLuna
DashaLuna

Reputation: 686

XSLT 1.0 recursion

I'm stuck with recursion, was wondering if anyone can help me out with it.

I have <Receipts> and <Deposits> elements, that are not verbose, i.e. that a <Receipts> element doesn't have an attribute to show what <Deposit> it is towards. I need to figure out <Deposits> "still amount due" and when a last receipt towards it was paid if any.

I'm trying to do it with the following code:

The idea was to take 1st deposit and see if there are receipts. If the deposit isn't fully paid and there are more receipts - call that recorsive function with all the same params except now count in following receipt.

If there aren't any more receipts or deposit is payed - process it correctly (add required attributes). Otherwise proceed with 2nd deposit. And so on.

However, the XSLT crashes with error message that "a processor stack has overflowed - possible cause is infinite template recursion"

I would really appreciate any help/teps... I'm not that great with recursion and can't understand why mine here doesn't work.

Thanks! :)

<!-- Accumulate all the deposits with @DueAmount attribute -->
<xsl:variable name="depositsClassified">
    <xsl:call-template name="classifyDeposits">
        <!-- a node-list of all Deposits elements ordered by DueDate Acs -->
        <xsl:with-param name="depositsAll" select="$deposits"/> 
        <xsl:with-param name="depositPrevAmount" select="'0'"/>
        <!-- a node-list of all Receipts elements ordered by ReceivedDate Acs -->
        <xsl:with-param name="receiptsAll" select="$receiptsAsc"/>
        <xsl:with-param name="receiptCount" select="'1'"/>
    </xsl:call-template>
</xsl:variable>


<xsl:template name="classifyDeposits">
    <xsl:param name="depositsAll"/>
    <xsl:param name="depositPrevAmount" select="'0'"/>
    <xsl:param name="receiptsAll"/>
    <xsl:param name="receiptCount"/>


    <xsl:if test="$depositsAll">
        <!-- Do required operations for the 1st deposit -->
        <xsl:variable name="depositFirst" select="$depositsAll[1]"/>
        <xsl:variable name="receiptSum">
            <xsl:choose>
                <xsl:when test="$receiptsAll">
                    <xsl:value-of select="sum($receiptsAll[position() &lt;= $receiptCount]/@ActionAmount)"/>
                </xsl:when>
                <xsl:otherwise>0</xsl:otherwise>
            </xsl:choose>
        </xsl:variable> 
        <xsl:variable name="diff" select="$depositPrevAmount + $depositFirst/@DepositTotalAmount - $receiptSum"/>

        <xsl:choose>
            <xsl:when test="$diff &gt; 0 and
                $receiptCount &lt; $receiptsQuantityOf">
                <xsl:call-template name="classifyDeposits">
                    <xsl:with-param name="depositsAll" select="$depositsAll"/>
                    <xsl:with-param name="depositPrevAmount" select="$depositPrevAmount"/>
                    <xsl:with-param name="receiptsAll" select="$receiptsAll"/>
                    <xsl:with-param name="receiptCount" select="$receiptCount + 1"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <!-- Record changes to the deposit (@DueAmount and receipt ReceivedDate) -->
                <xsl:apply-templates select="$depositFirst" mode="defineDeposit">
                    <xsl:with-param name="diff" select="$diff"/>
                    <xsl:with-param name="latestReceiptForDeposit" select="$receiptsAll[position() = $receiptCount]"/>
                </xsl:apply-templates>


                <!-- Recursive call to the next deposit -->
                <xsl:call-template name="classifyDeposits">
                    <xsl:with-param name="depositsAll" select="$depositsAll[position() &gt; 1]"/>
                    <xsl:with-param name="depositPrevAmount" select="$depositPrevAmount + $depositFirst/@DepositTotalAmount"/>
                    <xsl:with-param name="receiptsAll" select="$receiptsAll"/>
                    <xsl:with-param name="receiptCount" select="'1'"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:if>
</xsl:template>

<!-- Determine deposit's status, due amount and payment received date if any -->
<xsl:template match="Deposits" mode="defineDeposit">
    <xsl:param name="diff"/>
    <xsl:param name="latestReceiptForDeposit"/>

    <xsl:choose>
        <xsl:when test="$diff &lt;= 0">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'paid'"/>
                <xsl:with-param name="dueAmount" select="'0'"/>
                <xsl:with-param name="receipt" select="$latestReceiptForDeposit"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$diff = ./@DepositTotalAmount">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'due'"/>
                <xsl:with-param name="dueAmount" select="$diff"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:when test="$diff &lt; ./@DepositTotalAmount">
            <xsl:apply-templates select="." mode="addAttrs">
                <xsl:with-param name="status" select="'outstanding'"/>
                <xsl:with-param name="dueAmount" select="$diff"/>
                <xsl:with-param name="receipt" select="$latestReceiptForDeposit"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise/>
    </xsl:choose>
</xsl:template>

<xsl:template match="Deposits" mode="addAttrs">
    <xsl:param name="status"/>
    <xsl:param name="dueAmount"/>
    <xsl:param name="receipt" select="''"/>

    <!-- Constract a new MultiDeposits element with required info -->
    <xsl:copy>
        <xsl:copy-of select="./@*"/>
        <xsl:attribute name="Status"><xsl:value-of select="$status"/></xsl:attribute>
        <xsl:attribute name="DueAmount"><xsl:value-of select="$dueAmount"/></xsl:attribute>
        <xsl:if test="$receipt">
            <xsl:attribute name="latestReceiptDate">
                <xsl:value-of select="$receipt/@ActionDate"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:copy-of select="./*"/>
    </xsl:copy>
</xsl:template>

Upvotes: 0

Views: 2072

Answers (2)

Joshua
Joshua

Reputation: 384

One thing i've learned about concerning recursion is that you need to make sure that you have a condition that will trigger when you are done.

example:

decrement_me_until_zero(int i)
{
     if(i ==0) return;  //we're done here, roll on back up!
     else
     {
         i = i-1;
         decrement_me_until_zero(i);
         return;
     }
}

I don't know xlst at all, but this can be a simple reason why recursion can go into an infinite loop. Consider instead:

decrement_me_until_zero(int i)
{
     if(i ==0) return;  //we're done here, roll on back up!
     else
     {
         decrement_me_until_zero(i);
         i=i-1;                           //oops!  we decrement after we pass the variable down!
         return;
     }
}

I hope that helps

Upvotes: 0

Welbog
Welbog

Reputation: 60438

Your named template classifyDeposits is definitely recursing infinitely. It has no base case. In the <xsl:choose> section you have two conditions: <xsl:when> and <xsl:otherwise>, however you call classifyDeposits in both conditions.

So no matter what you pass to this template, it will call itself. You need to have a base case: a condition in which the template doesn't call itself.

In pseudocode, you're essentially doing this:

function recursive
  if (A>B) then
    call recursive
  else
    call recursive

You need to add another branch, somewhere, that does not call itself.

function recursive
  if (C=0) then
    return
  else if (A>B) then
    call recursive
  else
    call recursive

Of course, just having the condition isn't good enough. It needs to be reachable.

Upvotes: 4

Related Questions