Calculate and show sum from nested elements XSLT

2019-07-27 05:26发布

问题:

The XML is:

<?xml version="1.0" encoding="UTF-8"?>
<rows>

  <row id="FOLDER1">
    <cell image="blank.gif">Folder 1</cell>
    <cell></cell>
    <cell></cell>
    <cell></cell>
    <cell></cell>
    <cell></cell>
    <cell></cell>
    <cell sum="1">$23</cell>
    <row id="FOLDER2">
      <cell image="blank.gif">Folder 2</cell>
      <cell></cell>
      <cell></cell>
      <cell></cell>
      <cell></cell>
      <cell></cell>
      <cell></cell>
      <cell sum="2">$11</cell>
      <row id="FOLDER3">
        <cell image="blank.gif">Folder 3</cell>
        <cell></cell>
        <cell></cell>
        <cell></cell>
        <cell></cell>
        <cell></cell>
        <cell></cell>
        <cell sum="3">$44</cell>
        <row id="pro1">
          <cell image="blank.gif">Product 1</cell>
          <cell>324234</cell>
          <cell>3.00</cell>
          <cell>Kilo</cell>
          <cell>1.00</cell>
          <cell>No</cell>
          <cell>€ 33.33</cell>
          <cell>€ 33.33</cell>
        </row>
        <row id="pro2">
          <cell image="blank.gif">Product 2</cell>
          <cell>4354354</cell>
          <cell>1.00</cell>
          <cell>Kilo</cell>
          <cell >0.50</cell>
          <cell>No</cell>
          <cell>€ 2.53</cell>
          <cell>€ 1.26</cell>
        </row>

      </row>
      <row id="pro3">
        <cell image="blank.gif">Product 3</cell>
        <cell>435436</cell>
        <cell>10.00</cell>
        <cell>Kilo</cell>
        <cell>Yes</cell>
        <cell>€ 0.36</cell>
        <cell>€ 3.60</cell>
      </row>

    <row id="pro4">
      <cell image="blank.gif">Product 4</cell>
      <cell>435435</cell>
      <cell>3.28</cell>
      <cell>Kilo</cell>
      <cell>1.00</cell>
      <cell>No</cell>
      <cell>€ 17.38</cell>
      <cell>€ 17.38</cell>
    </row>
    <row id="NEWCAT_59">
        <cell image="blank.gif">&lt;strong&gt;+ Add new category&lt;/strong&gt;</cell>
      </row>
      <row id="NEWSEMING_59">
        <cell image="blank.gif">&lt;strong&gt;+ Add new product&lt;/strong&gt;</cell>
      </row>
   </row>
  </row>

</rows>

The XSLt is:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />

<xsl:template match="/">
  <html>
    <head/>
    <body>
      <xsl:apply-templates select="rows" />
    </body>
  </html>
</xsl:template>

<xsl:template match="rows">
  <table>
    <tbody><tr>
      <xsl:for-each select="row[1]/cell">
        <th><xsl:value-of select="position()" /></th>
      </xsl:for-each>
    </tr>
    <xsl:apply-templates select="descendant::row" />  
  </tbody>
  </table>
</xsl:template>  

<xsl:template match="row[contains(cell[1],'Add new ')]" />

<xsl:template match="row">
  <tr>
    <xsl:apply-templates select="cell" />
  </tr>  
</xsl:template>

<xsl:template name="style">
  <xsl:param name="margin" select="0" />
  <xsl:param name="display" select="'inline'" />
  <xsl:variable name="v1">
    <xsl:if test="$margin &gt; 0">
      <xsl:value-of select="concat('margin-left:',$margin,'px;')" />
    </xsl:if>
    <xsl:if test="$display">
      <xsl:value-of select="concat('display: ',$display,';')" />
    </xsl:if>
  </xsl:variable>  
  <xsl:if test="$v1">
    <xsl:attribute name="style"><xsl:value-of select="$v1" /></xsl:attribute>
  </xsl:if>
</xsl:template>

<xsl:template match="cell[position()=1][starts-with(../@id,'FOLDER')]" priority="3">
  <!-- First column for FOLDER rows -->
  <td>
    <img alt="neso" src="http://www.deporteandaluz.com/web/includes/dhtmlx/imgs/csh_winstyle/minus.gif">
      <xsl:call-template name="style">
        <xsl:with-param name="margin" select="(count(ancestor::row) - 1)*20" />
        <xsl:with-param name="display" select="''" />
       </xsl:call-template>
     </img>
  <xsl:value-of select="." />
  </td>  
</xsl:template>

<xsl:template match="cell[position()=1]" priority="2">
  <!-- First column for non-FOLDER rows -->
  <td>
     <div>
      <xsl:call-template name="style">
        <xsl:with-param name="margin" select="(count(ancestor::row) - 1)*20" />
       </xsl:call-template>
       <xsl:value-of select="." />
     </div>
  </td>  
