XSLT get some calculated node value over next line

2020-05-09 22:56发布

I have the following .xml file:

<?xml version="1.0" encoding="utf-8"?>
<upkeepList>
  <upkShare month_year="11_16-12_16" post_dte="01-20-17" due="02-04-17"> 
    <OpngBlnce>22</OpngBlnce>
    <mnthCrrntAmnt>77</mnthCrrntAmnt> 
    <Rcpt dte="01-26-17" No="5725">
      <amnt>22</amnt>
      <descrpt>"11/16" upkeep </descrpt>
    </Rcpt>
    <Rcpt dte="01-26-17" No="5726">
      <amnt>41</amnt>
      <descrpt>"12/16" upkeep </descrpt>
    </Rcpt>
  </upkShare>
  <upkShare month_year="01_17-02_17" post_dte="03-17-17" due="04-03-17">
    <OpngBlnce></OpngBlnce>   <!-- starting from this sect. this tag can be omitted as it'a calculated field -->
    <mnthCrrntAmnt>74</mnthCrrntAmnt>
    <Rcpt dte="03-30-17" No="5783">
     <amnt>50</amnt>
     <descrpt>"01/17-02/17" upkeep</descrpt>
    </Rcpt>
  </upkShare>
 <upkShare month_year="03-17" post_dte="04-16-17" due="05-02-17"> 
  <OpngBlnce></OpngBlnce>
  <mnthCrrntAmnt>55</mnthCrrntAmnt>
  <Rcpt dte="05-10-17" No="5815">
    <amnt>40</amnt>
    <descrpt>"03/17-04/17" upkeep 1</descrpt>
  </Rcpt>
  <Rcpt dte="05-15-17" No="5825">
   <amnt>9</amnt>
   <descrpt>"03/17-04/17" upkeep 2</descrpt>
  </Rcpt> 
 </upkShare>
 <upkShare month_year="04_17-05_17" post_dte="06-05-17" due="06-30-17">
  <OpngBlnce></OpngBlnce>
  <mnthCrrntAmnt>64</mnthCrrntAmnt>
  <Rcpt dte="06-14-17" No="5858">
   <amnt>37</amnt>
   <descrpt>"05/17" upkeep 1</descrpt>
  </Rcpt>
  <Rcpt dte="06-18-17" No="5863">
   <amnt>21</amnt>
   <descrpt>"05/17" upkeep 2</descrpt>
  </Rcpt>
 </upkShare>
 <upkShare month_year="06_17" post_dte="07-16-17" due="07-30-17">
  <OpngBlnce></OpngBlnce>
  <mnthCrrntAmnt>45</mnthCrrntAmnt>
  <Rcpt dte="07-28-17" No="5948">
   <amnt>38</amnt>
   <descrpt>"06/17" upkeep</descrpt>
  </Rcpt>
 </upkShare> 
 <upkShare month_year="07_17" post_dte="08-16-17" due="08-31-17">
  <OpngBlnce></OpngBlnce>
  <mnthCrrntAmnt>54</mnthCrrntAmnt>
  <Rcpt dte="07-28-17" No="6002">
   <amnt>33</amnt>
   <descrpt>"07/17" upkeep 1</descrpt>
  </Rcpt>
  <Rcpt dte="08-02-17" No="6017">
   <amnt>12</amnt>
   <descrpt>"07/17" upkeep 2</descrpt>
  </Rcpt>
 </upkShare>
</upkeepList>

and through some .xslt transformation i want to display it this way:

