Inside an <xsl:eval> expression or <xsl:script> block, you have access to a single node in the XML source through the this pointer. But through this node, the entire source document can be reached. Script in XSL thus has the potential to aggregate information in the source and output the results. This section shows the use of the this pointer, selectNodes, and nodeTypedValue to perform some common aggregations.
The following data fragment shows a portfolio containing current stock quotes. Aggregations of interest might include the total volume of trading for these stocks and the average percent change.
<portfolio xmlns="xmlns:dt="urn:schemas-microsoft-com:datatypes"> <date dt:dt="dateTime">1998-10-13T15:56:00</date> <stock> <symbol>ACXM</symbol> <name>acxiom corp</name> <price dt:dt="fixed.14.4">18.875</price> <change dt:dt="fixed.14.4">-1.250</change> <percent dt:dt="fixed.14.4">-6.21</percent> <volume dt:dt="fixed.14.4">0.23</volume> </stock> ... </portfolio>
Place <xsl:eval> elements in the XSL template where the aggregated values are to appear. Since the calculations necessary to compute averages and totals do not fit in a single line of script, user-defined functions perform these calculations.
<DIV>Average change: <B><xsl:eval>averageChange(this)</xsl:eval></B></DIV> <DIV>Total volume: <B><xsl:eval>totalVolume(this)</xsl:eval></B></DIV>
The averageChange and totalVolume functions called from the <xsl:eval> elements are defined in an <xsl:script> block.
<xsl:script><![CDATA[ function totalVolume(node) { total = 0; volumes = node.selectNodes("/portfolio/stock/volume"); for (v = volumes.nextNode(); v; v = volumes.nextNode()) total += v.nodeTypedValue; return formatNumber(total, "#") + " million shares"; } function averageChange(node) { total = 0; percents = node.selectNodes("/portfolio/stock/percent"); count = percents.length; for (p = percents.nextNode(); p; p = percents.nextNode()) total += p.nodeTypedValue; return formatNumber(total / count, "#.0") + "%"; } ]]></xsl:script>
In the totalVolume function, the selectNodes method is used to query the entire document for "volume" elements. The query "/portfolio/stock/volume" finds elements relative to the document root regardless of the node the query is performed on. The particular node passed into the function as a parameter is not important and can be any node in the source tree.
Once the volume elements have been collected, their values are extracted using the nodeTypedValue method and added together. This leverages the data type attributes in the XML source and eliminates the need for custom type conversion functions. The total is formatted by rounding it to the nearest integer and converting to a string.
The averageChange function works in a nearly identical fashion, but divides the total by the number of items to compute an average.
Try it! The script above generates the total volume and average change information in the Stock Sorter Sample.