XSLT 1.0有助于递归逻辑

时间:2010-04-20 11:41:49

标签: xslt recursion logic xslt-1.0

我遇到了逻辑上的麻烦,并会感激任何帮助/提示。

我有<Deposits>个元素和<Receipts>个元素。但是,没有任何标识证明哪些收据是支付给存款的。

我正在尝试使用以下属性更新<Deposits>元素:

  • @DueAmont - 仍需支付的金额
  • @Status - 无论是付费,未付(部分付费)还是到期
  • @ReceiptDate - 为此存款支付的最新收据日期

每笔存款都可以用一张或多张收据支付。也可能发生,1张收据可能包含一笔或多笔存款。例如。如果有3个存款:

  1. 500
  2. 100
  3. 450
  4. 使用以下收据支付:

    1. 200
    2. 100
    3. 250
    4. 我想获得以下信息:
      存款1 已全额付款(状态=已付款,dueAmount = 0,receiptNum = 3。) 部分支付存款2 (状态=未结,dueAmount = 50,receiptNum = 3。) 存款3 未付款(status = due,dueAmount = 450,receiptNum = NAN。



      实际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"/>
      

      我在代码中添加了注释,解释了我正在尝试做的事情。我现在盯着这个代码第3天不停 - 看不出我做错了什么。请有人帮我吗? :)

      谢谢!

      设置:
      $ deposit - 所有可用存款
      $ receiptsAsc - 按@ActionDate

      排序的所有可用收据

      代码:

      <!-- 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() &lt;= $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 &gt; 0) and ($receiptCount &lt; 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() &lt; $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 &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="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 &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="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>
      

2 个答案:

答案 0 :(得分:2)

有趣的问题。我认为更好的方法是添加一个参数来累积余额,这样您就不需要更新收据结构了。我的版本如下。我已根据您提供的样本输入对其进行了测试,并收到了预期的结果。

请注意,由于您在示例输入中使用了后一个元素名称,因此我将存款的模板模式从MultiDeposits更改为Deposits

<!-- 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 &gt; 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 &gt;= @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 &gt; 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> 

作为一个例子,这里是输入的递归方式:

存款1 = 2200,存款2 = 1100
收据1 = 200,收据2 = 2000,收据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")

答案 1 :(得分:1)

  

然而,没有任何标识证明哪些收据是支付给存款的。

这是您的问题的关键。您必须有办法将收据与存款UP FRONT相关联。想象一下,如果您正在处理纸质收据,您将如何处理?您必须询问给您收据的人是多少用于存款,然后一旦您发现存款,就将其记录在收据上。一旦你知道这一点并以表示收据的方式反映它,你就可以构建xslt以获取这些位。不幸的是我无法帮助你使用xslt,但想象每个收据都有每个分区的子元素。像:

<RECEIPTS total=500 blah blah blah>
      <subtotal deposit=1 amount=100>
      <subtotal deposit=2 amount=300>
</RECEIPTS>

然后当你循环时,抓住收据的子项,遍历每个小计并将其添加到适当的计数器中以存入金额。

另外,我注意到,根据您所需的输出,如果存款中有多张收据,会发生什么?你怎么代表那个?目前你有

Deposit 2 is partly paid (status=outstanding, dueAmount=50, receiptNum=3

如果存款2部分支付2张收据怎么办,属性receiptNum对你有意义吗?您可能需要对此进行扩展,可能通过添加小计子元素,方法与我之前提供的收据模型相同。

我想说如果你想对此进行处理,假装你在纸上做这件事。这将阐明你需要如何在代码中完成它。

在查看了其他一些帖子之后,我意识到您可能无法控制所获得的数据集。然而,在某些时候,你需要能够回答这个问题,“这些收据中有哪些收据存入哪些存款?”在那之后,我不得不说,你尝试使用递归来解决这个问题可能只会让你感到困惑。任何递归方法都可以用循环替换。我期待看到你的最终解决方案是什么样的。