469,625 Members | 1,089 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,625 developers. It's quick & easy.

[XSLT] averaging

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
ms****@math.wustl.edu Fine print:
http://www.math.wustl.edu/~msh210/ ... legal.html
Dec 28 '05 #1
2 1701
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
Dec 30 '05 #2
On Fri, 30 Dec 2005, Ed Beroset wrote, in small part:
Sure.


Wow. Thanks so much for your response -- beyond the call of, Usenet duty.
Or whatever. Anyway, thanks a lot.

Happy new year,

Michael Hamm It's not who you know, it's whom.
AM, Math, Wash. U. St. Louis Joan Rivers
ms****@math.wustl.edu Fine print:
http://www.math.wustl.edu/~msh210/ ... legal.html
Jan 2 '06 #3

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

2 posts views Thread by ted | last post: by
3 posts views Thread by Teksure | last post: by
1 post views Thread by Rotten Spice | last post: by
1 post views Thread by Sergey Dubinets | last post: by
12 posts views Thread by Chris | last post: by
1 post views Thread by Kid Programmer | last post: by
reply views Thread by gheharukoh7 | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.