XSLT 1.0 помогите с логикой рекурсии - PullRequest
1 голос
/ 20 апреля 2010

У меня проблемы с логикой, и буду признателен за любую помощь / советы.

У меня есть <Deposits> элементы и <Receipts> элементы. Однако, нет никакой идентификации, какая квитанция была заплачена к какому депозиту.

Я пытаюсь обновить элементы <Deposits> со следующими атрибутами:

  • @ DueAmont - сумма, которую еще нужно выплатить
  • @ Статус - будь то оплаченный, непогашенный (частично оплаченный) или причитающийся
  • @ ReceiptDate - дата последней квитанции, которая была выплачена в пользу этого депозита

Каждый депозит может быть оплачен одной или несколькими квитанциями. Также может случиться, что 1 квитанция может покрывать один или несколько депозитов. Например. Если есть 3 депозита:

  1. 500
  2. 100
  3. 450

Оплачиваются следующими квитанциями:

  1. 200
  2. 100
  3. 250

Я хочу получить следующую информацию:
Депозит 1 полностью оплачен (статус = оплачен, dueAmount = 0, квитанция = 3.
Депозит 2 частично оплачен (статус = невыплаченный, задолженность = 50, квитанция = 3.
Депозит 3 не выплачивается (статус = срок оплаты, срок платежа = 450, номер чека = 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"/>

Я добавил комментарии в коде, объясняющие, что я пытаюсь сделать. Я смотрю на этот код уже третий день без перерыва - не вижу, что я делаю не так. Пожалуйста, кто-нибудь может мне помочь с этим? :)

Спасибо!

Настройка:
$ депозиты - все доступные депозиты
$ recetsAsc - все доступные квитанции, отсортированные по их @ 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 ]

2 голосов
/ 20 апреля 2010

Интересный вопрос. Я считаю, что лучше всего добавить параметр для накопления баланса, чтобы вам не нужно было обновлять структуру квитанций. Моя версия следует. Я проверил его на предоставленном вами примере и получил ожидаемый результат.

Обратите внимание, что я изменил шаблон шаблона для депозитов с 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 голос
/ 20 апреля 2010

Однако не установлено, какая квитанция была оплачена на какой депозит.

Это ключ к вашей проблеме. Вы должны иметь возможность связать квитанции с депозитами ВВЕРХ. Представьте, если бы вы работали с бумажными квитанциями, как бы вы справились с этим? Вам нужно будет спросить человека, который дал вам квитанцию, сколько было предназначено для какого депозита, а затем, как только вы узнаете это, вы запишите это в квитанции. Как только вы узнаете это и отразите это в том, как вы представляете квитанции, вы можете создать 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 квитанциями, будет ли для вас по-прежнему иметь значение атрибут receiveNum? вам, возможно, придется расширить это, возможно, добавив дочерние элементы промежуточных итогов таким же образом, как и модель квитанций, которую я предлагал ранее.

Я бы сказал, если вы хотите справиться с этим, представьте, что вы делали все это с / на бумаге. Это пролило бы свет на то, как вам нужно сделать это в коде.

Просматривая некоторые другие ваши посты, я понимаю, что вы, возможно, не управляете набором данных, который получаете. Однако в какой-то момент вы должны быть в состоянии ответить на вопрос: «Какие суммы этих поступлений идут на какие депозиты?» После этого, я должен сказать, что ваши попытки использовать рекурсию для решения этой проблемы могут служить только для того, чтобы сбить вас с толку. Любой метод рекурсии можно заменить циклом. Я с нетерпением жду возможности увидеть ваше окончательное решение.

...