</xsl:template>

<xsl:template match="cell" priority="1">
  <!-- Subsequent columns -->
  <td>
     <div>
      <xsl:call-template name="style">
        <xsl:with-param name="margin" select="20" />
       </xsl:call-template>
       <xsl:value-of select="." />
     </div>
  </td>  
</xsl:template>

</xsl:stylesheet>

I need to calculate the sum by adding nested folder's last cells. FOLDER1 instead of $23 it should have 23+11+44,FOLDER2 should have 11+44 and FOLDER3 just 44 because there aren't nested rows there.

The expected output is:

<html>
  <head>
    <META http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <table>
      <tbody>
        <tr>
          <th>1</th>
          <th>2</th>
          <th>3</th>
          <th>4</th>
          <th>5</th>
          <th>6</th>
          <th>7</th>
          <th>8</th>
        </tr>
        <tr>
          <td><img alt="neso" src="http://www.deporteandaluz.com/web/includes/dhtmlx/imgs/csh_winstyle/minus.gif" style="">Folder 1</td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">$78</div>
          </td>
        </tr>
        <tr>
          <td><img alt="neso" src="http://www.deporteandaluz.com/web/includes/dhtmlx/imgs/csh_winstyle/minus.gif" style="margin-left:20px;">Folder 2</td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">$55</div>
          </td>
        </tr>
        <tr>
          <td><img alt="neso" src="http://www.deporteandaluz.com/web/includes/dhtmlx/imgs/csh_winstyle/minus.gif" style="margin-left:40px;">Folder 3</td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">
            </div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">$44</div>
          </td>
        </tr>
        <tr>
          <td>
            <div style="margin-left:60px;display: inline;">Product 1</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">324234</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">3.00</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">Kilo</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">1.00</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">No</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 33.33</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 33.33</div>
          </td>
        </tr>
        <tr>
          <td>
            <div style="margin-left:60px;display: inline;">Product 2</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">4354354</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">1.00</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">Kilo</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">0.50</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">No</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 2.53</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 1.26</div>
          </td>
        </tr>
        <tr>
          <td>
            <div style="margin-left:40px;display: inline;">Product 3</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">435436</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">10.00</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">Kilo</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">Yes</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 0.36</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 3.60</div>
          </td>
        </tr>
        <tr>
          <td>
            <div style="margin-left:40px;display: inline;">Product 4</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">435435</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">3.28</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">Kilo</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">1.00</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">No</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 17.38</div>
          </td>
          <td>
            <div style="margin-left:20px;display: inline;">€ 17.38</div>
          </td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

回答1:

Add the following templates to your stylesheet in order to produce the desired result from the current XML input.

<xsl:template match="cell[@sum]" priority="1.5">
    <td>
        <div>
            <xsl:call-template name="sum">
                <xsl:with-param name="currentRow" select=".." />
            </xsl:call-template>
        </div>
    </td>  
</xsl:template>

<xsl:template name="sum">
    <xsl:param name="currentRow" />
    <xsl:param name="sum" select="0"/>
    <xsl:choose>
        <xsl:when test="$currentRow//cell[@sum]">
            <xsl:call-template name="sum">
                <xsl:with-param name="currentRow" 
                                select="($currentRow//row[cell[@sum]])[1]" />
                <xsl:with-param name="sum" 
                                select="$sum+
                                        number(
                                          translate(
                                            $currentRow/cell[@sum], 
                                            '$',
                                            ''))"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text>$</xsl:text>
            <xsl:value-of select="$sum"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>


回答2:

One way to achieve the summation that you require is to start with the style-sheet that you have and make the following changes...

Do this...

  1. Strip the '$' character from your input document, if this is possible. XML is focussed on data and the semantic, not the presentational, so presentational characters are best left out. However, if this is not possible, let us know. I can make an alternative solution that uses the input document as is, with the $ sign in, but it will be more complicated.

  2. Simply add the following template...

    <xsl:template match="cell[@sum]" priority="1.5">
      <!-- Summation columns -->
      <td>
         <div>
          <xsl:call-template name="style">
            <xsl:with-param name="margin" select="20" />
           </xsl:call-template>
           <xsl:value-of select="concat( '$', sum( ../descendant-or-self::row/cell[@sum]))" />
         </div>
      </td>  
    </xsl:template>
    

Caveats

This solution relies on the following assumptions. If any of these assumptions are false, please advise and I will adapt the solution accordingly.

  1. Precisely one cell in a each sum-able row has attribute sum. These contain the values to be added.
  2. The content of the sum-able cells contain just a number. It excludes any presentation characters such as '$'