XSLT 1.0 help with recursion logic - xslt
I'm having troubles with the logic and would apprecite any help/tips.
I have <Deposits> elements and <Receipts> elements. However there isn't any identification what receipt was paid toward what deposit.
I am trying to update the <Deposits> elements with the following attributes:
#DueAmont - the amount that is still due to pay
#Status - whether it's paid, outstanding (partly paid) or due
#ReceiptDate - the latest receipt's date that was paid towards this deposit
Every deposit could be paid with one or more receipts. It also could happen, that 1 receipt could cover one or more deposits. For example. If there are 3 deposits:
500
100
450
That are paid with the following receipts:
200
100
250
I want to get the following info:
Deposit 1 is fully paid (status=paid, dueAmount=0, receiptNum=3.
Deposit 2 is partly paid (status=outstanding, dueAmount=50, receiptNum=3.
Deposit 3 is not paid (status=due, dueAmount=450, receiptNum=NAN.
Actual XML:
<Deposits DepositDate="2010-04-07T00:00:00" DepositTotalAmount="500.0000" NoOfPeople="10.0000" PerPerson="50.00"/>
<Deposits DepositDate="2010-04-12T00:00:00" DepositTotalAmount="100.0000" NoOfPeople="10.0000" PerPerson="10.00"/>
<Deposits DepositDate="2010-04-26T00:00:00" DepositTotalAmount="450.0000" NoOfPeople="10.0000" PerPerson="45.00"/>
<Receipts Amount="200.00" PaymentType="Cheque" Comment="" ReceiptAmount="200.00" ActionDate="2010-04-07T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>
<Receipts Amount="100.00" PaymentType="Cheque" Comment="" ReceiptAmount="100.00" ActionDate="2010-04-11T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>
<Receipts Amount="250.00" PaymentType="Cheque" Comment="" ReceiptAmount="250.00" ActionDate="2010-04-20T11:01:47" PayingInSlipNumber="" IsCard="No" IsRefund="No"/>
I've added comments in the code explaining what I'm trying to do. I am staring at this code for the 3rd day now non stop - can't see what I'm doing wrong. Please could anyone help me with it? :)
Thanks!
Set up:
$deposits - All the available deposits
$receiptsAsc - All the available receipts sorted by their #ActionDate
Code:
<!-- Accumulate all the deposits with #Status, #DueAmount and #ReceiptDate attributes Provide all deposits, receipts and start with 1st receipt -->
<xsl:variable name="depositsClassified">
<xsl:call-template name="classifyDeposits">
<xsl:with-param name="depositsAll" select="$deposits"/>
<xsl:with-param name="receiptsAll" select="$receiptsAsc"/>
<xsl:with-param name="receiptCount" select="'1'"/>
</xsl:call-template>
</xsl:variable>
<!-- Recursive function to associate deposits' total amounts with overall receipts paid
to determine whether a deposit is due, outstanding or paid. Also determine what's the due amount and latest receipt towards the deposit for each deposit -->
<xsl:template name="classifyDeposits">
<xsl:param name="depositsAll"/>
<xsl:param name="receiptsAll"/>
<xsl:param name="receiptCount"/>
<!-- If there are deposits to proceed -->
<xsl:if test="$depositsAll">
<!-- Get the 1st deposit -->
<xsl:variable name="deposit" select="$depositsAll[1]"/>
<!-- Calculate the sum of all receipts up to and including currenly considered -->
<xsl:variable name="receiptSum">
<xsl:choose>
<xsl:when test="$receiptsAll">
<xsl:value-of select="sum($receiptsAll[position() <= $receiptCount]/#ReceiptAmount)"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Difference between deposit amount and sum of the receipts calculated
above -->
<xsl:variable name="diff" select="$deposit/#DepositTotalAmount - $receiptSum"/>
<xsl:choose>
<!-- Deposit isn't paid fully and there are more receipts/payments exist.
So consider the same deposit, but take next receipt into calculation as
well -->
<xsl:when test="($diff > 0) and ($receiptCount < count($receiptsAll))">
<xsl:call-template name="classifyDeposits">
<xsl:with-param name="depositsAll" select="$depositsAll"/>
<xsl:with-param name="receiptsAll" select="$receiptsAll"/>
<xsl:with-param name="receiptCount" select="$receiptCount + 1"/>
</xsl:call-template>
</xsl:when>
<!-- Deposit is paid or we ran out of receipts -->
<xsl:otherwise>
<!-- process the deposit. Determine its status and then update
corresponding attributes -->
<xsl:apply-templates select="$deposit" mode="defineDeposit">
<xsl:with-param name="diff" select="$diff"/>
<xsl:with-param name="receiptNum" select="$receiptCount"/>
</xsl:apply-templates>
<!-- Recursively call the template with the rest of deposits excluding the first. Before hand update the #ReceiptsAmount. For the receipts before current it is now 0, for the current is what left in the $diff, and simply copy over receipts after current one. -->
<xsl:variable name="receiptsUpdatedRTF">
<xsl:for-each select="$receiptsAll">
<xsl:choose>
<!-- these receipts was fully accounted for the current deposit. Make them 0 -->
<xsl:when test="position() < $receiptCount">
<xsl:copy>
<xsl:copy-of select="./#*"/>
<xsl:attribute name="ReceiptAmount">0</xsl:attribute>
</xsl:copy>
</xsl:when>
<!-- this receipt was partly/fully(in case $diff=0) accounted for the current deposit. Make it whatever is in $diff -->
<xsl:when test="position() = $receiptCount">
<xsl:copy>
<xsl:copy-of select="./#*"/>
<xsl:attribute name="ReceiptAmount">
<xsl:value-of select="format-number($diff, '#.00;#.00')"/>
</xsl:attribute>
</xsl:copy>
</xsl:when>
<!-- these receipts weren't yet considered - copy them over -->
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="receiptsUpdated" select="msxsl:node-set($receiptsUpdatedRTF)/Receipts"/>
<!-- Recursive call for the next deposit. Starting counting receipts from the current one. -->
<xsl:call-template name="classifyDeposits">
<xsl:with-param name="depositsAll" select="$deposits[position() != 1]"/>
<xsl:with-param name="receiptsAll" select="$receiptsUpdated"/>
<xsl:with-param name="receiptCount" select="$receiptCount"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<!-- Determine deposit's status and due amount -->
<xsl:template match="MultiDeposits" mode="defineDeposit">
<xsl:param name="diff"/>
<xsl:param name="receiptNum"/>
<xsl:choose>
<xsl:when test="$diff <= 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="receiptNum" select="$receiptNum"/>
</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 < ./#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="receiptNum" select="$receiptNum"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>
<!-- Add new attributes (#Status, #DueAmount and #ReceiptDate) to the
deposit element -->
<xsl:template match="MultiDeposits" mode="addAttrs">
<xsl:param name="status"/>
<xsl:param name="dueAmount"/>
<xsl:param name="receiptNum" select="''"/>
<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="$receiptNum != ''">
<xsl:attribute name="ReceiptDate">
<xsl:value-of select="$receiptsAsc[position() = $receiptNum]/#ActionDate"/>
</xsl:attribute>
</xsl:if>
<xsl:copy-of select="./*"/>
</xsl:copy>
</xsl:template>
Interesting question. I believe a better approach is to add a parameter to accumulate the balance, so that you will not need to update the receipts structure. My version follows. I have tested it on the sample input you provided, and received the expected result.
Please note that I changed the template pattern for deposits from MultiDeposits to Deposits, since you used the latter element name in your sample input.
<!-- Accumulate all the deposits with #Status, #DueAmount and #ReceiptDate
attributes. Provide all deposits and receipts. -->
<xsl:variable name="depositsClassified">
<xsl:call-template name="classifyDeposits">
<xsl:with-param name="depositsAll" select="$deposits"/>
<xsl:with-param name="receiptsAll" select="$receiptsAsc"/>
</xsl:call-template>
</xsl:variable>
<!-- Recursive function to associate deposits' total amounts with overall
receipts paid to determine whether a deposit is due, outstanding or paid.
Also determine what's the due amount and latest receipt towards the
deposit for each deposit -->
<xsl:template name="classifyDeposits">
<xsl:param name="depositsAll"/>
<xsl:param name="receiptsAll"/>
<xsl:param name="balance" select="0"/>
<!-- If there are deposits to proceed -->
<xsl:if test="$depositsAll">
<!-- Get the 1st deposit -->
<xsl:variable name="deposit" select="$depositsAll[1]"/>
<!-- Get the 1st receipt. -->
<xsl:variable name="receipt" select="$receiptsAll[1]"/>
<!-- Calculate difference. -->
<xsl:variable
name="diff"
select="$balance + $deposit/#DepositTotalAmount
- $receipt/#ReceiptAmount"/>
<xsl:choose>
<!-- Deposit isn't paid fully and there are more receipts.
Move on to the next receipt, with updated balance. -->
<xsl:when test="($diff > 0) and $receiptsAll[2]">
<xsl:call-template name="classifyDeposits">
<xsl:with-param name="depositsAll" select="$depositsAll"/>
<xsl:with-param
name="receiptsAll"
select="$receiptsAll[position() != 1]"/>
<xsl:with-param
name="balance"
select="$balance - $receipt/#ReceiptAmount"/>
</xsl:call-template>
</xsl:when>
<!-- Deposit is paid or we ran out of receipts -->
<xsl:otherwise>
<!-- Process the deposit. Determine its status and then update
corresponding attributes -->
<xsl:apply-templates select="$deposit" mode="defineDeposit">
<xsl:with-param name="diff" select="$diff"/>
<xsl:with-param name="receipt" select="$receipt"/>
</xsl:apply-templates>
<!-- Recursive call for the next deposit. -->
<xsl:call-template name="classifyDeposits">
<xsl:with-param
name="depositsAll"
select="$depositsAll[position() != 1]"/>
<xsl:with-param name="receiptsAll" select="$receiptsAll"/>
<xsl:with-param
name="balance"
select="$balance + $deposit/#DepositTotalAmount"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<!-- Output deposit's status and due amount -->
<xsl:template match="Deposits" mode="defineDeposit">
<xsl:param name="diff"/>
<xsl:param name="receipt"/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:choose>
<xsl:when test="$diff >= #DepositTotalAmount">
<xsl:attribute name="Status">due</xsl:attribute>
<xsl:attribute name="DueAmount">
<xsl:value-of select="#DepositTotalAmount"/>
</xsl:attribute>
</xsl:when>
<xsl:when test="$diff > 0">
<xsl:attribute name="Status">outstanding</xsl:attribute>
<xsl:attribute name="DueAmount">
<xsl:value-of select="$diff"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="Status">paid</xsl:attribute>
<xsl:attribute name="DueAmount">0</xsl:attribute>
<xsl:attribute name="ReceiptDate">
<xsl:value-of select="$receipt/#ActionDate"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:copy-of select="node()"/>
</xsl:copy>
</xsl:template>
As an example, here's how a recursion develops for the input:
Deposit 1 = 2200, Deposit 2 = 1100
Receipt 1 = 200, Receipt 2 = 2000, Receipt 3 = 800
1Run) bal = 0;
diff = 0(bal) + 2200(dep1) - 200(recp1) = 2000
(diff > 0 & more receipts)
2Run) bal = 0-200(recp1) = -200;
diff = -200(bal) + 2200(dep1) - 2000(recp2) = 0
(output first deposit: status = "paid", proceed to next deposit)
3Run) bal= -200 + 2200(dep1) = 2000;
diff = 2000(bal) + 1100(dep2) -2000(recp2) = 1100
(diff > 0 & more receipts)
4Run) bal= 2000 - 2000(recp2) = 0;
diff = 0(bal) + 1100(dep2) - 800(recp3) = 300
(no more receipts, output second deposit: status = "outstanding")
However there isn't any identification what receipt was paid toward what deposit.
This is the key to your problem. You have to have a way to associate the receipts with the deposits UP FRONT. Imagine if you were working with paper receipts, how would you handle this? You'd have to ask the person who gave you the receipt how much was intended for which deposit, and then once you found that out you record it on the receipt. Once you know this and reflect it in the way you represent receipts, you can build the xslt to grab those bits out. Unfortunately I can't help you with the xslt for that, but imagine that each receipt has child element for each partition. like:
<RECEIPTS total=500 blah blah blah>
<subtotal deposit=1 amount=100>
<subtotal deposit=2 amount=300>
</RECEIPTS>
then as you loop through, grab the children of the receipt, loop through each subtotal and add it to the appropriate counter for the deposit sum.
Also, I noticed, from your desired output, what happens if more than one receipt is applied towards a deposit? how do you represent that? currently you have
Deposit 2 is partly paid (status=outstanding, dueAmount=50, receiptNum=3
what if deposit 2 was partially paid with 2 receipts, is the attribute receiptNum still going to have meaning for you? you might have to extend this, maybe by adding subtotal children elements in the same manner as the receipts model I offered earlier.
Id say if you want to get a handle on this, pretend you were doing this all with/on paper. That would shed light on how you need to do it in code.
After looking at some of your other posts, I realize that you may not be in control of the dataset you get. At some point however, you need to be able to answer the question, "Which amounts of these receipts go to which deposits?" After that, I have to say, your attempts at using recursion to solve this problem might be serving only to confuse you. Any recursion method can be replaced with a loop instead. I look forward to seeing what your final solution looks like.
Related
extract csv content from an xml tag
i have a source xml as below <rest-adapter-response> <metadata> <status>success</status> </metadata> <status-line> <code>200</code> <reason>OK</reason> </status-line> <header-lines> <Cache-Control>private, max-age=0</Cache-Control> <Transfer-Encoding>chunked</Transfer-Encoding> <Content-Type>application/octet-stream</Content-Type> <Expires>Thu, 25 Apr 2019 08:51:55 GMT</Expires> <Last-Modified>Fri, 10 May 2019 08:51:55 GMT</Last-Modified> <Server>Microsoft-IIS/10.0</Server> <X-SharePointHealthScore>1</X-SharePointHealthScore> <X-SP-SERVERSTATE>ReadOnly=0</X-SP-SERVERSTATE> <DATASERVICEVERSION>3.0</DATASERVICEVERSION> <X-Download-Options>noopen</X-Download-Options> <Content-Disposition>attachment</Content-Disposition> <SPClientServiceRequestDuration>224</SPClientServiceRequestDuration> <X-AspNet-Version>4.0.30319</X-AspNet-Version> <SPRequestGuid>de31db9e-70cb-8000-7fba-6c3e85d9c810</SPRequestGuid> <request-id>de31db9e-70cb-8000-7fba-6c3e85d9c810</request-id> <MS-CV>ntsx3stwAIB/umw+hdnIEA.0</MS-CV> <Strict-Transport-Security>max-age=31536000</Strict-Transport-Security> <X-FRAME-OPTIONS>SAMEORIGIN</X-FRAME-OPTIONS> <X-Powered-By>ASP.NET</X-Powered-By> <MicrosoftSharePointTeamServices>16.0.0.8824</MicrosoftSharePointTeamServices> <X-Content-Type-Options>nosniff</X-Content-Type-Options> <X-MS-InvokeApp>1; RequireReadOnly</X-MS-InvokeApp> <P3P>CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"</P3P> <Date>Fri, 10 May 2019 08:51:55 GMT</Date> </header-lines> <message-body> <non-xml-data-response>COA,COA Acct Desc,Acct Prefix,Revaluation Acct,Mapping Changes - Additions( A ) Deletions ( D ) Changes ( C ),MJE,OIM Recon,Comments for difference:10000274,"Citibank, Operating, USD, 31165975",1000,10009999,A,,X,10000374,"Citibank, Clearing, USD, 31165975",1000,10009999,A,,X,10006604,"HSBC, Operating, SAR, SA0345000000003179660002",1000,10009999,A,,X,10006605,"Citibank, Operating, ZAR, 0202099009",1000,10009999,A,,X,123,,,456,,,,</non-xml-data-response> </message-body> </rest-adapter-response> above XML is the response of a share point web service which tried to read a csv file and gave a response like this. as you can see in the above response xml , csv data did come but inside one xmltag called <message-body> , and also lost the new line after every row format!! now i need to recreate the csv !!. and the worst part is , in the tool where i receive this format i have capability to write xslt and xml ! no hosting language code or libraries could be used. also only xslt 1.0. there is a question like this question on creating csv from xml , but this is bit different from my requirement . am just learning xslt and xpath , can any one help me in this ? below is the requested output : click here to view the csv format
If one makes the assumption that the header row is terminated by a colon and that there are 7 values in each data row*, then it's possible to use the following stylesheet: XSLT 1.0 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" encoding="UTF-8"/> <xsl:template match="/rest-adapter-response"> <xsl:variable name="csv" select="message-body/non-xml-data-response" /> <!-- header --> <xsl:value-of select="substring-before($csv, ':')" /> <xsl:text>: </xsl:text> <!-- data --> <xsl:call-template name="restore-csv"> <xsl:with-param name="text" select="substring-after($csv, ':')"/> </xsl:call-template> </xsl:template> <xsl:template name="restore-csv"> <xsl:param name="text"/> <xsl:param name="i" select="1"/> <xsl:choose> <xsl:when test="contains($text, ',')"> <xsl:variable name="value"> <xsl:choose> <xsl:when test="starts-with($text, '"')"> <xsl:text>"</xsl:text> <xsl:value-of select="substring-before(substring-after($text, '"'), '"')"/> <xsl:text>"</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="substring-before($text, ',')"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- output --> <xsl:value-of select="$value"/> <xsl:choose> <xsl:when test="$i mod 7 = 0"> <xsl:text> </xsl:text> </xsl:when> <xsl:otherwise> <xsl:text>,</xsl:text> </xsl:otherwise> </xsl:choose> <!-- recursive call --> <xsl:call-template name="restore-csv"> <xsl:with-param name="text"> <xsl:choose> <xsl:when test="starts-with($text, '"')"> <xsl:value-of select="substring-after(substring-after($text, '"'), '",')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="substring-after($text, ',')"/> </xsl:otherwise> </xsl:choose> </xsl:with-param> <xsl:with-param name="i" select="$i + 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$text"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> Applied to your input example, the result will be: Result COA,COA Acct Desc,Acct Prefix,Revaluation Acct,Mapping Changes - Additions( A ) Deletions ( D ) Changes ( C ),MJE,OIM Recon,Comments for difference: 10000274,"Citibank, Operating, USD, 31165975",1000,10009999,A,,X 10000374,"Citibank, Clearing, USD, 31165975",1000,10009999,A,,X 10006604,"HSBC, Operating, SAR, SA0345000000003179660002",1000,10009999,A,,X 10006605,"Citibank, Operating, ZAR, 0202099009",1000,10009999,A,,X 123,,,456,,, This may need more work to handle possible escaped double-quotes within quoted values. -- (*) The strange thing here is that there are 8 values in the header row, but only 7 in the data rows.
Debugging Error in Xpath 2.0 expression [closed]
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers. This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers. Closed 8 years ago. Improve this question Problem in the cases in the below XSLt code, even though variables are defined, it shows undefined variable. First choose is to test wheather the Year is for Leap year or non Leap Year. Second choose only finds the cases for number of Days of the month. <xsl:template name="CalDateNTime"> <xsl:param name="pDate"/> <xsl:param name="pMonth"/> <xsl:param name="pYear"/> <xsl:choose> <!-- Leap Year --> <xsl:when test="($pYear mod 4=0 and $pYear mod 100 !=0) or $pYear mod 400 =0" > <xsl:value-of select="$pDate"/> <xsl:value-of select="$rDate"/> <xsl:value-of select="$pMonth"/> <xsl:choose> <xsl:when test="$pMonth=1 and $rDate=32"> <xsl:variable name="rDate" select="1"/> <xsl:variable name="rMonth" select="2"/> </xsl:when> <xsl:when test="$pMonth=2 and $rDate=30"> <xsl:variable name="rDate" select="1"/> <xsl:variable name="rMonth" select="3"/> </xsl:when> <xsl:when test="$pMonth=3 and $rDate=32"> <xsl:variable name="rDate" select="1"/> <xsl:variable name="rMonth" select="4"/> </xsl:when> </xsl:choose> </xsl:when> <!-- Non - Leap Year --> <xsl:otherwise> <xsl:variable name="jan" select="31"/> <xsl:variable name="feb" select="28"/> </xsl:otherwise> </xsl:choose> </xsl:template>
XSLT variables only exist within their parent. <xsl:when test="$pMonth=1 and $rDate=32"> <xsl:variable name="rDate" select="1" /> <xsl:variable name="rMonth" select="2" /> </xsl:when> These two variables are out of scope immediately (at the </xsl:when>). You must structure your program differently. Put the <xsl:choose> into the variable. I can only assume what your partial sample code is supposed to do. My assumption is: It should calculate the day after a given date. Here's an alternative implementation: <xsl:template name="CalDateNTime"> <xsl:param name="pDate" /> <xsl:param name="pMonth" /> <xsl:param name="pYear" /> <xsl:variable name="dayNum" select="'312831303130313130313031'" /> <xsl:variable name="isLeap" select="($pYear mod 4 = 0 and $pYear mod 100 != 0) or ($pYear mod 400 = 0)" /> <!-- determine index into the $dayNum string --> <xsl:variable name="idx" select="($pMonth - 1) * 2" /> <xsl:variable name="maxDate" select="substring($dayNum, $idx + 1, 2)" /> <xsl:variable name="isSameMonth" select="($pDate < $maxDate) or ($pDate = $maxDate and $pMonth = 2 and $isLeap)" /> <!-- calculate following day, month, year values --> <xsl:variable name="nDate"> <xsl:choose> <xsl:when test="$isSameMonth"><xsl:value-of select="$pDate + 1" /></xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="nMonth"> <xsl:choose> <xsl:when test="$isSameMonth"><xsl:value-of select="$pMonth" /></xsl:when> <xsl:when test="$pMonth < 12"><xsl:value-of select="$pMonth + 1" /></xsl:when> <xsl:otherwise>1</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="nYear"> <xsl:choose> <xsl:when test="not($isSameMonth) and $nMonth = 1"><xsl:value-of select="$pYear + 1" /></xsl:when> <xsl:otherwise><xsl:value-of select="$pYear" /></xsl:otherwise> </xsl:choose> </xsl:variable> <!-- build output string --> <xsl:value-of select="concat($nYear, '-')" /> <xsl:if test="$nMonth < 10">0</xsl:if> <xsl:value-of select="concat($nMonth, '-')" /> <xsl:if test="$nDate < 10">0</xsl:if> <xsl:value-of select="$nDate" /> </xsl:template> Usage <xsl:call-template name="CalDateNTime"> <xsl:with-param name="pDate" select="28" /> <xsl:with-param name="pMonth" select="2" /> <xsl:with-param name="pYear" select="2000" /> </xsl:call-template> Result 2000-02-29
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() <= $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 > 0 and $receiptCount < $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() > 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 <= 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 < ./#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>
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.
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
XSLT sum of values based on data in two different tables
XML: <Budget> <Table1> <AllocID>1</AllocID> <Amount>1000</Amount> </Table1> <Table1> <AllocID>2</AllocID> <Amount>2000</Amount> </Table1> <Table1> <AllocID>3</AllocID> <Amount>3000</Amount> </Table1> <Table2> <AllocID>1</AllocID> <Split>100</Split> </Table2> <Table2> <AllocID>2</AllocID> <Split>100</Split> </Table2> </Budget> I am displaying the amounts in a table, but only if a "Split" value exists in Table2. <xsl:for-each select="Budget/Table1"> <xsl:for-each select="//Budget/Table2[AllocID = current()/AllocID]"> <xsl:value-of select="Amount"/> </xsl:for-each> </xsl:for-each> //Total for the records here. I need to get the sum of Amounts from Table1, but only if a value for the AllocID exists in Table2. So in this example, I only want the sum of Amount for AllocIDs 1 & 2. How would I do this in xslt without modifying the given datasets?
Knowing nothing else about your input data (I have no idea why you chose not to use actual XML), I'm going to have to to guess a little: <Budget> <Table1> <AllocID>1000</AllocID> <AllocID>2000</AllocID> <AllocID>3000</AllocID> </Table1> <Table2> <AllocID>1000</AllocID> <AllocID>2000</AllocID> </Table2> </Budget> Easiest is the XPath sum() function along with the right XPath expression: <!-- this will return 3000 for the above input --> <xsl:template match="/" > <xsl:value-of select=" sum(Budget/Table1/AllocID[. = //Budget/Table2/AllocID]) " /> </xsl:template> Running sums can also be calculated with a recursive function, like this: <!-- this will also return 3000 for the above input --> <xsl:template match="/" > <xsl:call-template name="total"> <xsl:with-param name="nodes" select=" Budget/Table1/AllocID[. = //Budget/Table2/AllocID] " /> </xsl:call-template> </xsl:template> <xsl:template name="total"> <xsl:param name="nodes" /> <xsl:choose> <!-- either there is something to calculate... --> <xsl:when test="string(number($nodes[1])) != 'NaN'"> <xsl:variable name="subtotal"> <xsl:call-template name="total"> <xsl:with-param name="nodes" select="$nodes[position() > 1]" /> </xsl:call-template> </xsl:variable> <xsl:value-of select="number($nodes[1]) + $subtotal" /> </xsl:when> <!-- ...or we assume 0 --> <xsl:otherwise> <xsl:value-of select="0" /> </xsl:otherwise> </xsl:choose> </xsl:template> This is slower, but allows for greater flexibility in the calculation process. You can replace all the non-numeric values with 0, for example. Or use different values of the given nodes according to your own rules.
XSLT Paging - default to current Date
I am using an xslt transform to show a long list of events. It has paging, but what I would like is for it to default to the first events that are closest to the current date.
I'm assuming you have a useful date format (YYYY-MM-DD). <xsl:param name="currentDate" select="''" /><!-- fill this from outside! --> <xsl:template name="isPageSelected"><!-- returns true or false --> <xsl:param name="eventsOnPage" /><!-- expects a node-set of events --> <xsl:choose> <xsl:when test="$eventsOnPage"> <!-- create a string "yyyy-mm-dd,YYYY-MM-DD-" (note the trailing dash) --> <xsl:variable name="dateRange"> <xsl:for-each select="$eventsOnPage"> <xsl:sort select="date" /> <xsl:if test="position() = 1"> <xsl:value-of select="concat(date, ',')" /> </xsl:if> <xsl:if test="position() = last()"> <xsl:value-of select="concat(date, '-')" /> </xsl:if> </xsl:for-each> </xsl:variable> <!-- trailing dash ensures that the "less than" comparison succeeds --> <xsl:value-of select=" $currentDate >= substring-before($dateRange, ',') and $currentDate < substring-after($dateRange, ',') " /> </xsl:when> <xsl:otherwise> <xsl:value-of select="false()" /> </xsl:otherwise> </xsl:choose> </xsl:template> So in your paging routine, to find out if the current page is the selected one, invoke <xsl:variable name="isPageSelected"> <xsl:call-template name="isPageSelected"> <xsl:with-param name="eventsOnPage" select="event[whatever]" /> </xsl:call-template> </xsl:variable> <!-- $isPageSelected now is true or false, proceed accordingly -->
Since sorting in XSLT 1.0 is horribly, horribly broken, your best bet is to find an extension or include a Unix-style time somewhere in your source XML so you can sort on that (although an ISO-formatted string might also work).