<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <title>apartment's building monthly maintenance allowances share bills 
  </title>
 </head>
 <body>
  <h3> apartment's monthly maintenance share bills (2017) </h3> 
  <table border="1" width="56%" style="text-align:center; margin-left:65px; border-collapse:collapse; margin-top:22px">
 <tr> <th>PostDate</th> <!-- maintanance upkeep bills post date --> 
 <th>OpngBalnce</th> <!-- opening balance - OB (starting from 2nd line this OB will read previous CB-->
 <th>MnthCrrntAmnt</th> <!-- monthly current upkeep amount to pay - MUPA -->
 <th>TotAmntToPay</th> <!-- total amount to pay - TAP = OB + MUPA --> 
 <th>TotAmntPd</th> <!-- total amount paid (receipts sum) - TAPD -->
 <th>ClsngBalnce</th> <!-- closing balance CB = TAP - TAPD -->
</tr> 
<tr> <td>01-20-17</td> <!-- PostDate -->
 <td>22</td> <!-- OB initially is 22 -->
 <td>77</td> <!-- MUPA -->
 <td>99</td> <!-- TAP = OB + MUPA -->
 <td>63</td> <!-- TAPD -->
 <td>36</td> <!-- CB = TAP - TAPD -->
</tr>
<tr> <!-- 2nd row -->
 <td>03-17-17</td> 
 <td>36</td> 
 <td>74</td> 
 <td>110</td> 
 <td>50</td> 
 <td>60</td> 
</tr>
<tr> <!-- 3rd row -->
 <td>04-16-17</td> 
 <td>60</td> 
 <td>55</td> 
 <td>115</td> 
 <td>49</td> 
 <td>66</td> 
</tr>
<tr> <!-- 4th row -->
 <td>06-05-17</td> 
 <td>66</td> 
 <td>64</td> 
 <td>130</td> 
 <td>58</td> 
 <td>72</td> 
</tr>
<tr><!-- 5th row -->
 <td>07-16-17</td> 
 <td>72</td> 
 <td>45</td> 
 <td>117</td> 
 <td>38</td> 
 <td>79</td> 
</tr>
<tr><!-- 6th row -->
 <td>08-16-17</td> <!-- PostData -->
 <td>79</td> <!-- OB = prev CB -->
 <td>54</td> <!-- MUPA -->
 <td>133</td> <!-- TAP = OB + MUPA -->
 <td>43</td> <!-- TAPD -->
 <td>90</td> <!-- CB = TAP - TAPD -->
</tr>
</table>
</body>
</html>

Here is a monthly maintenance allowances bills list for an apartment building which I need to display through that xslt transformation over that .xml document.

There are some apartment building shared bills for every apartment which need to be posted up. Upkeep list to be put up .. for people to know how much do they have to pay for utilities they consumed during the month. Water use / consumption mainly ... All previous table acronyms meaning will follow (I'll repeat it once more).

OB = Opening Balance or Initial Balance; that's a fixed value by which I open up my example for line 1; starting from second line, this OB will be as for previous closing balance (CB). ex: OB for line 2 is CB of line 1 (36) OB for line 3 is CB of line 2, and so on (60);

MUPA = monthly current upkeep amount to pay; this is the upkeep amount one should pay for what he/she used;

TAP = total amount to pay; TAP = OB + MUPA - this is the TOTAL amount one would pay;

TAPD = total amount paid (receipts sum); one does pay some amount either with one, two or even three receipts sum of all receipts one paid is total amount paid (TAPD);

CB = closing balance (CB) or amount due is the remaing after TAPD is subtracted from TAP (CB = TAP - TAPD)

The trick is that I just can't figure how am I suppose to take the previous CB over the next line OB.

标签: xml xslt
1条回答
唯我独甜
2楼-- · 2020-05-09 23:32

Consider the following simplified example:

The goal here is to produce an output similar to a bank statement, where each transaction is a line with previous balance, amount and a running total.

The transformation is executed in two passes:

  1. First, the transactions are sorted by date (here, the task is simplified by using an ISO-8601 date that can be sorted as text), and any missing values are coerced to zero;

  2. Next, the sorted values are processed, with the previous balance generated by summing all preceding transactions values. This is not an efficient method - but for relatively small amounts of data it can be convenient due to its simplicity.

XML

<transactions>
    <transaction>
        <date>2019-01-01</date>
        <opening-balance>100</opening-balance>
        <amount/>
    </transaction>
    <transaction>
        <date>2019-03-03</date>
        <opening-balance/>
        <amount>33</amount>
    </transaction>
    <transaction>
        <date>2019-02-02</date>
        <opening-balance/>
        <amount>-22</amount>
    </transaction>
    <transaction>
        <date>2019-05-05</date>
        <opening-balance/>
        <amount>55</amount>
    </transaction>
    <transaction>
        <date>2019-04-04</date>
        <opening-balance/>
        <amount>-44</amount>
    </transaction>
</transactions>

XSLT 1.0 (+ EXSLT)

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:decimal-format name="coerce" NaN="0" />

<xsl:template match="/transactions">
    <!-- FIRST PASS: SORT TRANSACTIONS BY DATE -->
    <xsl:variable name="txs">
        <xsl:for-each select="transaction">
            <xsl:sort select="date"/>
            <!-- COERCE MISSING VALUES TO ZERO -->
            <tx date="{date}" 
                opening-balance="{format-number(opening-balance, '#', 'coerce')}"
                amount="{format-number(amount, '#', 'coerce')}"/>
        </xsl:for-each>
    </xsl:variable>
    <!-- OUTPUT -->
    <statement>
        <xsl:for-each select="exsl:node-set($txs)/tx">
            <line>
                <date>
                    <xsl:value-of select="@date"/>
                </date>
                <xsl:variable name="prev-balance" select="sum(preceding-sibling::tx/@opening-balance) + sum(preceding-sibling::tx/@amount) + @opening-balance" />
                <previous-balance>
                    <xsl:value-of select="$prev-balance"/>
                </previous-balance>
                <amount>
                    <xsl:value-of select="@amount"/>
                </amount>
                <run-total>
                    <xsl:value-of select="@amount + $prev-balance"/>
                </run-total>
            </line>
        </xsl:for-each>
    </statement>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<statement>
  <line>
    <date>2019-01-01</date>
    <previous-balance>100</previous-balance>
    <amount>0</amount>
    <run-total>100</run-total>
  </line>
  <line>
    <date>2019-02-02</date>
    <previous-balance>100</previous-balance>
    <amount>-22</amount>
    <run-total>78</run-total>
  </line>
  <line>
    <date>2019-03-03</date>
    <previous-balance>78</previous-balance>
    <amount>33</amount>
    <run-total>111</run-total>
  </line>
  <line>
    <date>2019-04-04</date>
    <previous-balance>111</previous-balance>
    <amount>-44</amount>
    <run-total>67</run-total>
  </line>
  <line>
    <date>2019-05-05</date>
    <previous-balance>67</previous-balance>
    <amount>55</amount>
    <run-total>122</run-total>
  </line>
</statement>

A more efficient method would use the so-called sibling recursion method to eliminate the repeated summing of same nodes over and over:

XSLT 1.0 (+ EXSLT)

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:decimal-format name="coerce" NaN="0" />

<xsl:template match="/transactions">
    <!-- FIRST PASS: SORT TRANSACTIONS BY DATE -->
    <xsl:variable name="txs">
        <xsl:for-each select="transaction">
            <xsl:sort select="date"/>
            <!-- COERCE MISSING VALUES TO ZERO -->
            <tx date="{date}" 
                opening-balance="{format-number(opening-balance, '#', 'coerce')}"
                amount="{format-number(amount, '#', 'coerce')}"/>
        </xsl:for-each>
    </xsl:variable>
    <!-- OUTPUT -->
    <statement>
        <!-- PROCESS THE FIRST TRANSACTION -->
        <xsl:apply-templates select="exsl:node-set($txs)/tx[1]"/>
    </statement>
</xsl:template>

<xsl:template match="tx">
    <xsl:param name="balance" select="0"/>
    <xsl:variable name="prev-balance" select="$balance + @opening-balance" />
    <xsl:variable name="run-total" select="$prev-balance + @amount" />
    <line>
        <date>
            <xsl:value-of select="@date"/>
        </date>
        <previous-balance>
            <xsl:value-of select="$prev-balance"/>
        </previous-balance>
        <amount>
            <xsl:value-of select="@amount"/>
        </amount>
        <run-total>
            <xsl:value-of select="$run-total"/>
        </run-total>
    </line>
    <!-- PROCESS THE NEXT TRANSACTION -->
    <xsl:apply-templates select="following-sibling::tx[1]">
        <xsl:with-param name="balance" select="$run-total"/>
    </xsl:apply-templates>
</xsl:template>     

</xsl:stylesheet>
查看更多
登录 后发表回答