By Sal Mangano
Cover | Table of Contents | Colophon
http://www.exslt.org.
substring($value, (string-length($value) - string-length($substr)) + 1) = $substr
starts-with( ) function but
no ends-with( ). However, as the previous code
shows, ends-with can be implemented easily in
terms of substring( )
and string-length(
). The code simply extracts the last
string-length($substr) characters from the target
string and compares them to the substring.
<xsl:template name="index-of">
<xsl:param name="input"/>
<xsl:param name="substr"/>
<xsl:choose>
<xsl:when test="contains($input, $substr)">
<xsl:value-of select="string-length(substring-before($input, $substr))+1"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
string-length(substring-before($value,
$substr))+1. However, in general, you need a way
to handle the case in which the substring is not present. Here, zero
is chosen as an indication of this case, but you can use another
value such as -1 or NaN.
translate with an empty replace string. For
example, the following code can strip whitespace from a string:
translate($input," 	
&xd;", "")
translate( )
is a versatile string function that is
often used to compensate for missing string-processing capabilities
in XSLT. Here you use the fact that translate( )
will not copy characters in the input string that are in the
from string but do not have a corresponding
character in the to string.
translate to remove all but a
specific set of characters from a string. For example, the following
code removes all non-numeric characters from a string:
translate($string,
translate($string,'0123456789',''),'')
translate( ) removes all characters of
interest (e.g., numbers) to obtain a from string
for the outer translate( ), which removes these
non-numeric characters from the original string.
normalize-space(
),
which does just that. If you ever
needed to normalize based on characters other than spaces, then you
might use the following code (where C is the character you want to
normalize):
translate(normalize-space(translate($input,"C "," C")),"C "," C")
substr. Using this
technique, you can create a substring-before-last
and a substring-after-last.
<xsl:template name="substring-before-last">
<xsl:param name="input" />
<xsl:param name="substr" />
<xsl:if test="$substr and contains($input, $substr)">
<xsl:variable name="temp" select="substring-after($input, $substr)" />
<xsl:value-of select="substring-before($input, $substr)" />
<xsl:if test="contains($temp, $substr)">
<xsl:value-of select="$substr" />
<xsl:call-template name="substring-before-last">
<xsl:with-param name="input" select="$temp" />
<xsl:with-param name="substr" select="$substr" />
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template name="substring-after-last">
<xsl:param name="input"/>
<xsl:param name="substr"/>
<!-- Extract the string which comes after the first occurence -->
<xsl:variable name="temp" select="substring-after($input,$substr)"/>
<xsl:choose>
<!-- If it still contains the search string the recursively process -->
<xsl:when test="$substr and contains($temp,$substr)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="input" select="$temp"/>
<xsl:with-param name="substr" select="$substr"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$temp"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
substring-before and
substring-after)
begin searching at the start of the
string. Sometimes you need to search a string from the end. The
simplest way to do this in XSLT is to apply the built-in search
functions recursively until the last instance of the substring is
found.
N times, where N is a
parameter. For example, you might need to pad out a string with
spaces to achieve alignment.
$count is odd:
<xsl:template name="dup">
<xsl:param name="input"/>
<xsl:param name="count" select="1"/>
<xsl:choose>
<xsl:when test="not($count) or not($input)"/>
<xsl:when test="$count = 1">
<xsl:value-of select="$input"/>
</xsl:when>
<xsl:otherwise>
<!-- If $count is odd append an extra copy of input -->
<xsl:if test="$count mod 2">
<xsl:value-of select="$input"/>
</xsl:if>
<!-- Recursively apply template after doubling input and
halving count -->
<xsl:call-template name="dup">
<xsl:with-param name="input"
select="concat($input,$input)"/>
<xsl:with-param name="count"
select="floor($count div 2)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
$count
times is to
figure out a way to concatenate the string to itself
$count-1 times. This can be done recursively by
the following code, but this code will be expensive unless
$count is small, so it is not recommended:
<xsl:template name="slow-dup">
<xsl:param name="input"/>
<xsl:param name="count" select="1"/>
<xsl:param name="work" select="$input"/>
<xsl:choose>
<xsl:when test="not($count) or not($input)"/>
<xsl:when test="$count=1">
<xsl:value-of select="$work"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="slow-dup">
<xsl:with-param name="input" select="$input"/>
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="work"
select="concat($work,$input)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>$input in a subtle yet
effective way:
<xsl:template name="reverse">
<xsl:param name="input"/>
<xsl:variable name="len" select="string-length($input)"/>
<xsl:choose>
<!-- Strings of length less than 2 are trivial to reverse -->
<xsl:when test="$len < 2">
<xsl:value-of select="$input"/>
</xsl:when>
<!-- Strings of length 2 are also trivial to reverse -->
<xsl:when test="$len = 2">
<xsl:value-of select="substring($input,2,1)"/>
<xsl:value-of select="substring($input,1,1)"/>
</xsl:when>
<xsl:otherwise>
<!-- Swap the recursive application of this template to
the first half and second half of input -->
<xsl:variable name="mid" select="floor($len div 2)"/>
<xsl:call-template name="reverse">
<xsl:with-param name="input"
select="substring($input,$mid+1,$mid+1)"/>
</xsl:call-template>
<xsl:call-template name="reverse">
<xsl:with-param name="input"
select="substring($input,1,$mid)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="search-and-replace">
<xsl:param name="input"/>
<xsl:param name="search-string"/>
<xsl:param name="replace-string"/>
<xsl:choose>
<!-- See if the input contains the search string -->
<xsl:when test="$search-string and
contains($input,$search-string)">
<!-- If so, then concatenate the substring before the search
string to the replacement string and to the result of
recursively applying this template to the remaining substring.
-->
<xsl:value-of
select="substring-before($input,$search-string)"/>
<xsl:value-of select="$replace-string"/>
<xsl:call-template name="search-and-replace">
<xsl:with-param name="input"
select="substring-after($input,$search-string)"/>
<xsl:with-param name="search-string"
select="$search-string"/>
<xsl:with-param name="replace-string"
select="$replace-string"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- There are no more occurences of the search string so
just return the current input string -->
<xsl:value-of select="$input"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
$punc plus whitespace to be word
delimiters:
translate( )
function. This code, for example,
converts from upper- to lowercase:
translate($input,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')
translate($input, 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stylesheet [
<!ENTITY UPPERCASE "ABCDEFGHIJKLMNOPQRSTUVWXYZ">
<!ENTITY LOWERCASE "abcdefghijklmnopqrstuvwxyz">
<!ENTITY UPPER_TO_LOWER " '&UPPERCASE;' , '&LOWERCASE;' ">
<!ENTITY LOWER_TO_UPPER " '&LOWERCASE;' , '&UPPERCASE;' ">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="test"
select=" 'The rain in Spain falls mainly in the plain' "/>
<output>
<lowercase>
<xsl:value-of
select="translate($test,&UPPER_TO_LOWER;)"/>
</lowercase>
<uppercase>
<xsl:value-of
select="translate($test,&LOWER_TO_UPPER;)"/>
</uppercase>
</output>
</xsl:template>
</xsl:stylesheet><token> element text. It also defaults to
character-level tokenization if the delimiter string is empty.
<xsl:template name="tokenize">
<xsl:param name="string" select="''" />
<xsl:param name="delimiters" select="' 	
'" />
<xsl:choose>
<!-- Nothing to do if empty string -->
<xsl:when test="not($string)" />
<!-- No delimiters signals character level tokenization. -->
<xsl:when test="not($delimiters)">
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="$string" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="_tokenize-characters">
<xsl:param name="string" />
<xsl:if test="$string">
<token><xsl:value-of select="substring($string, 1, 1)" /></token>
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="_tokenize-delimiters">
<xsl:param name="string" />
<xsl:param name="delimiters" />
<xsl:param name="last-delimit"/>
<!-- Extract a delimiter -->
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)" />
<xsl:choose>
<!-- If the delimiter is empty we have a token -->
<xsl:when test="not($delimiter)">
<token><xsl:value-of select="$string"/></token>
</xsl:when>
<!-- If the string contains at least one delimiter we must split it -->
<xsl:when test="contains($string, $delimiter)">
<!-- If it starts with the delimiter we don't need to handle the -->
<!-- before part -->
<xsl:if test="not(starts-with($string, $delimiter))">
<!-- Handle the part that comes before the current delimiter -->
<!-- with the next delimiter. If ther is no next the first test -->
<!-- in this template will detect the token -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="substring-before($string, $delimiter)" />
<xsl:with-param name="delimiters"
select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:if>
<!-- Handle the part that comes after the delimiter using the -->
<!-- current delimiter -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiters" select="$delimiters" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- No occurances of current delimiter so move on to next -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string"
select="$string" />
<xsl:with-param name="delimiters"
select="substring($delimiters, 2)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
$string =~ /^C*$/ |
translate($string,'C','') = '' |
$string =~ /^C+$/ |
$string and translate($string,'C', '') = '' |
$string =~ /C+/ |
contains($string,'C') |
$string =~ /C{2,4}/
|
contains($string,'CC') and not(contains($string,'CCCCC')) |
$string =~ /^abc/ |
starts-with($string,'abc') |
$string =~ /abc$/ |
substring($string, string-length($string) - string-length('abc') + 1) = 'abc'
|
$string =~ /abc/ |
contains($string,'abc') |
$string =~ /^[^C]*$/ |
translate($string,'C','') = $string |
http://www.exslt.org/). At the time of
publication, these functions are:
node-set str:tokenize(string input, string delimiters?)
str:tokenize function
splits up a string and returns a node
set of token elements, each containing one token from the string.
	

  (i.e.,
whitespace characters).
node-set str:replace(string, object search, object replace)
str:replace function
replaces any occurrences of search strings
within a string with replacement nodes to create a node set.
http://www.incrementaldevelopment.com/xsltrick/)
contains some interesting mathematical XSLT curiosities such as
computing primes and differentiating polynomials. These tricks can be
instructive because they might extend your understanding of XSLT.
Instead, this chapter concentrates on recipes that demonstrate
commonly used mathematics that can be implemented economically within
the confines of XSLT.
sin,
cos, etc.). If you desperately need a pure XSLT
implementation of trigonometric functions, then Recipe 2.5 will point you in one general direction.
xsl:decimal-format
establishes a named formatting rule that can be referenced by the
format-number( )
function whenever that format rule is
required. xsl:decimal-format has a rich set of
attributes that describe the formatting rules. Table 2-1 explains each attribute and shows its default
value in parentheses.
|
Attribute
|
Purpose
|
|---|---|
|
name
|
An optional name for the rule. If absent, this rule becomes the
default rule. There can be only one default, and all names must be
unique (even when there is a difference in import precedence).
|
|
decimal-separator (.)
|
The character used to separate the whole and fractional parts of a
number.
|
|
grouping-separator (,)
|
The character used to separate groups of digits.
|
|
infinity (Infinity)
|
The string that represents infinity.
|
|
minus-sign (-)
|
round,
ceiling, and floor functions
always map numbers to integer values.
$pi =
3.1415926535897932:
<xsl:value-of select="round($pi * 10000) div 10000"/>
<xsl:value-of select="ceiling($pi * 10000) div 10000"/>
<xsl:value-of select="floor($pi * 10000) div 10000"/>
format-number( ):
<xsl:value-of select="format-number($pi,'#.####')"/>
format-number never uses a format specification as
an indication to remove significant digits from the whole part:
<xsl:value-of select="format-number($pi * 100,'#.####')"/>
format-number to get the effect of
truncating rather than rounding by using one more formatting digit
than required and then chopping off the last character:
<xsl:variable name="pi-to-5-sig" select="format-number($pi,'#.#####')"/> <xsl:value-of select="substring($pi-to-5-sig,1,string-length($pi-to-5-sig) -1)"/>
<xsl:value-of select="round($pi * 10000000000000000) div 10000000000000000"/>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://www.ora.com/XSLTCookbook/math">
<math:romans>
<math:roman value="1">i</math:roman>
<math:roman value="1">I</math:roman>
<math:roman value="5">v</math:roman>
<math:roman value="5">V</math:roman>
<math:roman value="10">x</math:roman>
<math:roman value="10">X</math:roman>
<math:roman value="50">l</math:roman>
<math:roman value="50">L</math:roman>
<math:roman value="100">c</math:roman>
<math:roman value="100">C</math:roman>
<math:roman value="500">d</math:roman>
<math:roman value="500">D</math:roman>
<math:roman value="1000">m</math:roman>
<math:roman value="1000">M</math:roman>
</math:romans>
<xsl:variable name="math:roman-nums" select="document('')/*/*/math:roman"/>
<xsl:template name="math:roman-to-number">
<xsl:param name="roman"/>
<xsl:variable name="valid-roman-chars">
<xsl:value-of select="document('')/*/math:romans"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="translate($roman,$valid-roman-chars,'')">NaN</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:roman-to-number-impl">
<xsl:with-param name="roman" select="$roman"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="math:roman-to-number-impl">
<xsl:param name="roman"/>
<xsl:param name="value" select="0"/>
<xsl:variable name="len" select="string-length($roman)"/>
<xsl:choose>
<xsl:when test="not($len)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:when test="$len = 1">
<xsl:value-of select="$value + $math:roman-nums[. = $roman]/@value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="roman-num"
select="$math:roman-nums[. = substring($roman, 1, 1)]"/>
<xsl:choose>
<xsl:when test="$roman-num/following-sibling::math:roman =
substring($roman, 2, 1)">
<xsl:call-template name="math:roman-to-number-impl">
<xsl:with-param name="roman" select="substring($roman,2,$len - 1)"/>
<xsl:with-param name="value" select="$value - $roman-num/@value"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:roman-to-number-impl">
<xsl:with-param name="roman" select="substring($roman,2,$len - 1)"/>
<xsl:with-param name="value" select="$value + $roman-num/@value"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet><xsl:variable name="math:base-lower"
select="'0123456789abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="math:base-upper"
select="'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template name="math:convert-base">
<xsl:param name="number"/>
<xsl:param name="from-base"/>
<xsl:param name="to-base"/>
<xsl:variable name="number-base10">
<xsl:call-template name="math:convert-to-base-10">
<xsl:with-param name="number" select="$number"/>
<xsl:with-param name="from-base" select="$from-base"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="math:convert-from-base-10">
<xsl:with-param name="number" select="$number-base10"/>
<xsl:with-param name="to-base" select="$to-base"/>
</xsl:call-template>
</xsl:template>
math:convert-to-base-10 normalizes
the input number to lowercase. Thus, for example, you treat
ffff hex the same as FFFF hex,
which is the normal convention. Two error checks are performed to
make sure the base is in the range you can handle and that the number
does not contain illegal characters inconsistent with the base. The
trivial case of converting from base 10 to base 10 is also handled:
<xsl:template name="math:convert-to-base-10">
<xsl:param name="number"/>
<xsl:param name="from-base"/>
<xsl:variable name="num"
select="translate($number,$math:base-upper, $math:base-lower)"/>
<xsl:variable name="valid-in-chars"
select="substring($math:base-lower,1,$from-base)"/>
<xsl:choose>
<xsl:when test="$from-base < 2 or $from-base > 36">NaN</xsl:when>
<xsl:when test="not($num) or translate($num,$valid-in-chars,'')">NaN</xsl:when>
<xsl:when test="$from-base = 10">
<xsl:value-of select="$number"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:convert-to-base-10-impl">
<xsl:with-param name="number" select="$num"/>
<xsl:with-param name="from-base" select="$from-base"/>
<xsl:with-param name="from-chars" select="$valid-in-chars"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template><xsl:template name="math:abs">
<xsl:param name="x"/>
<xsl:choose>
<xsl:when test="$x < 0">
<xsl:value-of select="$x * -1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
true always converts to the number 1 and
false to the number 0.
<xsl:template name="math:abs">
<xsl:param name="x"/>
<xsl:value-of select="(1 - 2 *($x < 0)) * $x"/>
</xsl:template>
sqrt to EXSLT that uses
Newton's method:
<xsl:template name="math:sqrt">
<!-- The number you want to find the square root of -->
<xsl:param name="number" select="0"/>
<!-- The current 'try'. This is used internally. -->
<xsl:param name="try" select="1"/>
<!-- The current iteration, checked against maxiter to limit loop count -->
<xsl:param name="iter" select="1"/>
<!-- Set this up to ensure against infinite loops -->
<xsl:param name="maxiter" select="20"/>
<!-- This template was written by Nate Austin using Sir Isaac Newton's
method of finding roots -->
<xsl:choose>
<xsl:when test="$try * $try = $number or $iter > $maxiter">
<xsl:value-of select="$try"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:sqrt">
<xsl:with-param name="number" select="$number"/>
<xsl:with-param name="try" select="$try -
(($try * $try - $number) div (2 * $try))"/>
<xsl:with-param name="iter" select="$iter + 1"/>
<xsl:with-param name="maxiter" select="$maxiter"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template><xsl:template name="math:sum">
<!-- Initialize nodes to empty node set -->
<xsl:param name="nodes" select="/.."/>
<xsl:param name="result" select="0"/>
<xsl:choose>
<xsl:when test="not($nodes)">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:otherwise>
<!-- call or apply template that will determine value of node
unless the node is literally the value to be summed -->
<xsl:variable name="value">
<xsl:call-template name="some-function-of-a-node">
<xsl:with-param name="node" select="$nodes[1]"/>
</xsl:call-template>
</xsl:variable>
<!-- recurse to sum rest -->
<xsl:call-template name="math:sum">
<xsl:with-param name="nodes" select="$nodes[position( ) != 1]"/>
<xsl:with-param name="result" select="$result + $value"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="math:sum-dvc">
<xsl:param name="nodes" select="/.."/>
<xsl:param name="result" select="0"/>
<xsl:param name="dvc-threshold" select="100"/>
<xsl:choose>
<xsl:when test="count($nodes) <= $dvc-threshold">
<xsl:call-template name="math:sum">
<xsl:with-param name="nodes" select="$nodes"/>
<xsl:with-param name="result" select="$result"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="half" select="floor(count($nodes) div 2)"/>
<xsl:variable name="sum1">
<xsl:call-template name="math:sum-dvc">
<xsl:with-param name="nodes" select="$nodes[position( ) <= $half]"/>
<xsl:with-param name="result" select="$result"/>
<xsl:with-param name="dvc-threshold" select="$dvc-threshold"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="math:sum-dvc">
<xsl:with-param name="nodes" select="$nodes[position( ) > $half]"/>
<xsl:with-param name="result" select="$sum1"/>
<xsl:with-param name="dvc-threshold" select="$dvc-threshold"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>math:min, math:max,
math:lowest, and math:highest.
min and
max find the value of the node with minimum and
maximum numerical value, respectively. EXSLT defines
math:min as follows:
The minimum value is defined as follows. The node set passed as an argument is sorted in ascending order as it would be by xsl:sort with a data type of number. The minimum is the result of converting the string value of the first node in this sorted list to a number using the number function.If the node set is empty, or if the result of converting the string values of any of the nodes to a number is NaN, then NaN is returned.
math:max is defined similarly. EXSLT provides pure
XSLT implementations that are literal implementations of this
definition, as shown in Example 2-9.
<xsl:template name="math:min">
<xsl:param name="nodes" select="/.." />
<xsl:choose>
<xsl:when test="not($nodes)">NaN</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$nodes">
<xsl:sort data-type="number" />
<xsl:if test="position( ) = 1">
<xsl:value-of select="number(.)" />
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="math:max">
<xsl:param name="nodes" select="/.." />
<xsl:choose>
<xsl:when test="not($nodes)">NaN</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$nodes">
<xsl:sort data-type="number" order="descending" />
<xsl:if test="position( ) = 1">
<xsl:value-of select="number(.)" />
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template><xsl:template name="math:median">
<xsl:param name="nodes" select="/.."/>
<xsl:variable name="count" select="count($nodes)"/>
<xsl:variable name="middle" select="ceiling($count div 2)"/>
<xsl:variable name="even" select="not($count mod 2)"/>
<xsl:variable name="m1">
<xsl:for-each select="$nodes">
<xsl:sort data-type="number"/>
<xsl:if test="position( ) = $middle">
<xsl:value-of select=". + ($even * ./following-sibling::*[1])"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- The median -->
<xsl:value-of select="$m1 div ($even + 1)"/>
</xsl:template>
$m1 ends up being equal to the
middle node, and you divide by 1 to get the answer. On the other
hand, if the number of nodes is odd, $m1 ends up
being the sum of the two middle nodes, and you divide by two to get
the answer.
<xsl:template name="math:mode">
<xsl:param name="nodes" select="/.."/>
<xsl:param name="max" select="0"/>
<xsl:param name="mode" select="/.."/>
<xsl:choose>
<xsl:when test="not($nodes)">
<xsl:copy-of select="$mode"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="first" select="$nodes[1]"/>
<xsl:variable name="try" select="$nodes[<xsl:template name="math:P">
<xsl:param name="n" select="1"/>
<xsl:param name="r" select="1"/>
<xsl:choose>
<xsl:when test="$n < 0 or $r < 0">NaN</xsl:when>
<xsl:when test="$n = 0">0</xsl:when>
<xsl:otherwise>
<xsl:call-template name="prod-range">
<xsl:with-param name="start" select="$r + 1"/>
<xsl:with-param name="end" select="$n"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="math:C">
<xsl:param name="n" select="1"/>
<xsl:param name="r" select="1"/>
<xsl:choose>
<xsl:when test="$n < 0 or $r < 0">NaN</xsl:when>
<xsl:when test="$n = 0">0</xsl:when>
<xsl:otherwise>
<xsl:variable name="min"
select="($r <= $n - $r) * $r + ($r > $n - $r) * $n - $r"/>
<xsl:variable name="max"
select="($r >= $n - $r) * $r + ($r < $n - $r) * $n - $r"/>
<xsl:variable name="numerator">
<xsl:call-template name="prod-range">
<xsl:with-param name="start" select="$max + 1"/>
<xsl:with-param name="end" select="$n"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="denominator">
<xsl:call-template name="math:fact">
<xsl:with-param name="number" select="$min"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$numerator div $denominator"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" id="bittesting">
<!--powers of two-->
<xsl:variable name="bit15" select="32768"/>
<xsl:variable name="bit14" select="16384"/>
<xsl:variable name="bit13" select="8192"/>
<xsl:variable name="bit12" select="4096"/>
<xsl:variable name="bit11" select="2048"/>
<xsl:variable name="bit10" select="1024"/>
<xsl:variable name="bit9" select="512"/>
<xsl:variable name="bit8" select="256"/>
<xsl:variable name="bit7" select="128"/>
<xsl:variable name="bit6" select="64"/>
<xsl:variable name="bit5" select="32"/>
<xsl:variable name="bit4" select="16"/>
<xsl:variable name="bit3" select="8"/>
<xsl:variable name="bit2" select="4"/>
<xsl:variable name="bit1" select="2"/>
<xsl:variable name="bit0" select="1"/>
<xsl:template name="bitTest">
<xsl:param name="num"/>
<xsl:param name="bit" select="$bit0"/>
<xsl:choose>
<xsl:when test="( $num mod ( $bit * 2 ) ) -
( $num mod ( $bit ) )">1</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="bitAnd">
<xsl:param name="num1"/>
<xsl:param name="num2"/>
<xsl:param name="result" select="0"/>
<xsl:param name="test" select="$bit15"/>
<xsl:variable name="nextN1"
select="($num1 >= $test) * ($num1 - $test) + not($num1 >= $test) * $num1"/>
<xsl:variable name="nextN2"
select="($num2 >= $test) * ($num2 - $test) + not($num2 >= $test) * $num2"/>
<xsl:choose>
<xsl:when test="$test < 1">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:when test="$num1 >= $test and $num2 >= $test">
<xsl:call-template name="bitAnd">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result + $test"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="bitAnd">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="bitOr">
<xsl:param name="num1"/>
<xsl:param name="num2"/>
<xsl:param name="result" select="0"/>
<xsl:param name="test" select="$bit15"/>
<xsl:variable name="nextN1"
select="($num1 >= $test) * ($num1 - $test) + not($num1 >= $test) * $num1"/>
<xsl:variable name="nextN2"
select="($num2 >= $test) * ($num2 - $test) + not($num2 >= $test) * $num2"/>
<xsl:choose>
<xsl:when test="$test < 1">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:when test="$num1 >= $test or $num2 >= $test">
<xsl:call-template name="bitOr">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result + $test"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="bitOr">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="bitXor">
<xsl:param name="num1"/>
<xsl:param name="num2"/>
<xsl:param name="result" select="0"/>
<xsl:param name="test" select="$bit15"/>
<xsl:variable name="nextN1"
select="($num1 >= $test) * ($num1 - $test) + not($num1 >= $test) * $num1"/>
<xsl:variable name="nextN2"
select="($num2 >= $test) * ($num2 - $test) + not($num2 >= $test) * $num2"/>
<xsl:choose>
<xsl:when test="$test < 1">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:when test="$num1 >= $test and not($num2 >= $test)
or not($num1 >= $test) and $num2 >= $test">
<xsl:call-template name="bitXor">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result + $test"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="bitXor">
<xsl:with-param name="num1" select="$nextN1"/>
<xsl:with-param name="num2" select="$nextN2"/>
<xsl:with-param name="result" select="$result"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="bitNot">
<xsl:param name="num"/>
<xsl:param name="result" select="0"/>
<xsl:param name="test" select="$bit15"/>
<xsl:choose>
<xsl:when test="$test < 1">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:when test="$num >= $test">
<xsl:call-template name="bitNot">
<xsl:with-param name="num" select="$num - $test"/>
<xsl:with-param name="result" select="$result"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="bitNot">
<xsl:with-param name="num" select="$num"/>
<xsl:with-param name="result" select="$result + $test"/>
<xsl:with-param name="test" select="$test div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>