Reputation: 21
long time reader, 1st time poster here. I'm usually able to get quite a lot of info from other posts on the site but i can't find a solution for this particular problem.
Using xslt, I'm currently able to show a sub total of each client invoice then the total of those invoices by adding another variable of $grandtotal
to my below xslt template and adding the $sum
to it in each iteration of the loop.
What I now need to do is to find the top 5 highest totaling invoices.
This is a shortened version of my XML:
<bits>
<client type="Commercial">
<clientid>1</clientid>
<inv>
<invno>1</invno>
<product>
<productid>321</productid>
<productprice>99.00</productprice>
<totalqty>2</totalqty>
</product>
<product>
<productid>333</productid>
<productprice>299.00</productprice>
<totalqty>1</totalqty>
</product>
</inv>
<inv>
<invno>2</invno>
<product>
<productid>321</productid>
<productprice>99.00</productprice>
<totalqty>2</totalqty>
</product>
<product>
<productid>333</productid>
<productprice>299.00</productprice>
<totalqty>2</totalqty>
</product>
</inv>
</client>
<client type="Government">
<clientid>2</clientid>
<inv>
<invno>3</invno>
<product>
<productid>399</productid>
<productprice>1469.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>354</productid>
<productprice>15.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>311</productid>
<productprice>58.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>341</productid>
<productprice>199.00</productprice>
<totalqty>1</totalqty>
</product>
</inv>
</client>
</bits>
I have used the following code to sum the invoice total for each client:
<xsl:for-each select="//client">
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="inv/product"/>
</xsl:call-template>
</xsl:for-each>
<xsl:template name="sum">
<xsl:param name="nodes" />
<xsl:param name="sum" select="0" />
<xsl:variable name="current" select="$nodes[1]" />
<xsl:if test="$current">
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="sum" select="$sum + $current/totalqty * $current/productprice" />
</xsl:call-template>
</xsl:if>
<xsl:if test="not($current)">
<xsl:value-of select="$sum" />
</xsl:if>
What I'd like to do is reuse this code to also show the 5 highest summing invoice and their corresponding <clientid>
and type
eg:
Top 5:
In the past i have used a for loop <xsl:for-each select=...>
<xsl:sort select="Total" data-type="number" order="descending"/>
...<xsl:if test="position()<6">
to show top 5 but this is looking at a stored value.
I will need another solution.
I need some tips at this point as I'm still very new to markup!
Upvotes: 2
Views: 474
Reputation: 12729
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="xsl exsl">
<xsl:output method="html" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<table>
<th><td>Client id</td><td>Invoice no</td><td>Invoice total</td>
<td>Type</td></th>
<xsl:variable name="rows">
<xsl:apply-templates select="client/inv" />
</xsl:variable>
<xsl:for-each select="exsl:node-set($rows)/tr">
<xsl:sort select="td[4]" data-type="number" order="descending" />
<xsl:variable name="rank" select="position()" />
<xsl:copy-of select="self::node()[$rank < 6]" />
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="inv">
<tr>
<td><xsl:value-of select="../clientid" /></td>
<td><xsl:value-of select="invno" /></td>
<xsl:variable name="gross-prices">
<xsl:for-each select="product">
<t><xsl:value-of select="productprice * totalqty" /></t>
</xsl:for-each>
</xsl:variable>
<td><xsl:value-of select="sum( exsl:node-set($gross-prices)/t)" /></td>
<td><xsl:value-of select="../@type" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
...when applied to this input...
<bits>
<client type="Commercial">
<clientid>1</clientid>
<inv>
<invno>1</invno>
<product>
<productid>321</productid>
<productprice>99.00</productprice>
<totalqty>2</totalqty>
</product>
<product>
<productid>333</productid>
<productprice>299.00</productprice>
<totalqty>1</totalqty>
</product>
</inv>
<inv>
<invno>2</invno>
<product>
<productid>321</productid>
<productprice>99.00</productprice>
<totalqty>2</totalqty>
</product>
<product>
<productid>333</productid>
<productprice>299.00</productprice>
<totalqty>2</totalqty>
</product>
</inv>
</client>
<client type="Government">
<clientid>2</clientid>
<inv>
<invno>3</invno>
<product>
<productid>399</productid>
<productprice>1469.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>354</productid>
<productprice>15.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>311</productid>
<productprice>58.00</productprice>
<totalqty>1</totalqty>
</product>
<product>
<productid>341</productid>
<productprice>199.00</productprice>
<totalqty>1</totalqty>
</product>
</inv>
</client>
</bits>
...yields...
<table>
<th>
<td>Client id</td>
<td>Invoice no</td>
<td>Invoice total</td>
<td>Type</td>
</th>
<tr>
<td>2</td>
<td>3</td>
<td>1741</td>
<td>Government</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>796</td>
<td>Commercial</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>497</td>
<td>Commercial</td>
</tr>
</table>
As long as we have access to node-set(), we don't need to fold or divide-and-conquer to calculate sums. We can simply use the native sum() function.
Upvotes: 1