Michael Hamm wrote:
In place of the '[...]' I wish to have the weighted averages, as follows.
In place of the first '[...]' I want
( 1 + 2*2 + 3*3 + 7 + 2*8 + 3*9 ) / ( 1 + 2 + 3 + 7 + 8 + 9 )
(which is the same as the weighted average of the various weighted
averages already computed), and in place of the second '[...]' I want
( 4 + 2*5 + 3*6 + 10 + 2*11 + 3*12 + 13 + 2*14 + 3* 15 ) divided
by ( 4+ 5+ 6+ 10 + 11 + 12 + 13 + 14 + 15 ).
Is there a way to do this in XSLT?
Sure. One way to do it directly is this:
<xsl:value-of
select="(sum(o[si='s']/v1)+2*sum(o[si='s']/v2)+3*sum(o[si='s']/v3)) div
(sum(o[si='s']/v1)+sum(o[si='s']/v2)+sum(o[si='s']/v3))"/>
That works, but it has some limitations. First, it's very long and hard
to read. Second, you have to redo the entire calculation for [si='i']
which is a maintenance problem. Third, if you ever need to change the
calculation, you'll have to change it in multiple places, which is
another maintenance problem. A better strategy is to separate the
selection from the calculation, and the way to do that is to use templates.
Also, I notice that you use xsl:for-each in your example where
xsl:apply-templates would be a better choice. Early on when I was
learning XSLT, it became clear to me that whenever I found myself using
xsl:for-each, it was probably better done using xsl:apply-templates.
I wrote a new XSLT which is longer, but it has the advantage that it
doesn't have the limitations mentioned above.
Note that the calculations use recursion, which is the usual way to get
things done in XSLT. You'll find it useful as you create more complex
calculations in XSLT.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
<xsl:output method="html" indent="yes" />
<xsl:template match="r">
<html>
<body>
<table>
<thead>
<tr>
<th>n</th>
<th>si</th>
<th>v1</th>
<th>v2</th>
<th>v3</th>
<th>calculation</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="o" />
</tbody>
<tfoot>
<tr>
<th scope="row" colspan="5">Average when "s"</th>
<td><xsl:call-template name="avg">
<xsl:with-param name="list" select="o[si='s']"/>
</xsl:call-template></td>
</tr>
<tr>
<th scope="row" colspan="5">Average when "i"</th>
<td><xsl:call-template name="avg">
<xsl:with-param name="list" select="o[si='i']"/>
</xsl:call-template></td>
</tr>
</tfoot>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="o">
<tr>
<td><xsl:value-of select="n" /></td>
<td><xsl:value-of select="si" /></td>
<td><xsl:value-of select="v1" /></td>
<td><xsl:value-of select="v2" /></td>
<td><xsl:value-of select="v3" /></td>
<td><xsl:call-template name="avg">
<xsl:with-param name="list" select="."/>
</xsl:call-template></td>
</tr>
</xsl:template>
<xsl:template match="o" mode="calcnum">
<xsl:value-of select="(v1 + (2 * v2) + (3 * v3))"/>
</xsl:template>
<xsl:template match="o" mode="calcdenom">
<xsl:value-of select="(v1 + v2 + v3)" />
</xsl:template>
<xsl:template name="avg">
<xsl:param name="list"/>
<xsl:variable name="num">
<xsl:call-template name="numerator">
<xsl:with-param name="list" select="$list"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="denom">
<xsl:call-template name="denominator">
<xsl:with-param name="list" select="$list"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$num div $denom"/>
</xsl:template>
<xsl:template name="numerator">
<xsl:param name="list"/>
<xsl:choose>
<xsl:when test="$list">
<xsl:variable name="first">
<xsl:apply-templates select="$list[1]" mode="calcnum"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:call-template name="numerator">
<xsl:with-param name="list" select="$list[position()!=1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$first + $rest"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="denominator">
<xsl:param name="list"/>
<xsl:choose>
<xsl:when test="$list">
<xsl:variable name="first">
<xsl:apply-templates select="$list[1]" mode="calcdenom"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:call-template name="denominator">
<xsl:with-param name="list" select="$list[position()!=1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$first + $rest"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
For completeness, here's your original data file:
<?xml version="1.0"?>
<r>
<o><n>1</n><si>s</si><v1>1</v1><v2>2</v2><v3>3</v3></o>
<o><n>2</n><si>i</si><v1>4</v1><v2>5</v2><v3>6</v3></o>
<o><n>3</n><si>s</si><v1>7</v1><v2>8</v2><v3>9</v3></o>
<o><n>5</n><si>i</si><v1>10</v1><v2>11</v2><v3>12</v3></o>
<o><n>6</n><si>i</si><v1>13</v1><v2>14</v2><v3>15</v3></o>
</r>
Ed