BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


XSLT Cookbook
XSLT Cookbook Solutions and Examples for XML and XSLT Developers By Sal Mangano
December 2002
Pages: 670

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Strings
I believe everybody in the world should have guns. Citizens should have bazookas and rocket launchers too. I believe that all citizens should have their weapons of choice. However, I also believe that only I should have the ammunition. Because frankly, I wouldn't trust the rest of the goobers with anything more dangerous than [a] string.
—Scott Adams
When it comes to manipulating strings, XSLT certainly lacks the heavy artillery of Perl. XSLT is a language optimized for processing XML markup, not strings. However, since XML is simply a structured form of text, string processing is inevitable in all but the most trivial transformation problems. Unfortunately, XSLT has only nine standard functions for string processing. Java, on the other hand, has about two dozen, and Perl, the undisputed king of modern text-processing languages, has a couple dozen plus a highly advanced regular-expression engine.
XSLT programmers have two choices when they need to perform advanced string processing. First, they can call out to external functions written in Java or some other language supported by their XSLT processor. This choice is wise if portability is not an issue and fairly heavy-duty string manipulation is needed. Second, they can implement the advanced string-handling functionality directly in XSLT. This chapter shows that quite a bit of common string manipulation can be done within the confines of XSLT. Advanced string capabilities are implemented in XSLT by combining the capabilities of the native string functions and by exploiting the power of recursion, which is an integral part of all advanced uses of XSLT. In fact, recursion is such an important technique in XSLT that it is worthwhile to look through some of these recipes even if you have no intention of implementing your string-processing needs directly in XSLT.
This book also refers to the excellent work of EXSLT.org, a community initiative that helps standardize extensions to the XSLT language. You may want to check out their site at http://www.exslt.org.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Testing if a String Ends with Another String
You need to test if a string ends with a particular substring.
substring($value, (string-length($value) - string-length($substr)) + 1) = $substr
XSLT contains a native 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.
Programmers used to having the first position in a string start at index 0 should note that XSLT strings start at index 1.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Finding the Position of a Substring
You want to find the index of a substring within a string rather than the text before or after 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>
The position of a substring within another string is simply the length of the string preceding it plus 1. If you are certain that the target string contains the substring, then you can simply use 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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Removing Specific Characters from a String
You want to strip certain characters (e.g., whitespace) from a string.
Use translate with an empty replace string. For example, the following code can strip whitespace from a string:
translate($input," &#x9;&#xa;&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.
You can also use 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',''),'')
The inner 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.
Sometimes you do not want to remove all occurrences of whitespace, but instead want to remove leading, trailing, and redundant internal whitespace. XPath has a built-in function, 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")
However, this transformation won't work quite right if the input string contains whitespace characters other than spaces, i.e., tab (#x9), newline (#xA), and carriage return (#xD). The reason is that the code swaps space with the character to normalize and then normalizes the resulting spaces and swaps back. If nonspace whitespace remains after the first transformation, it will also be normalized, which might not be what you want. Then again, the applications of nonwhitespace normalizing are probably rare anyway. Here you use this technique to remove extra
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Finding Substrings from the End of a String
XSLT does not have any functions for searching strings in reverse.
Using recursion, you can emulate a reverse search with a search for the last occurrence of 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>
Both XSLT string-searching functions (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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Duplicating a String N Times
You need to duplicate a string N times, where N is a parameter. For example, you might need to pad out a string with spaces to achieve alignment.
A nice solution is a recursive approach that doubles the input string until it is the required length while being careful to handle cases in which $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>
The most obvious way to duplicate a string $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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Reversing a String
You need to reverse the characters of a string.
This template reverses $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 &lt; 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>
The algorithm shown in the solution is not the most obvious, but it is efficient. In fact, this algorithm successfully reverses even very large strings, whereas other more obvious algorithms either take too long or fail with a stack overflow. The basic idea behind this algorithm is to swap the first half of the string with the second half and to keep applying the algorithm to these halves recursively until you are left with strings of length two or less, at which point the reverse operation is trivial. The following example illustrates how this algorithm works. At each step, I placed a + where the string was split and concatenated.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Replacing Text
You want to replace all occurrences of a substring within a target string with another string.
The following recursive template replaces all occurrences of a search string with a replacement string.
<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>
If you want to replace only whole words, then you must ensure that the characters immediately before and after the search string are in the class of characters considered word delimiters. We chose the characters in the variable $punc plus whitespace to be word delimiters:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Converting Case
You want to convert an uppercase string to lowercase or vice versa.
Use the XSLT translate( ) function. This code, for example, converts from upper- to lowercase:
translate($input,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')
This example converts from lower- to uppercase:
translate($input, 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')
This recipe is, of course, trivial. However, I include it as an opportunity to discuss the solution's shortcomings. Case conversion is trivial as long as your text is restricted to a single locale. In English, you rarely, if ever, need to deal with special characters containing accents or other complicated case conversions in which a single character must convert to two characters. The most common example is German, in which the lowercase "ß" is converted to an uppercase "SS". Many modern programming languages provide case-conversion functions that are sensitive to locale, but XSLT does not support this concept directly. This is unfortunate, considering that XSLT has other features supporting internationalization.
A slight improvement can be made by defining general XML entities for each type conversion, as shown in the following example:
<?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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Tokenizing a String
You want to break a string into a list of tokens based on the occurrence of one or more delimiter characters.
Jeni Tennison implemented this solution (but the comments are my doing). The tokenizer returns each token as a node consisting of a <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="' &#x9;&#xA;'" />
  <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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Making Do Without Regular Expressions
You would like to perform regular-expression-like operations but you don't want to resort to nonstandard extensions.
Several common regular-expression-like matches can be emulated in native XSLT. Table 1-1 lists the regular-expression matches by using Perl syntax along with their XSLT/XPath equivalent. The single character "C" is a proxy for any user-specified single character, and the string "abc" is a proxy for any user supplied-string of nonzero length.
Table 1-1: Regular-expression matches
$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
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using the EXSLT String Extensions
You have good reason to use extension functions for string processing, but you are concerned about portability.
You may find that your XSLT processor already implements string functions defined by the EXSLT community (http://www.exslt.org/). At the time of publication, these functions are:
node-set str:tokenize(string input, string delimiters?)
The str:tokenize function splits up a string and returns a node set of token elements, each containing one token from the string.
The first argument is the string to be tokenized. The second argument is a string consisting of a number of characters. Each character in this string is taken as a delimiting character. The string given by the first argument is split at any occurrence of any character.
If the second argument is omitted, the default is the string &#x9;&#xA;&#xD;&#x20; (i.e., whitespace characters).
If the second argument is an empty string, the function returns a set of token elements, each of which holds a single character.
node-set str:replace(string, object search, object replace)
The str:replace function replaces any occurrences of search strings within a string with replacement nodes to create a node set.
The first argument gives the string within which strings are to be replaced.
The second argument is an object that specifies a search string list. If the second argument is a node set, then the search string list shows the result of converting each node in the node set to a string with the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Numbers and Math
Arithmetic is being able to count up to twenty without taking off your shoes.
—Mickey Mouse
Chapter 1 lamented the absence of sophisticated string-processing facilities in native XSLT. By comparison, XSLT's handling of numerical computation is truly "Mickey Mouse"! XSLT 1.0 gives you facilities for basic arithmetic, counting, summing, and formatting numbers, but the remaining mathematics is up to your sheer wit. Fortunately, as with strings, XSLT's recursive powers permit reasonable mathematical feats with reasonable effort.
Do not expect to find matrix multiplication or Fast-Fourier transform recipes in this section. If you really need them to perform on XML-encoded data then XSLT is not the language for you. Instead, bring the data into a C, C++, or Fortran program using an XSLT frontend converter or native SAX or DOM interface. Nevertheless, a web page called "Gallery of Stupid XSL and XSLT Tricks" (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.
Some of this section's early examples read more like tutorials on how to use native functionality in XSLT. I include these examples because they represent XSLT facilities that are sometimes misunderstood.
Many of the recipes shown here are implementations of EXSLT's math definitions. When a pure XSLT implementation is available at EXSLT.org, we will discuss that first and then consider alternative solutions. Pure XSLT implementations are provided for all extensions defined in EXSLT math except for trigonometric functions (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.
Many discussion sections in this chapter will explore alternative implementations of the solution. Readers uninterested in technical details are encouraged simply to use the example shown in the solution section since it will be the best solution or as good as the alternatives.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Formatting Numbers
You need to display numbers in various formats.
This problem has two general solutions.

Section 2.1.2.1: Use xsl:decimal-format in conjunction with format-number( )

The top-level element 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.
Table 2-1: Attributes of the xsl:decimal-format element
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 (-)
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Rounding Numbers to a Specified Precision
You want to round to a specific number of decimal places; however, XSLT's round, ceiling, and floor functions always map numbers to integer values.
Multiply, round, and divide using a power of ten that determines how many decimal digits are required. Assuming $pi = 3.1415926535897932:
<xsl:value-of select="round($pi * 10000) div 10000"/>
results in 3.1416. Similarily:
<xsl:value-of select="ceiling($pi * 10000) div 10000"/>
results in 3.1416, and:
<xsl:value-of select="floor($pi * 10000) div 10000"/>
results in 3.1415.
Rounding to a specific number of decimal places is also achieved using format-number( ):
<xsl:value-of select="format-number($pi,'#.####')"/>
This results in 3.1416. This will work even if more than one significant digit is in the whole part because 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,'#.####')"/>
This results in 314.1593.
You can use 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)"/>
This results in 3.1415.
This multiply, round, and divide technique works well as long as the numbers involved remain within the representational limits of IEEE floating point. If you try to capture too many places after the decimal, then the rules of IEEE floating point will interfere with the expected result. For example, trying to pick up 16 decimal digits of pi will give you only 15:
<xsl:value-of select="round($pi * 10000000000000000) div 10000000000000000"/>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Converting from Roman Numerals to Numbers
You need to convert a Roman numeral to a number.
Roman numbers do not use a place value system; instead, the number is composed by adding or subtracting the fixed value of the specified Roman numeral characters. If the following character has a lower or equal value, you add; otherwise, you subtract:
<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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Converting from One Base to Another
You need to convert strings representing numbers in some base to numbers in another base.
This example provides a general solution for converting from any base between 2 and 36 to any base in the same range. It uses two global variables to encode the value of all characters in a base 36 system as offsets into the string—one for uppercase encoding and the other for lowercase:
<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>
This template reduces the general problem to two subproblems of converting to and from base 10. Performing base 10 conversions is easier because it is the native base of XPath numbers.
The 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 &lt; 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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Implementing Common Math Functions
You need to go beyond fifth-grade math even though XSLT 1.0 does not.
Pure XSLT implementations are provided for absolute value, square root, logarithms, power, and factorial.

Section 2.5.2.1: Absolute value: math:abs(x)

The obvious but long-winded way to determine the absolute value of a number is shown here:
<xsl:template name="math:abs">
     <xsl:param name="x"/>
     
     <xsl:choose>
          <xsl:when test="$x &lt; 0">
               <xsl:value-of select="$x * -1"/>
          </xsl:when>
          <xsl:otherwise>
               <xsl:value-of select="$x"/>
          </xsl:otherwise>
     </xsl:choose>
     
</xsl:template>
The short but obscure way relies on the fact that the 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 &lt; 0)) * $x"/>
</xsl:template>
I prefer the latter because it is concise. Alternatively, you can use an extension function (see Chapter 12).

Section 2.5.2.2: Square root: math:sqrt(x)

Nate Austin contributed a native XSLT 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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Computing Sums and Products
You need to sum or multiply functions of numbers contained in a node set.
The abstract form of sum for processors that support tail-recursive optimization is as follows:
<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>
Two techniques can handle a large number of nodes in the absence of tail-recursive optimization. The first is commonly called divide and conquer. The idea behind this technique is to reduce the amount of work by at least a factor of two on each recursive step:
<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) &lt;= $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(  ) &lt;= $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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Finding Minimums and Maximums
You need to find the minimum (or maximum) numerical node (or nodes) in a node set.
The EXSLT functions that perform these operations are 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.
Example 2-9. EXSLT min and max implement directly from the definition
<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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Computing Statistical Functions
You need to compute averages, variances, and standard deviations.
Three types of averages are used by statisticians: the mean (layperson's average), the median, and the mode.
The mean is trivial—simply sum using Recipe 2.6 and divide by the count.
The median is the number that falls in the middle of the set of numbers when they are sorted. If the count is even, then the mean of the two middle numbers is generally taken:
<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>
Handling the even case relies on the Boolean-to-number conversion trick used in several other examples in this book. If the number of nodes is odd, $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.
The mode is the most frequently occurring element(s) in a set of elements that need not be numbers. If identical nodes compare with equality on their string values, then the following solution does the trick:
<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[
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Computing Combinatorial Functions
You need to compute the number of permutations or combinations of size r of a given set.
If you know the formula for permutations of size r is N! / r! and you know that the formula for combinations is N! / r! * (N-r)!, then you might disregard this example; this book already gave an example for factorial. However, since factorials get very big quickly, you need to be a little crafty to get the best bang for your calculating buck:
<xsl:template name="math:P">
     <xsl:param name="n" select="1"/>
     <xsl:param name="r" select="1"/>
      <xsl:choose>
        <xsl:when test="$n &lt; 0 or $r &lt; 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 &lt; 0 or $r &lt; 0">NaN</xsl:when>
        <xsl:when test="$n = 0">0</xsl:when>
        <xsl:otherwise>
          <xsl:variable name="min" 
               select="($r &lt;= $n - $r) * $r +  ($r > $n - $r) * $n - $r"/>
          <xsl:variable name="max" 
               select="($r >= $n - $r) * $r +  ($r &lt; $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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Testing Bits
You want to treat numbers as bit masks even though XSLT does not have integers or associated bitwise operators.
When working with XML, don't go out of your way to encode information in bits. Use this solution only when you have no control over the encoding of the data.
The following solution works on 16-bit numbers, but can easily be extended up to 32:
<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 &lt; 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 &lt; 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 &lt; 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 &lt; 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>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Dates and Times
Does anyone really know what time it is? Does anyone really care?
—Chicago
Native XSLT 1.0 does not know what time it is and does not seem to care. However, dates and times are a necessary aspect of everyday life. The need to manipulate them arises frequently in computing, especially in web development. Therefore, it is surprising and unfortunate that standard XSLT does not have any built-in date and time support.
The examples in this section can help compensate for XSLT's lack of support for dates and times. Unfortunately, one of the most crucial date and time capabilities cannot be implemented in XSLT—that is, getting the current date and time. For that, you need to call out to another language whose library supports interacting with the hardware's real-time clock. Both Java and JavaScript have this capability. If your application just needs to format dates and times that already exist in a document, then the routines here should cover most needs.
Data and time manipulation and conversion can be tricky, but it is almost purely an exercise in intricate integer arithmetic involving what are essentially base conversions in a mixed radix system. Working with non-Gregorian calendars and determining holidays also requires quite a bit of historical, religious, and cultural knowledge. Readers with no application for date and time routines may wish to skip this chapter because little by way of XSLT technique is unique to these algorithms. Those who are curious about the theory behind these calculations should definitely look at the papers cited in Section 3.1.1.
I am grateful to Jason Diamond for graciously contributing many of the templates dealing with Gregorian time. The XSLT code for dealing with non-Gregorian calendars was adapted from Edward M. Reingold's public domain Lisp implementation. Some of the algorithms were adapted to better suit XSLT and build upon the existing foundation provided by Jason's code.
Do not be confused by the technique used to pass a date into most templates. It is designed for maximum convenience. You can pass in the date in two ways: by using a string formatted using ISO date-time rules and by using the individual parameters for the year, month, and day. The following example should clarify usage:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Introduction
Native XSLT 1.0 does not know what time it is and does not seem to care. However, dates and times are a necessary aspect of everyday life. The need to manipulate them arises frequently in computing, especially in web development. Therefore, it is surprising and unfortunate that standard XSLT does not have any built-in date and time support.
The examples in this section can help compensate for XSLT's lack of support for dates and times. Unfortunately, one of the most crucial date and time capabilities cannot be implemented in XSLT—that is, getting the current date and time. For that, you need to call out to another language whose library supports interacting with the hardware's real-time clock. Both Java and JavaScript have this capability. If your application just needs to format dates and times that already exist in a document, then the routines here should cover most needs.
Data and time manipulation and conversion can be tricky, but it is almost purely an exercise in intricate integer arithmetic involving what are essentially base conversions in a mixed radix system. Working with non-Gregorian calendars and determining holidays also requires quite a bit of historical, religious, and cultural knowledge. Readers with no application for date and time routines may wish to skip this chapter because little by way of XSLT technique is unique to these algorithms. Those who are curious about the theory behind these calculations should definitely look at the papers cited in Section 3.1.1.
I am grateful to Jason Diamond for gra