xslt, math functions, numeric methods
1. | Test For Numeric Values? | |||||||
> I want to test a node value to see whether <xsl:if test="string(number(.))='NaN'"> <xsl:attribute name="align">right</xsl:attribute> </xsl:if> | ||||||||
2. | A library of trig functions | |||||||
Sourceforge - a library of trig functions by our own Dimitre! | ||||||||
3. | Testing for set membership | |||||||
You are trying to test "is $d one of the nodes in descendant-or-self::*". Try writing this as count(.|descendant-or-self::*) != count(descendant-or-self::*) or better, if you are prepared to use an extension function boolean(set:intersection(., descendant-or-self::*)) using the EXSLT extension library. (This is better because the pure XSLT solution navigates the descendant-or-self axis twice.) Another approach to this problem is to work from both ends: find the intersection of the descendants of $r and the ancestors of $d, using: set:intersection($r/descendant-or-self::*, $d/ancestor-or-self::*)[criteria()] If you don't want to use set:intersection, the standard XPath expression to get the intersection of P and Q is P[count(.|Q) = count(Q)] | ||||||||
4. | Simple computations in XSLT | |||||||
Uing simple expressions such as <xsl:variable name="sum" select="$a + $b"/> <xsl:variable name="difference" select="$a - $b"/> <xsl:variable name="product" select="$a * $b"/> | ||||||||
5. | How to accumulate subtotals at each grouping level | |||||||
To convert <doc> <element name="a" value="1" /> <element name="a" value="2" /> <element name="a" value="3" /> <element name="a" value="2" /> <element name="b" value="1" /> <element name="b" value="1" /> <element name="b" value="3" /> <element name="b" value="4" /> <element name="a" value="2" /> <element name="a" value="3" /> <element name="a" value="6" /> <element name="a" value="2" /> <element name="c" value="1" /> <element name="c" value="4" /> <element name="c" value="2" /> <element name="c" value="1" /> </doc> by grabbing consecutive runs of the same @name into a containing section element, and adding a total for each section, to produce: <section> <element name="a" value="1"/> <element name="a" value="2"/> <element name="a" value="3"/> <element name="a" value="2"/> <total>8</total> </section> <section> <element name="b" value="1"/> <element name="b" value="1"/> <element name="b" value="3"/> <element name="b" value="4"/> <total>9</total> </section> <section> <element name="a" value="2"/> <element name="a" value="3"/> <element name="a" value="6"/> <element name="a" value="2"/> <total>13</total> </section> <section> <element name="c" value="1"/> <element name="c" value="4"/> <element name="c" value="2"/> <element name="c" value="1"/> <total>8</total> </section> You could do <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Tranform" version="1.0"> <xsl:template match="doc"> <xsl:apply-templates mode="sec" select="*[1]"/> </xsl:template> <xsl:template mode="sec" match="*"> <xsl:variable name="n" select="@name"/> <section> <xsl:apply-templates mode="elem" select="."/> </section> <xsl:apply-templates mode="sec" select="following-sibling::*[not(@name=$n)][1]"/> </xsl:template> <xsl:template mode="elem" match="*"> <xsl:variable name="n" select="@name"/> <xsl:param name="t" select="0"/> <xsl:copy-of select="."/> <xsl:apply-templates mode="elem" select="following-sibling::*[position()=1 and (@name=$n)]"> <xsl:with-param name="t" select="$t+@value"/> </xsl:apply-templates> <xsl:if test= "not(following-sibling::*[position()=1 and (@name=$n)])"> <total> <xsl:value-of select="$t+@value"/> </total> </xsl:if> </xsl:template> </xsl:stylesheet> | ||||||||
6. | Subtraction Doesnt work | |||||||
>For some reason, the first two variable declarations work fine, but the >third one gives this error: >pattern = '$pm-$hourMinutes' Extra illegal tokens: '$', 'hourMinutes' > > <xsl:variable name="hours" select="round($pm div 60)"/> > <xsl:variable name="hourMinutes" select="$hours*60"/> > <xsl:variable name="minutes" select="$pm-$hourMinutes"/> > >Can't I subtract in XSL? The problem is that a variable name is an XML QName, which can include hyphens. Without any spaces, the select value is parsed as '$' 'pm-' '$' 'hourMinutes' - in other words, a variable indicator, the name of the variable 'pm-', and then another variable indicator, which isn't legal there. Instead, try <xsl:variable name="minutes" select="$pm - $hourMinutes"/> | ||||||||
7. | Total | |||||||
XML output is the default. Elements (within templates) that are not associated with the XSL namespace (i.e., they don't have the xsl: prefix) are going to be in your output. If the field is numeric, use the sum() function -- variables aren't necessary. The argument to the function is a node-set containing the numbers to be totaled. Indicate the node-set by using an XPath expression that identifies the appropriate nodes in the original XML. Here is an example where this expression is very explicit: '/doc/num' matches element nodes named 'num' that are children of elements named 'doc' that are children of the root node. Given this XML: <?xml version="1.0"?> <doc> <num>1</num> <num>7</num> <notnum>hello world</notnum> <num>4</num> </doc> Here is the simplest XSL that gives you the total of all 'num' element children of 'doc': <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:element name="grandTotal"> <xsl:value-of select="sum(/doc/num)"/> </xsl:element> </xsl:template> </xsl:stylesheet> ...This is exactly the same as: <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <grandTotal><xsl:value-of select="sum(/doc/num)"/></grandTotal> </xsl:template> </xsl:stylesheet> ...And if you really need to use variables: <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:variable name="total" select="sum(/doc/num)"/> <grandTotal><xsl:value-of select="$total"/></grandTotal> </xsl:template> </xsl:stylesheet> | ||||||||
8. | How to select Node with maximum attribute value | |||||||
<xsl:template name="get-max"> <xsl:param name="nodes"/> <xsl:choose> <xsl:when test="$nodes"> <xsl:variable name="max-of-rest"> <xsl:call-template name="get-max"> <xsl:with-param name="nodes" select="$nodes[position()!=1]"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="nodes[1]/@mid > $max-of-rest"> <xsl:value-of select="nodes[1]/@mid"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$max-of-rest"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:value-of select="-1 div 0"/> <!-- minus infinity --> </xsl:otherwise> </xsl:choose> </xsl:template> Later note: > I want to use the variable in an XPath > outside of the xsl:for-each, ie in > another scope. David Carlisle fills in, <xsl:variable name="x"> either my solution or Mike Kay's </xsl:variable> then you can use $x and it'll coerce from a RTF to a string to a number if need be. | ||||||||
9. | Hex arithmatic in XSLT | |||||||
This template adds hex values F0F0F0F0 and 0F0F0F0F to come up with FFFFFFFF as it should be. This was tested with Xalan 1.0.1. <?xml version="1.0"?> <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:call-template name="hexMath"> <xsl:with-param name="op1" select="'F0F0F0F0'"/> <xsl:with-param name="oper" select="'+'"/> <xsl:with-param name="op2" select="'0F0F0F0F'"/> </xsl:call-template> </xsl:template> <xsl:template name="hexMath"> <xsl:param name="op1"/> <xsl:param name="oper"/> <xsl:param name="op2"/> <xsl:variable name="numOp1"> <xsl:call-template name="hexToDec"> <xsl:with-param name="hexVal" select="$op1"/> <xsl:with-param name="decVal" select="0"/> </xsl:call-template> </xsl:variable> <xsl:variable name="numOp2"> <xsl:call-template name="hexToDec"> <xsl:with-param name="hexVal" select="$op2"/> <xsl:with-param name="decVal" select="0"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$oper = '+'"> <xsl:variable name="res"> <xsl:call-template name="decToHex"> <xsl:with-param name="decVal" select="$numOp1 + $numOp2"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$res"/> </xsl:when> <xsl:when test="$oper = '-'"> <xsl:variable name="res"> <xsl:call-template name="decToHex"> <xsl:with-param name="decVal" select="$numOp1 - $numOp2"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$res"/> </xsl:when> <xsl:when test="$oper = '*'"> <xsl:variable name="res"> <xsl:call-template name="decToHex"> <xsl:with-param name="decVal" select="$numOp1 * $numOp2"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$res"/> </xsl:when> <xsl:when test="$oper = 'div'"> <xsl:variable name="res"> <xsl:call-template name="decToHex"> <xsl:with-param name="decVal" select="$numOp1 div $numOp2"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$res"/> </xsl:when> <xsl:when test="$oper = 'mod'"> <xsl:variable name="res"> <xsl:call-template name="decToHex"> <xsl:with-param name="decVal" select="$numOp1 mod $numOp2"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$res"/> </xsl:when> <xsl:otherwise> <xsl:message>Error! Unknown operation!</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="singleDecToHex"> <xsl:param name="num"/> <xsl:choose> <xsl:when test="$num < 16 and $num >= 0"> <xsl:variable name="table" select="'0123456789ABCDEF'"/> <xsl:value-of select="substring($table,$num + 1,1)"/> </xsl:when> <xsl:otherwise> <xsl:message> Number to convert to hexadecimal out of range</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="singleHexToDec"> <xsl:param name="hex"/> <xsl:variable name="table" select="'0123456789ABCDEF'"/> <xsl:value-of select="string-length(substring-before($table,$hex))"/> </xsl:template> <xsl:template name="findPower"> <xsl:param name="value"/> <xsl:param name="currPower"/> <xsl:param name="multiplier"/> <xsl:param name="accumulator"/> <xsl:choose> <xsl:when test="$value < $accumulator"> <xsl:value-of select="$accumulator div $multiplier"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="findPower"> <xsl:with-param name="value" select="$value"/> <xsl:with-param name="currPower" select="$currPower + 1"/> <xsl:with-param name="multiplier" select="$multiplier"/> <xsl:with-param name="accumulator" select="$accumulator * $multiplier"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="decToHex"> <xsl:param name="decVal"/> <xsl:variable name="power"> <xsl:call-template name="findPower"> <xsl:with-param name="value" select="$decVal"/> <xsl:with-param name="currPower" select="0"/> <xsl:with-param name="multiplier" select="16"/> <xsl:with-param name="accumulator" select="1"/> <!-- everything to 0 power is 1 --> </xsl:call-template> </xsl:variable> <xsl:call-template name="decToHexIter"> <xsl:with-param name="decVal" select="$decVal"/> <xsl:with-param name="hexVal" select="''"/> <xsl:with-param name="power" select="$power"/> </xsl:call-template> </xsl:template> <xsl:template name="decToHexIter"> <xsl:param name="decVal"/> <xsl:param name="hexVal"/> <xsl:param name="power"/> <xsl:choose> <xsl:when test="$decVal > 0"> <xsl:variable name="currDecVal" select="($decVal - ($decVal mod $power)) div $power"/> <xsl:variable name="hexDigit"> <xsl:call-template name="singleDecToHex"> <xsl:with-param name="num" select="$currDecVal"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="decToHexIter"> <xsl:with-param name="decVal" select="$decVal - ($currDecVal * $power)"/> <xsl:with-param name="hexVal" select="concat($hexVal,$hexDigit)"/> <xsl:with-param name="power" select="$power div 16"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$hexVal"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="raiseToPower"> <xsl:param name="number"/> <xsl:param name="power"/> <xsl:call-template name="raiseToPowerIter"> <xsl:with-param name="multiplier" select="$number"/> <xsl:with-param name="accumulator" select="1"/> <xsl:with-param name="reps" select="$power"/> </xsl:call-template> </xsl:template> <xsl:template name="raiseToPowerIter"> <xsl:param name="multiplier"/> <xsl:param name="accumulator"/> <xsl:param name="reps"/> <xsl:choose> <xsl:when test="$reps > 0"> <xsl:call-template name="raiseToPowerIter"> <xsl:with-param name="multiplier" select="$multiplier"/> <xsl:with-param name="accumulator" select="$accumulator * $multiplier"/> <xsl:with-param name="reps" select="$reps - 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$accumulator"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="hexToDec"> <xsl:param name="hexVal"/> <xsl:param name="decVal"/> <xsl:variable name="hexLength" select="string-length($hexVal)"/> <xsl:choose> <xsl:when test="$hexLength > 0"> <xsl:variable name="hexPos"> <xsl:call-template name="singleHexToDec"> <xsl:with-param name="hex" select="substring($hexVal,1,1)"/> </xsl:call-template> </xsl:variable> <xsl:variable name="addToDec"> <xsl:call-template name="raiseToPower"> <xsl:with-param name="number" select="16"/> <xsl:with-param name="power" select="$hexLength - 1"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="hexToDec"> <xsl:with-param name="hexVal" select="substring($hexVal,2)"/> <xsl:with-param name="decVal" select="$decVal + ($addToDec * $hexPos)"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$decVal"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:transform> | ||||||||
10. | Quantity times price | |||||||
> But I need the sum of each (price*qty). I tried everything I could think > of, but no luck. Any clues on how to do this? Otherwise: <xsl:variable name="Items" select="//Item"/> <xsl:variable name="TotalValue"> <xsl:call-template name="Total"> <xsl:with-param name="Items" select="$Items"/> <xsl:with-param name="RunningTotal" select="0"/> </xsl:call-template> </xsl:variable> Total = <xsl:value-of select="format-number($TotalValue, '####0.0#')"/> </xsl:template> <xsl:template name="Total"> <xsl:param name="Items"/> <xsl:param name="RunningTotal"/> <xsl:choose> <xsl:when test="not($Items)"> <xsl:copy-of select="$RunningTotal"/> </xsl:when> <xsl:otherwise> <xsl:variable name="CurrentTotal" select="$RunningTotal + ($Items[1]/@qty * $Items[1]/@price)"/> <xsl:call-template name="Total"> <xsl:with-param name="Items" select="$Items[position()>1]"/> <xsl:with-param name="RunningTotal" select="$CurrentTotal"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> | ||||||||
11. | Calculations in XSL using multiple XML data fileds | |||||||
>I want the XSL to perform the following calculation > > (q1/quotes/quote/last) - ((q2/quotes/quote/last)/7.45) > >I am hoping that this can be done in XSL but cannot find out how this can be >done. The only thing you're lacking is knowing how to divide in XSLT (well, actually in XPath). The '/' can't be used because it's used in XPath to separate steps, so instead 'div' is used instead: q1/quotes/quote/last - (q2/quotes/quote/last div 7.45) To give the result to two decimal places, you need the format-number() XSLT function: format-number(q1/quotes/quote/last - (q2/quotes/quote/last div 7.45), '#,##0.00') How the XML is generated won't matter in terms of how you solve this problem, but what XSLT processor you're using will: if you're using MSXML, make sure it's the latest version. >If it cannot be done in XSL, can anyone suggest how else (script?) this can >be done If you're using MSXML, then you can use an extension function called msxsl:script to write functions in JScript or VBScript that can then be used within XPath as XPath functions. XSLT is enough to handle simple calculations, but if you get anything complicated (e.g. sin/cos) then you need something more, which msxsl:script provides. The details are given in the MSXML SDK. | ||||||||
12. | Max and Min | |||||||
> I would like to know if we can find out Maximum and Minumum of a element in > a xml file using xsl or any other XML technologes. The best way involves an XPath: the minimum value is the value of the node such that there are no other nodes that have a value less than that value; the maximum is the value of the node such that there are no other nodes with a value *more* than that value. So, to find the maximum of the 'in' elements, use: in[not(parent::TIME/in > .)] [in English: "the 'in' element(s) for which it is *not* the case that there is a 'in' element (child of the 'in' element's parent, 'TIME') that has a value less than the value of this one"] and to find the minimum: out[not(parent::TIME/out < .)] For some reason, I've also done this by sorting the elements, in either ascending or descending order, and picking out the first in the list. If you sort in ascending order, you'll get the minimum; if in descending order, the maximum. So, to find the maximum of the 'in' elements, use: <xsl:for-each select="in"> <xsl:sort select="." data-type="number" order="descending" /> <xsl:if test="position() = 1"> <xsl:value-of select="." /> </xsl:if> </xsl:for-each> Mike Kay adds: But I'd express caution, certainly for large node-sets. This is likely to be an O(n-squared) solution (it certainly is in Saxon). Doing an XSLT sort and extracting the first or last element is likely to be O(n*log(n)). Doing a recursive walk of the node-set as described in XSLT Prog Ref page 171 is likely to be O(n). | ||||||||
13. | Finding element with min value in multiple files | |||||||
> How do I find max or min value of a given element in multiple files? Lots of ways. A quick and simple way to find the minimum would be to have a variable hold the values: <xsl:variable name="costs" select="document('Replies/File')/Document/Cost" /> And then to find those nodes in the $costs node set where there's no other node in the node set with a smaller value: $costs[not($costs < .)] Another way is to use a sort in ascending order and then pick the first in the list: <xsl:for-each select="$costs"> <xsl:sort select="." data-type="number" /> <xsl:if test="position() = 1"> <xsl:value-of select="." /> </xsl:if> </xsl:for-each> A final way involves constructing a new result tree fragment holding the costs: <xsl:variable name="costs"> <xsl:copy-of select="document(Replies/File)/Document/Cost" /> </xsl:variable> and then to use this as the basis of a recursive solution. Apply templates to the first Cost in this list: <xsl:apply-templates select="$costs/Cost[1]" /> and have a template: <xsl:template match="Cost" mode="minimum"> <xsl:variable name="next" select="following-sibling::Cost[. < current()]" /> <xsl:choose> <xsl:when test="$next"> <xsl:apply-templates select="$next" mode="minimum" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="." /> </xsl:otherwise> </xsl:choose> </xsl:template> This solution works best under XSLT 1.1, but you can use it now if you use the node-set() extension function provided under most XSLT processors. For now you need to apply templates with something like: <xsl:apply-templates select="saxon:node-set($costs)/Cost[1]" /> Which one of the above works best will depend on how many Costs you have, what your processor optimises and so on - try them to see. | ||||||||
14. | Real Numbers, variations. | |||||||
XPath number with 20 digits gives you a head ache <xsl:value-of select="number(string('92125374252539897737'))" /> Results from XSLT processors I have on my machine: Output XSLT Processor ====================================== 92125374252539904000 SAXON 6.1 Xalan-C 1.0 UXT 1.03.00 XT 92125374252539900000 Xalan-J 2.0.D07 Xalan-J 1.2.2 MSXML 3.0 9.21253742525399E19 Oracle V2 (beta) 2147483647 iXSLT 2.0c Interesting. Which, if any, is actually right? Mike Kay comments: Oracle's output (stated version) is definitely wrong: the result should never be in scientific notation. | ||||||||
15. | max(), min() and avg() functions | |||||||
> i want to get the maximum, minimum and average values of > xml elements....i > want to use XSLTs for this purpose...does XSLT > support mathematical functions XSLT supports count() and sum() aggegate functions. By using a trick to assign a variable to a computed value, you can use XSLT to calculate min, max, and avg like this: Let's say your source document is: <list> <item>15</item> <item>10</item> <item>20</item> </list> Then it's easy to assign a value to the sum() of the items by doing: <xsl:variable name="the_sum" value="sum(/list/item)"/> or the count of the items: <xsl:variable name="the_count" value="count(/list/item)"/> for the average, you can do something like this: <xsl:variable name="the_avg" select="sum(/list/item) div count(/list/item)"/> for the max, you can do: <!-- | assign variable based on picked the first item in | the numerically-sorted-descending list of items. +> <xsl:variable name="the_max"> <xsl:for-each select="/list/item"> <xsl:sort data-type="number" order="descending"/> <xsl:if test="position()=1"> <xsl:value-of select="."/></xsl:if> </xsl:for-each> </xsl:variable> for the min, you just reverse the sort order: <!-- | assign variable based on picked the first item in | the numerically-sorted-descending list of items. +> <xsl:variable name="the_min"> <xsl:for-each select="/list/item"> <xsl:sort data-type="number" order="ascending"/> <xsl:if test="position()=1"> <xsl:value-of select="."/></xsl:if> </xsl:for-each> </xsl:variable> | ||||||||
16. | Random Number Generator | |||||||
If you're using a Java processor, something like <xsl:variable name="rnd" select="math:random()" xmlns:math="java:java.lang.Math"/> Details vary slightly depending on the processor. | ||||||||
17. | Decimal to Hexadecimal conversion | |||||||
I think Goetz posted this a long time ago ( Sorry if the cite is off) <xsl:template name="printHex"> <xsl:param name="number">0</xsl:param> <xsl:variable name="low"> <xsl:value-of select="$number mod 16"/> </xsl:variable> <xsl:variable name="high"> <xsl:value-of select="floor($number div 16)"/> </xsl:variable> <xsl:choose> <xsl:when test="$high > 0"> <xsl:call-template name="printHex"> <xsl:with-param name="number"> <xsl:value-of select="$high"/> </xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:text>0x</xsl:text> </xsl:otherwise> </xsl:choose> <xsl:choose> <xsl:when test="$low < 10"> <xsl:value-of select="$low"/> </xsl:when> <xsl:otherwise> <xsl:variable name="temp"> <xsl:value-of select="$low - 10"/> </xsl:variable> <xsl:value-of select="translate($temp, '012345', 'ABCDEF')"/> </xsl:otherwise> </xsl:choose> </xsl:template> | ||||||||
18. | Calculate mantissa and exponent | |||||||
here it is, courtesy of sdussinger: sample.xml: <a> sample.xsl: <xsl:stylesheet output: [ejm@localhost xslt]$ java com.icl.saxon.StyleSheet | ||||||||
19. | Sum of products | |||||||
You could use this recursive template: <xsl:template name="prod-sum"> | ||||||||
20. | Summation, numbers contain comma's (European format) | |||||||
As a general principle, it's best to hold data in XML documents in a form that's optimised for processing rather than for display to humans. Using schemas will encourage that further. So I would say, first do a transformation to a document that holds these numbers in their canonical form (12345.12 etc), which you can achieve using translate(., ',', ''), and then apply the summation to the result. Jeni T then expands on that (as she is wont to do :-) This is because XPath can't convert '2,345.12' to a number (due to the comma, as you found). The best solution is to change your XML source document so that it doesn't contain commas in numeric values. This is the best solution because (a) it makes your XSLT simple (you can use sum()) and (b) according to XML Schema and therefore the rest of the XML world, the text representation of numbers doesn't contain commas; removing them will mean that you can use XML Schema or other languages that use the XML Schema data types with your XML as well. If you can't do that, then you need a recursive template that will enable you to do the conversion of the string value of the Amount elements to the numbers that you want them to be. The conversion is really simple - get rid of the commas with the translate() function. With $amount being an Amount element: translate($amount, ',', '') You could use one of Dimitre's generic templates, or the following (tail-)recursive template, which works through the Amount elements one by one, keeping track of the subtotal as it goes, and finally returns the total when it runs out of Amount elements: <xsl:template name="sumAmounts"> <xsl:param name="amounts" select="Amount" /> <xsl:param name="subtotal" select="0" /> <xsl:choose> <xsl:when test="$amounts"> <xsl:call-template name="sumAmounts"> <xsl:with-param name="amounts" select="$amounts[position() > 1]" /> <xsl:with-param name="subtotal" select="$subtotal + translate($amounts[1], ',', '')" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$subtotal" /> </xsl:otherwise> </xsl:choose> </xsl:template> | ||||||||
21. | Any one know how to eliminate Trailing Zeros | |||||||
<xsl:value-of select="format-number('13.70','#,##0.#')"/> result: 13.7 If you want the zeros back: <xsl:value-of select="format-number('13.70','#,##0.00')"/> | ||||||||
22. | Number precision | |||||||
The data type "double" doesn't technically exist in XPath 1.0 -- there are only strings, numbers, booleans and node sets. However, numbers in XPath 1.0 are double-precision 64-bit format IEEE 754 values. Numbers are converted to strings using the following rules:
As you can see, there's nothing about using scientific notation for the string value of a number in this description. XPath/XSLT 1.0 processors shouldn't use the syntax "1e-6", "1e-06", "1e-006", etc, but should use "0.000001". This means that aside from NaN, Infinity and -Infinity, the string value of a number can be converted back into a number using the number() function. You can change the format a little using the format-number() function top get a different string, but format-number() doesn't provide scientific notation either. A proper double value type will be introduced in XPath 2.0 (xs:double). Currently, it looks likely that you'll be able to create doubles with literals that use scientific notation: [134] DoubleLiteral ::= (("." [0-9]+) | When you convert xs:double values to a string, they'll use the canonical lexical representation for xs:double as defined in the XML Schema: Datatypes Recommendation: The canonical representation for double is defined by prohibiting certain options from the Lexical representation (para 3.2.5.1). Specifically, the exponent must be indicated by "E". Leading zeroes and the preceding optional "+" sign are prohibited in the exponent. For the mantissa, the preceding optional "+" sign is prohibited and the decimal point is required. For the exponent, the preceding optional "+" sign is prohibited. Leading and trailing zeroes are prohibited subject to the following: number representations must be normalized such that there is a single digit to the left of the decimal point and at least a single digit to the right of the decimal point. So the double 0.000001 should be turned into the string "1.0E-6" as far as I can tell. According to the XSLT 2.0 WD, the XSL WG will be adding support for scientific notation to the format-number() function as well (see http://www.w3.org/TR/xslt20/#issue-scientific-notation) which should give you more control over the format. | ||||||||
23. | Minimum value | |||||||
There are basically three approaches to finding a minumum value:
The interesting thing is that they have very different scaleability. (1) visits each node once so it has O(n) performance; (2) does a sort so it has O(n log n) performance, (3) if implemented crudely will compare every node with every other, so it potentially has O(n*n) performance (but processors may be able to optimize it). (However, although (1) performs in linear time, it may use a lot of memory depending on how well recursion is implemented). Of course if you use Dimitre's FXSL library or the EXSLT max() and min() extension functions then you don't have to decide between these algorithms, someone else has already done the hard work for you. In XPath 2.0 there are max() and min() functions in the core function library. | ||||||||
24. | Sum function - portable | |||||||
It would be easiest if when you built the comma-separated list, you created a result tree fragment in which each value you want in the total is in a separate element. The string-value of the fragment can still give you the list output that you want, but you can also use XPath/XSLT's sum function on the node-set equivalent of the fragment. xalan:nodeSet() (or exsl:node-set() if you're using a more recent snapshot of Xalan) will convert the RTF to a node-set. The most portable alternative is to use a recursive template to dissect the string. Given XML: <?xml version="1.0"?> <some> <data>1</data> <data>2</data> <data>3</data> <data>5</data> <data>7</data> <data>11</data> </some> Here's a stylesheet that demonstrates both solutions: <?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:xalan="http://xml.apache.org/xalan" exclude-result-prefixes="xalan exsl"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:variable name="list"> <xsl:for-each select="some/data"> <dummyElement> <xsl:value-of select="."/> </dummyElement> <xsl:if test="position() != last()"> <comma>,</comma> </xsl:if> </xsl:for-each> </xsl:variable> <xsl:text>The list is: </xsl:text> <xsl:value-of select="$list"/> <xsl:text> The sum is: </xsl:text> <xsl:choose> <xsl:when test="function-available('exsl:node-set')"> <xsl:value-of select="count(exsl:node-set($list)/dummyElement)"/> </xsl:when> <xsl:when test="function-available('xalan:nodeSet')"> <xsl:value-of select="sum(xalan:nodeSet($list)/dummyElement)"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="sum"> <xsl:with-param name="str" select="string($list)"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="sum"> <xsl:param name="str"/> <xsl:param name="total" select="0"/> <xsl:choose> <xsl:when test="contains($str,',')"> <xsl:call-template name="sum"> <xsl:with-param name="str" select="substring-after($str,',')"/> <xsl:with-param name="total" select="$total + substring-before($str,',')"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$total + $str"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | ||||||||
25. | MathML to SVG | |||||||
"MathML to SVG Converter" by Schemasoft at schemasoft.com written in XSLT1.1, apparently. Crazy, huh? Also, Marc Gaetano and Stephane Lavirotte from Universite de Nice have presented another converter (written in Java) at the MathML conference. See matmlconference.org and mainline.essi.fr (in French) | ||||||||
26. | Binary OR function | |||||||
Dion Houston proffers I haven't had a chance to test it exhaustively, but it appears to work: <xslt:template name="BinaryOr"> <xslt:param name="op1" select="'0'"/> <xslt:param name="op2" select="'0'"/> <xslt:if test="$op1 != '' or $op2 != ''"> <xslt:call-template name="BinaryOr"> <xslt:with-param name="op1" select="substring($op1, 1, string-length($op1)-1)"/> <xslt:with-param name="op2" select="substring($op2,1, string-length($op2)-1)"/> </xslt:call-template> <xslt:choose> <xslt:when test="$op2 mod 2">1</xslt:when> <xslt:otherwise><xslt:value-of select="$op1 mod 2"/></xslt:otherwise> </xslt:choose> </xslt:if> </xslt:template> This is a recursive template that starts from the left most character, iterating successively. At each position it'll take a modulus by 2 on the second param. If it is 1, then it displays one, otherwise it returns the modulus of the first param. Ruben ??? offers the one I like! set the two variables to strings, e.g. <xsl:variable name="operand1" select="'1010101110'"/> <xsl:value-of select="translate(string(number($operand1) + number($operand2)), '2', '1')"/> Dimitre Novatchev offers For any instance of a class of problems, requiring to perform an operation on two lists memberwise, the functional solution is the zipWith() function, which is implemented in FXSL. A string is a special kind of list in XSLT, because we do not have a "char" datatype, and cannot represent a string as a list of chars. This is why every FXSL function, which operates on lists (implemented as node-sets) has also a string analogue. Here's the string analog of zipWith(): str-zipWith.xsl: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:template name="str-zipWith"> <xsl:param name="pFun" select="/.."/> <xsl:param name="pStr1" select="/.."/> <xsl:param name="pStr2" select="/.."/> <xsl:if test="string-length($pStr1) and string-length($pStr2)"> <xsl:apply-templates select="$pFun"> <xsl:with-param name="pArg1" select="substring($pStr1, 1, 1)"/> <xsl:with-param name="pArg2" select="substring($pStr2, 1, 1)"/> </xsl:apply-templates> <xsl:call-template name="str-zipWith"> <xsl:with-param name="pFun" select="$pFun"/> <xsl:with-param name="pStr1" select="substring($pStr1, 2)"/> <xsl:with-param name="pStr2" select="substring($pStr2, 2)"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet> Now let's test it to solve your problem -- we need to "or" two strings containing only 0-s or 1-s: test-str-zipWith.xsl: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:myOr="f:myOr" > <xsl:import href="str-zipWith.xsl"/> <!-- to be applied on numList.xml --> <xsl:output method="text"/> <myOr:myOr/> <xsl:template match="/"> <xsl:variable name="vApos">'</xsl:variable> <xsl:variable name="vNL" select="'
'"/> <xsl:variable name="vFun" select="document('')/*/myOr:*[1]"/> <xsl:value-of select="concat(' ', $vApos, /*/*[1], $vApos, $vNL, 'or', $vNL, ' ', $vApos, /*/*[2], $vApos, $vNL, '=', $vNL, ' ', $vApos )"/> <xsl:call-template name="str-zipWith"> <xsl:with-param name="pFun" select="$vFun"/> <xsl:with-param name="pStr1" select="/*/*[1]"/> <xsl:with-param name="pStr2" select="/*/*[2]"/> </xsl:call-template> </xsl:template> <xsl:template match="myOr:*"> <xsl:param name="pArg1"/> <xsl:param name="pArg2"/> <xsl:value-of select="number(number($pArg1) or number($pArg2))"/> </xsl:template> </xsl:stylesheet> Let's apply the above transformation on the following source xml: test-str-zipWith.xml: <test-str-zipWith> I can immediately implement "binary and", "xor", some kind of primitive encryption and ***any*** character-wise operation. The versions of zipWith for more than two lists (and their string analogues) are left as an exercise to the reader :o) | ||||||||
27. | fibonacci | |||||||
You can do it in Saxon 7.x as: <xsl:function name="m:fibonacci" saxon:memo-function="yes"> <xsl:param name="n" as="xs:integer"> <xsl:result as="xs:integer" select="if ($n=0) then 0 else if ($n=1) then 1 else m:fibonacci($n-1) + m:fibonacci($n-2)"/> </xsl:function> In XSLT 1.0 you can do by calling a recursive template starting with the value 0, calling itself with the value $param+1 until the required parameter value is reached, each time supplying the values of the last two items in the sequence as parameters. Mike Brown offers the 1.0 version I have no idea if this meets your complexity requirements. I was just testing 4suite's tail recursion optimization with it a couple months ago. <?xml version="1.0" encoding="utf-8"?> <!-- Fibonacci Numbers if n = 0, f(n) = 0 if n = 1, f(n) = 1 otherwise, f(n) = f(n-2) + f(n-1) tail-recursive version based on Scheme code by Ben Gum and John David Stone, at http://www.math.grin.edu/~gum/courses/151/readings/tail-recursion.xhtml --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" indent="no"/> <xsl:param name="n" select="100"/> <xsl:template match="/"> <xsl:value-of select="concat('f(',$n,') = ')"/> <xsl:call-template name="fibo"> <xsl:with-param name="n" select="$n"/> </xsl:call-template> </xsl:template> <xsl:template name="fibo"> <xsl:param name="n" select="0"/> <xsl:call-template name="fibo-guts"> <xsl:with-param name="current" select="0"/> <xsl:with-param name="next" select="1"/> <xsl:with-param name="remaining" select="$n"/> </xsl:call-template> </xsl:template> <xsl:template name="fibo-guts"> <xsl:param name="current" select="0"/> <xsl:param name="next" select="0"/> <xsl:param name="remaining" select="0"/> <xsl:choose> <xsl:when test="$remaining = 0"> <xsl:value-of select="$current"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="fibo-guts"> <xsl:with-param name="current" select="$next"/> <xsl:with-param name="next" select="$current + $next"/> <xsl:with-param name="remaining" select="$remaining - 1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | ||||||||
28. | union operator | |||||||
The following Xpath expression selects a node-set $ns1 if a condition $p is true and selects a node-set $ns2 if the same condition $p is not true: $ns1[$p] | $ns2[not($p)] When it is known that the condition $p is that $ns1 must be non-empty, the above XPath expression can be simplified to the following: $ns1 | $ns2[not($ns1)] In this specific case we'll have: /*/thing[lang($mylang)] | /*/thing[1][not(/*/thing[lang($mylang)] )] | ||||||||
29. | Numbers to Words | |||||||
There is probably a better way to do this, but here is my attempt... It will translate up to trillion, (999999999999999) after that it starts putting in the multiple instead of the name (1000000000000000). If I were going to do it again I might start by breaking the number string up into pieces so that math operations on large numbers doesn't limit the size of number supported... Enjoy, Josh ===== xml.xml ===== <?xml version="1.0" encoding="ISO-8859-1"?> <a> <num>12345678901234</num> </a> ===== Output ===== <?xml version="1.0" encoding="utf-8"?> <a> <numInWords>twelve trillion three hundred and forty-five billion six hundred and seventy-eight million nine hundred and one thousand two hundred and thirty-f our</numInWords> </a> ===== num-to-words.xsl ===== <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="utf-8"/> <xsl:template match="/"> <a><numInWords> <xsl:call-template name="num-to-word"> <xsl:with-param name="value" select="a/num"/> </xsl:call-template> </numInWords></a> </xsl:template> <xsl:template name="num-to-word"> <xsl:param name="value"/> <xsl:param name="multiple"/> <xsl:variable name="m"> <xsl:choose> <xsl:when test="$multiple"> <xsl:value-of select="$multiple"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="get-multiple"> <xsl:with-param name="value" select="$value"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <!-- partition, thousands, millions, billions, etc --> <xsl:when test="$value > 9999"> <xsl:variable name="part" select="floor($value div $m)"/> <xsl:variable name="r" select="$value - ($part * $m)"/> <xsl:call-template name="num-to-word"> <xsl:with-param name="value" select="$part"/> </xsl:call-template> <xsl:text> </xsl:text><xsl:call-template name="word-map"><xsl:with-param name="val" select="$m"/></xsl:call-template> <xsl:if test="$r"> <xsl:text> </xsl:text> <xsl:call-template name="num-to-word"> <xsl:with-param name="value" select="$r"/> </xsl:call-template> </xsl:if> </xsl:when> <!-- special case the teens --> <xsl:when test="$value < 19"> <xsl:call-template name="word-map"> <xsl:with-param name="val" select="$value"/> </xsl:call-template> </xsl:when> <!-- handles values 20 - 9999 --> <xsl:otherwise> <xsl:variable name="part" select="floor($value div $m)"/> <xsl:variable name="r" select="$value - ($part * $m)"/> <xsl:choose> <xsl:when test="$m=10 and $part > 1"> <xsl:call-template name="word-map"> <xsl:with-param name="val" select="$part*$m"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="word-map"> <xsl:with-param name="val" select="$part"/> </xsl:call-template> <!-- add hundred, thousand, million, etc --> <xsl:if test="$m > 10"> <xsl:text> </xsl:text><xsl:call-template name="word-map"><xsl:with-param name="val" select="$m"/></xsl:call-template> </xsl:if> </xsl:otherwise> </xsl:choose> <xsl:if test="$r"> <xsl:choose> <!-- for: one hundred and five --> <xsl:when test="$m = 100"><xsl:text> and </xsl:text></xsl:when> <!-- for: thirty-five --> <xsl:when test="$m = 10"><xsl:text>-</xsl:text></xsl:when> <!-- for: one thousand one hundred --> <xsl:otherwise><xsl:text> </xsl:text></xsl:otherwise> </xsl:choose> <!-- translate the next part --> <xsl:call-template name="num-to-word"> <xsl:with-param name="value" select="$r"/> </xsl:call-template> </xsl:if> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- determine the multiple for a number --> <xsl:template name="get-multiple"> <xsl:param name="value"/> <xsl:param name="multiple" select="1"/> <xsl:param name="inc" select="10"/> <xsl:choose> <!-- at the thousand mark start multiplying by 1000 --> <xsl:when test="$value > 999"> <xsl:call-template name="get-multiple"> <xsl:with-param name="value" select="floor($value div 1000)"/> <xsl:with-param name="multiple" select="$multiple * 1000"/> <xsl:with-param name="inc" select="1000"/> </xsl:call-template> </xsl:when> <xsl:when test="$value > $inc"> <xsl:call-template name="get-multiple"> <xsl:with-param name="value" select="floor($value div $inc)"/> <xsl:with-param name="multiple" select="$multiple * $inc"/> <xsl:with-param name="inc" select="$inc"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$multiple"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="word-map"> <xsl:param name="val"/> <xsl:choose> <xsl:when test="$val = 1">one</xsl:when> <xsl:when test="$val = 2">two</xsl:when> <xsl:when test="$val = 3">three</xsl:when> <xsl:when test="$val = 4">four</xsl:when> <xsl:when test="$val = 5">five</xsl:when> <xsl:when test="$val = 6">six</xsl:when> <xsl:when test="$val = 7">seven</xsl:when> <xsl:when test="$val = 8">eight</xsl:when> <xsl:when test="$val = 9">nine</xsl:when> <xsl:when test="$val = 10">ten</xsl:when> <xsl:when test="$val = 11">eleven</xsl:when> <xsl:when test="$val = 12">twelve</xsl:when> <xsl:when test="$val = 13">thirteen</xsl:when> <xsl:when test="$val = 14">fourteen</xsl:when> <xsl:when test="$val = 15">fifteen</xsl:when> <xsl:when test="$val = 16">sixteen</xsl:when> <xsl:when test="$val = 17">seventeen</xsl:when> <xsl:when test="$val = 18">eighteen</xsl:when> <xsl:when test="$val = 19">nineteen</xsl:when> <xsl:when test="$val = 20">twenty</xsl:when> <xsl:when test="$val = 30">thirty</xsl:when> <xsl:when test="$val = 40">forty</xsl:when> <xsl:when test="$val = 50">fifty</xsl:when> <xsl:when test="$val = 60">sixty</xsl:when> <xsl:when test="$val = 70">seventy</xsl:when> <xsl:when test="$val = 80">eighty</xsl:when> <xsl:when test="$val = 90">ninety</xsl:when> <xsl:when test="$val = 100">hundred</xsl:when> <xsl:when test="$val = 1000">thousand</xsl:when> <xsl:when test="$val = 1000000">million</xsl:when> <xsl:when test="$val = 1000000000">billion</xsl:when> <xsl:when test="$val = 1000000000000">trillion</xsl:when> <xsl:otherwise><xsl:value-of select="$val"/></xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | ||||||||
30. | Random selection | |||||||
For randomization in XSLT, you should check out Dimitre Novatchev's excellent work on FXSL including a whole module for random number generation. Casting the Dice with FXSL: Random Number Generation Functions in XSLT topxml or Yes, you could do that. I have a hack in the forthcoming OReilly book "XML Hacks" that shows a simple way to randomize with xslt. Basically, insert a string of random numbers into a node in the style sheet and index into it to get the random numbers. Or you could pass in a different random string as a global parameter each time you run the stylesheet. | ||||||||
31. | Bit Testing | |||||||
will be <xsl:if test="(@Flags div 128) mod 2 = 1"> | ||||||||
32. | XSLT for math computations | |||||||
Fundamentally wrong and incorrect question ! Not only one shouldn't be forbidden to use whatever tool they themselves have selected. The prime number problem is not at all an isolated example. FXSL provides pure XSLT implementation of some most fundamental math function groups such as: - exponential/logarithmic: exp(), ln(). log(), log2(), log10(), sqrt(), pow(), ... etc. - trigonometric: sin(), cos(), tan(), cot(), secans(), cosecans() - inverse-trigonometric: the above with the prefix "arc" - Hyperbolic-trigonometric: the trigonometric fns with the prefix "h" - Finding the root of any equation of the form f(x) = 0, where f() is a continuos function of one real argument -- using the Newton-Raphston method and the binary search method. - Random number generator functions that produce a sequence of random numbers in a given interval and/or with a given distribution - some statistical functions - etc. See this at: sourceforge and sourceforge The other, factual reason is that using XSLT for math computations is simple, compact, elegant and fun. For example, to get the sequence: N^10, N = 1 to 10 one simply writes this one-line expression: f:map(f:flip(f:pow(),10), 1 to 10) To get the sum of the tenth powers of all numbers from 1 to 10, one writes this one-liner: sum(f:map(f:flip(f:pow(),10), 1 to 10)) To get the tenth root of the above, this one-liner is used: f:pow(sum(f:map(f:flip(f:pow(),10), 1 to 10)), 0.1) Here's the full result of a transformation. The expressions to the left of the "=" sign are the XPath expressions that are actually used in the transformation: f:pow(2,5) = 32.000000498873234 ----------------------------------------------- f:flip(f:pow(), 2, 5) = 25.000000210097642 ----------------------------------------------- f:map(f:flip(f:pow(),2), 1 to 10) = 1 4.00000002487061 8.999999997225032 16.0000000654391 25.000000210097642 36.00000008032039 49.00000001473974 64.00000003117357 81.00000012870524 99.99999981533682 ----------------------------------------------- f:map(f:flip(f:pow(),10), 1 to 10) = 1 1024.000031977348 59048.99991721205 1.0485760215547918E6 9.765625412399698E6 6.046617668837886E7 2.8247524947463E8 1.0737418267311203E9 3.486784428898773E9 9.999999907930431E9 ----------------------------------------------- sum(f:map(f:flip(f:pow(),10), 1 to 10)) = 1.4914341865157238E10 ----------------------------------------------- f:pow(sum(f:map(f:flip(f:pow(),10), 1 to 10)), 0.1) = 10.407835264401298 ----------------------------------------------- f:log(2,4) = 0.5000000015112621 ----------------------------------------------- f:flip(f:log(), 2, 4) = 1.9999999939549515 ----------------------------------------------- f:map(f:flip(f:log(),2), 2 to 10) = 1 1.584962493378093 1.9999999939549515 2.321928090518739 2.58496249071745 2.807354909651913 2.9999999868509244 3.169924988315051 3.3219280785926495 The time it took to produce these results on my PC was: 469 milliseconds The results are self-explanatory and it is very easy to learn how to compose arbitrary expressions using the basic math functions and operators of FXSL and XPath 2.0. The combining "glue" is functional composition and using currying/partial application. Note also, that as of present all (most important) XPath 2.0 functions and operators can be used as higher-order functions, by using a convenient FXSL wrapper with the same name. For example, to produce the sequence: 2*N, N = 1 to 10 the following XPath expression can be used: f:map(f:mult(2), 1 to 10) In this expression f:mult() is(a wrapper of) the op:numeric-multiply operator as defined in W3C f:mult(2) is the partial application of f:mult() with 2 as the first argument. It is itself a function that takes one argument and returns it multiplied by 2. FXSL provides such wrappers for all (most important) XPath 2.0 functions and operators. One final example. The following one-liner returns a sequence of numeric values approximating sqrt(2) with precision ranging from 0 to 13 digits after the decimal point: f:map(f:round-half-to-even(f:sqrt(2, 0.000001)), 0 to 13) and the result is: 1 1.4 1.41 1.414 1.4142 1.41421 1.414214 1.4142136 1.41421356 1.414213562 1.4142135624 1.41421356237 1.414213562375 1.4142135623747 Hope this post makes it possible to be more clear about the usefulness of XSLT in math computations. It should certainly dispell the myth of XSLT being "slow" in these. Fittingly, Michael emphasized in his post, that the argument, XSLT is slow, is not accurate for straightforward transformations any more, often the time for parsing the input and serializing the output is neglected. | ||||||||
33. | Why does 3 > 2 > 1 return false() and 1 < 2 < 3 return true()?? | |||||||
I was recently reminded that XPath 1.0 allows comparisons such as 3 > 2 > 1 which return false. While XPath 1.0 syntax allows consecutive comparisons, semantics differ from e.g. Python where evaluating 3 > 2 > 1 would return True. Comparison operators are left associative, thus 3 > 2 > 1 can be rewritten to (3 > 2) > 1. When the comparison in the parentheses is evaluated, the expression is reduced to true() > 1. The greater-that comparison forces the boolean value to be cast to a number using the number() function, thus the expression reduces to 1 > 1 which in turn evaluates to false(). 1 < 2 < 3 however reduces to 1 < 3 and returns true(). XPath 2.0 incompatibilities has this to say about the subject matter: "Consecutive comparison operators such as A < B < C were supported in XPath 1.0, but are not permitted by the XPath 2.0 grammar. In most cases such comparisons in XPath 1.0 did not have the intuitive meaning, so it is unlikely that they have been widely used in practice. If such a construct is found, an XPath 2.0 processor will report a syntax error, and the construct can be rewritten as (A < B) < C" | ||||||||
34. | Sum over computed nodes | |||||||
You may have noticed there is another thread running on the same subject - how to sum over values that are computed from those held in the nodes, rather than the actual string-values of the nodes. The solutions on offer include:
| ||||||||
35. | Count unique terms | |||||||
How do I count the number of elements in the key "systems" seeing that several row could have the same "serial" value ? The purpose being to compute the number of unique serial values found in the XML document... That's the basis of the "muenchian grouping" technique: count(/inventory/row/serial[generate-id()=generate-id(key('systems',.)[1])]) Andrew offers count(distinct-values(/inventory/row/serial)) if you are using XSLT 2.0 | ||||||||
36. | Maths in xsl | |||||||
I think you are better off with two templates: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";> <xsl:template match="m"> <xsl:variable name="sum"> <xsl:apply-templates select="*[1]"/> </xsl:variable> <xsl:choose> <xsl:when test="$sum != ''"> <xsl:value-of select="@value * $sum"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="@value"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="i"> <xsl:variable name="sum"> <xsl:apply-templates select="following-sibling::*[1]"/> </xsl:variable> <xsl:choose> <xsl:when test="$sum != ''"> <xsl:value-of select="@value + $sum"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="@value"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> If you are using XSLT 2.0 then you can drop the choose/when for the more terse if then else: select="if ($sum != '') then (@value + $sum) else (@value)" | ||||||||
37. | mod 10 xpath vs math | |||||||
the answer: it is the same. Mod in xpath keeps the sign, so (for example) -(-9 mod 10) is 9 as is 9 mod 10. To test if $x and $y are equal mod 10 you need to test ($x -$y) mod 10 = 0 rather than ($x mod 10) = ($y mod 10) for the reasons given. In the real world (that is, in mathematics) it is, also the mod function doesn't return an integer but rather an element of the finite set Z_n (which is how come 0 = 10 in the first place) however in most computer languages, and in particular in XPath the designers define mod not to be an operation on the set Z_n but rather an operation on Z (The integers) that is "remainder after division, and that retains the sign, so in XPath -9 mod 10 = -9 1 mod 10 = 1 so even though 1 and -9 are equal mod 10, it's not true that (1 mod 10) = (-9 mod 10) in Xpath, but it is true that (1 - -9 ) mod 10 = 0 which means that to check $x and $y are equal mod 10 you can use ($x - $y) mod 10 = 0 as you don't care which coset representative the mod operator uses, but if you want to generate the coset representative for -$x you can't just use -$x mod 10 you have to use (10 - $x) mod 10 to get the positive representative, hence my comment that it matters whether you are just checking a given checkdigit, or if you are calculting a digit and need to ensure you end up in the range 0 to 1. |