XSLT 1.0 справка по логике рекурсии

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

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

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

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

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

  1. 500
  2. 100
  3. 450

Которые оплачиваются следующими квитанциями:

  1. 200
  2. 100
  3. 250

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

Спасибо!

Настройка:
$deposits — все доступные депозиты
$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>

person DashaLuna    schedule 20.04.2010    source источник
comment
@Dasha: Объяснение вашего XML приятно, но фактический XML (даже если он сокращен, чтобы соответствовать части, которую вы объясняете) намного лучше. Слова всегда двусмысленны, код не очень.   -  person Tomalak    schedule 20.04.2010
comment
@ Томалак хорошая мысль. Я также добавил XML.   -  person DashaLuna    schedule 20.04.2010
comment
@Даша: Как вы определяете, какая квитанция относится к какому депозиту? Или это просто вопрос заполнения всех депозитов квитанциями в порядке возрастания даты?   -  person Tomalak    schedule 20.04.2010
comment
@Tomalak - да, он подсчитывает все поступления в порядке возрастания ко всем депозитам в порядке возрастания, начиная с 1-го депозита, чтобы определить, какие депозиты выплачены, частично выплачены и подлежат оплате. Фиксация последней даты поступления депозита в случае его полной или частичной выплаты. Надеюсь, это имеет смысл, извините, если я объясню это слишком расплывчато.   -  person DashaLuna    schedule 21.04.2010
comment
@Даша: Итак, ваше заявление. Однако нет никакой идентификации, какая квитанция была выплачена на какой депозит. красная сельдь? Я имею в виду, кажется, что это не имеет большого значения, поскольку все они оказываются в одном и том же ведре.   -  person Tomalak    schedule 21.04.2010
comment
@Tomalak: Извините, я немного запутался. Элементы чеков генерируются в системе с течением времени. Отчеты, для которых мне нужна эта функциональность, могут быть созданы в любое время. В определенный момент времени, когда создается отчет, некоторые депозиты будут выплачены полностью/частично или причитаются к оплате. Если они оплачены полностью/частично, мне нужно показать дату последней квитанции, которая была выплачена в связи с этим, и если какая-либо сумма все еще остается непогашенной для депозита. Имеет ли это смысл?   -  person DashaLuna    schedule 21.04.2010
comment
@Даша: Конечно. Я (и @Joshua ниже) были на неправильном пути, потому что мы думали, что существует несколько отдельных, совершенно не связанных между собой квитанций и отдельных депозитов, которые могут быть выплачены по той или иной квитанции. Но на самом деле это одна большая квитанция (разбитая на мелкие части), и любой депозит идет на такую ​​же большую квитанцию.   -  person Tomalak    schedule 21.04.2010
comment
@Tomalak: Мне очень жаль, что я не очень хорошо это объяснил :( Я попытался подкрепить объяснение примером. Перечисляю, с чего я должен начать и что мне нужно получить в конце (списки депозиты и квитанции, а также окончательную/обновленную информацию о депозите).   -  person DashaLuna    schedule 21.04.2010


Ответы (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")
person markusk    schedule 20.04.2010
comment
@markusk: Большое спасибо! Это работает как по волшебству! Извините за путаницу с «Мультидепозитами», это должно быть «Депозиты», как вы правильно заметили. Мне очень нравится это решение с балансом, а не слишком сложное с переписыванием элементов ‹Receipts›. Я немного запутался, как работает $balance в рекурсивном вызове следующего депозита (например, ‹xsl:with-param name=balance select=$balance + $deposit/@DepositTotalAmount/›). Я работал со следующей настройкой: Депозит 1 = 2200, Депозит 2 = 1100 и Поступление 1 = 200, Поступление 2 = 2000, Поступление 3 = 800. [См. следующий комментарий] - person DashaLuna; 21.04.2010
comment
1Run) бал = 0; diff = 0(bal) + 2200(dep) - 200(recp) = 2000 (это › 0 и более квитанций) 2Run) bal = 0-200(recp) = -200; diff = -200(bal) + 2200(dep) - 2000(recp) = 0 (перейти к следующему депозиту) 3Run) bal= -200 + 2200(dep) = 2000; diff = 2000(bal) + 1100(dep) - 200(recp) = 2900 (это > 0 и более квитанций) 4Run) bal= 2000 - 200(rec) = 1800; diff = 1800(bal) + 1100(dep) - 2000(recp) = 900 (это > 0 и более квитанций) 5Run) bal= 1800 - 2000(rec) = -200; diff = -200(bal) +1100 (dep) -800 (rec) = 100 (больше нет депозитов) [см. следующий комментарий] - person DashaLuna; 21.04.2010
comment
Таким образом, я получаю 100 вместо 300, которые они все еще должны за 2-й депозит. Я запутался, где я ошибаюсь здесь. Был бы очень признателен, если бы вы указали на это. Извините, если это неясно, я старался изо всех сил с примером. Большое спасибо!! - person DashaLuna; 21.04.2010
comment
@DashaLuna: В вашем резюме для 3Run есть ошибка), разница должна быть 1100, а не 2900. Я обновил свой ответ с помощью вашего примера ввода. Вызов <xsl:with-param name="balance" select="$balance + $deposit/@DepositTotalAmount"/> гарантирует, что, когда депозит помечен как оплаченный, сумма депозита учитывается в текущем чеке при расчете разницы для следующего депозита. - person markusk; 22.04.2010
comment
@markusk: Спасибо за пример! Я вижу, где я делал это неправильно. При переходе к следующему депозиту я бы начал с 1-го квитанции начального набора узлов, а не с полученного на предыдущем шаге (где мы просматриваем квитанции и накапливаем их в $balance, когда депозит не полностью выплачен и квитанции остались). Эм, надеюсь, это имеет смысл. Большое спасибо, что нашли время и разместили ответ и комментарии! Ты гений :) - person DashaLuna; 22.04.2010
comment
@DashaLuna: Рад помочь, я рад, что мой код был вам полезен. :-) - person markusk; 22.04.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 был частично оплачен двумя квитанциями, будет ли для вас по-прежнему иметь значение атрибутrecitNum? вам, возможно, придется расширить это, возможно, добавив дочерние элементы промежуточных итогов таким же образом, как модель квитанций, которую я предложил ранее.

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

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

person Joshua    schedule 20.04.2010
comment
@Джошуа: Спасибо за ответ. Мне жаль, что мое объяснение не было ясным с самого начала. Я ответил @Tomalak в комментариях к вопросу. Я надеюсь, что комментарии прояснят, что мне нужно. В этом отношении решение @markusk — это то, что я искал. Еще раз спасибо, что нашли время и опубликовали, я очень ценю это. - person DashaLuna; 21.04.2010