Math and XSLT
A Russian translation of this article can be found here.
XSLT's full support of XPath's math capabilities lets you do all the basic kinds of arithmetic and a little more. Let's look at a stylesheet that demonstrates these capabilities by using the values from this document:
<numbers> <x>4</x> <y>3.2</y> <z>11</z> </numbers>
Lines A through N of the stylesheet each make (or attempt to make) a different calculation. These calculations use the numbers in the document above and some other numbers that are either hardcoded in the stylesheet or retrieved from functions that return numbers.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml" omit-xml-declaration="yes"/> <xsl:template match="numbers"> A. 4 + 3.2 = <xsl:value-of select="x + y"/> B. 3.2 - 4 = <xsl:value-of select="y - x"/> C. 4 * 3.2 = <xsl:value-of select="x * y"/> D. 11/3.2 = <xsl:value-of select="z div y"/> E. 4 + 3.2 * 11 = <xsl:value-of select="x+y*z"/> F. (4 + 3.2) * 11 = <xsl:value-of select="(x+y)*z"/> G. 11 mod 4 = <xsl:value-of select="z mod x"/> H. 4 + 3.2 + 11 = <xsl:value-of select="sum(*)"/> I. floor(3.2) = <xsl:value-of select="floor(y)"/> J. ceiling(3.2) = <xsl:value-of select="ceiling(y)"/> K. round(3.2) = <xsl:value-of select="round(y)"/> L. 11 + count(*) = <xsl:value-of select="11+count(*)"/> M. 3.2 + string-length("3.2") = <xsl:value-of select="y + string-length(y)"/> N. 11 + "hello" = <xsl:value-of select="z + 'hello'"/> </xsl:template> </xsl:stylesheet>
Before we talk about what each line is doing, let's look at the result of applying the stylesheet to the numbers document.
A. 4 + 3.2 = 7.2 B. 3.2 - 4 = -0.8 C. 4 * 3.2 = 12.8 D. 11/3.2 = 3.4375 E. 4 + 3.2 * 11 = 39.2 F. (4 + 3.2) * 11 = 79.2 G. 11 mod 4 = 3 H. 4 + 3.2 + 11 = 18.2 I. floor(3.2) = 3 J. ceiling(3.2) = 4 K. round(3.2) = 3 L. 11 + count(*) = 14 M. 3.2 + string-length("3.2") = 6.2 N. 11 + "hello" = NaN
The stylesheet has a single template rule for the source tree's numbers element. This template has a series of xsl:value-of instructions whose select attributes use the values of the numbers element's x, y, and z child elements to do various kinds of math. Mathematical expressions like these can use the full power of XPath to say which element or attribute has a number they need; this stylesheet, however, is more concerned with demonstrating the range of mathematical operations available than with using fancy XPath expressions to retrieve elements and attributes from odd parts of a document.
Line A of the template adds the value of x (4) to the value of y (3.2) and puts their sum, 7.2, in the result tree. It's simple, straightforward, and shows that you're not limited to integers for stylesheet math.
Line B subtracts 4 from 3.2 for a result of -0.8. Negative numbers shouldn't pose any difficulties for XSLT processors.
|Warning With some XSLT processors, the use of decimal numbers may introduce a tiny error. For example, the "3.2 - 4" in this example comes out as "-.7999999999999998" on some processors. While being off by .0000000000000002 isn't much, being off at all shows that math is not XSLT's strong point.|
Line C multiplies 4 and 3.2, using the asterisk as the multiplication operator, for an answer of 12.8.
Line D divides the value of the z element, (11), by 3.2, showing an XSLT processor's ability to perform floating-point division. Although most programming languages traditionally use the slash character to represent mathematical division, XPath already uses the slash to separate the steps in an XPath location path (for example, wine/vintage to represent the vintage child element of the wine element). Accordingly, XPath and XSLT use the string "div" for division.
Lines E and F show how parentheses have the same effect on operator precedence that they have in normal math notation: without them, multiplication happens before addition, so that 4 + 3.2 * 11 = 4 + 35.2. With the parentheses around the "4+3.2", that happens first, so that (4 + 3.2) * 11 = 7.2 * 11.
Line G demonstrates the mod operator, which shows the remainder if you divide the first term by the second. The example shows that 11 mod 4 equals 3, because 4 goes into 11 twice with 3 left over. This operator is great for checking whether one number divides into another evenly; just check whether the larger number mod the smaller equals zero.
Line H demonstrates the sum() function. With a node list as an argument, it sums all the numbers in the list. In the example the asterisk means "all the children of the context node" -- the numbers element's x, y, and z children.
Lines I and J demonstrate the floor() and ceiling() functions. If you pass either of these an integer, they return that integer. If you pass floor() a non-integer, it returns the highest integer below that number. In the example, floor(3.2) is 3. The ceiling function returns the smallest integer above a non-integer number; in the example, ceiling(3.2) equals 4.
Line K's round() function rounds off a non-integer by returning the closest integer. When 3.2 is passed to it, it returns 3; passing 3.5 or 3.6 to it would cause it to return 4.
Line L incorporates another XPath function, count(), which returns the number of nodes in the set passed to it as an argument. XPath offers several functions that, while not explicitly mathematical, return numbers and can be used for any calculations you like: count(), last(), position(), and string-length(). Line M demonstrates string-length(), which returns the length of a string.
Also in Transforming XML
Line N demonstrates what happens when you try to perform math with something that isn't a number: when 11 gets added to the string "hello", the result is the string "NaN", an abbreviation for "Not a Number." When you pull a number out of an element's content or attribute value and then use it for a calculation, you can't always be sure that what you pulled is really a number, so XSLT's clearly defined behavior for the unworkable case makes it easier to check for and cope with in your code.
XSLT is about manipulating text, not numbers, but you can build on the mathematical operations provided as part of XSLT to perform more complicated calculations. For example, the following stylesheet, which accepts any document as input, computes the value of pi. The precision of the result depends on the value of the iterations variable.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text"/> <!-- Compute pi. Based on Leibniz's algorithm that pi/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11... which I did as pi = 4 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11... --> <xsl:variable name="iterations" select="80000"/> <xsl:template name="pi"> <!-- named template called by main template below --> <xsl:param name="i">1</xsl:param> <xsl:param name="piValue">0</xsl:param> <xsl:choose> <!-- If there are more iterations to do, add the passed value of pi to another round of calculations. --> <xsl:when test="$i <= $iterations"> <xsl:call-template name="pi"> <xsl:with-param name="i" select="$i + 4"/> <xsl:with-param name="piValue" select="$piValue + (4 div $i) - (4 div ($i + 2))"/> </xsl:call-template> </xsl:when> <!-- If no more iterations to do, add computed value to result tree. --> <xsl:otherwise> <xsl:value-of select="$piValue"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="/"> <xsl:call-template name="pi"/> </xsl:template> </xsl:stylesheet>
The repetition is implemented using a recursive named template. With the iterations setting shown, the stylesheet creates this result:
With that many iterations, the answer is only accurate up to the first four digits after the decimal. Of course, if you want to compute the value of pi seriously, there are many more appropriate languages, but it's nice to know that you can push XSLT to do some fairly complex math when necessary.