[XSLT] averaging

M

Michael Hamm

I have the following XML file (simplified from the actual):

<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>

and the following stylesheet (again simplified):
<xsl:template match="/"><html><body><table>
<xsl:for-each select="r/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:value-of
select="(v1 + (2 * v2) + (3 * v3)) div (v1 + v2 + v3)" /></td>
</tr></xsl:for-each>
<tfoot>
<tr><th scope="row" colspan="5">Average when "s"</th>
<td> [...] </td></tr>
<tr><th scope="row" colspan="5">Average when "i"</th>
<td> [...] </td></tr></tfoot></table></body></html>

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?

Thanks,

Michael Hamm It's not who you know, it's whom.
AM, Math, Wash. U. St. Louis Joan Rivers
(e-mail address removed) Fine print:
http://www.math.wustl.edu/~msh210/ ... legal.html
 
E

Ed Beroset

Michael said:
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:eek:utput 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:eek:therwise>0</xsl:eek:therwise>
</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:eek:therwise>0</xsl:eek:therwise>
</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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top