Strings

1 Quoting of special characters within XPath string
2 URL decomposition
3 Replacing a string by another which is found by reference in the same XML document
4 Quotes in a string
5 splitting lines at n characters
6 String split into elements
7 Replacing newline with break.
8 CRLF to BR
9 Trim or pad a string to a fixed length
10 Computing string-length of nodesets
11 Substituting substrings in an element's text for HTML output
12 Substring-after, last occurrence
13 lastIndexOf('char')
14 count the number of specific characters in a string.
15 Reverse a string

1 Quoting of special characters within XPath string

David Carlisle

>  How do I have to escape
>  quotes to get a well-formed XPath string?

First just consider the xpath syntax.

You can use " or ' to delimit a string literal, so if you only want one then you can delimit with the other.

  "'"  or '"'

if you want both then you can not do it directly in a string literal but you can construct the string '" using

translate('a"','a',"'")
or
concat("'",'"')

or if you drop out of xpath, to xslt

<xsl:variable name="x">'"</xsl:variable>

then use $x as this result tree fragment will coerce to a string.

Then you need to get one of those expressions into an XML attribute If you use " to delimit the attribute value then you need to quote " so you end up with

select="translate('a"','a',"'")"

which looks a bit odd but the XML parser eats that and gives the xpath system translate('a"','a',"'") which takes the string a" and replaces the a by '.

2 URL decomposition

Juliane Harbarth

Can you think of a way to decompose a URL? I want to take something like: http://www.agilic.com/purchase.htm and end up with purchase.htm. ...

You can do this by using a recursive named template, e.g.

 <xsl:template name="filename">
   <xsl:param name="x"/>
   <xsl:choose>
     <xsl:when test="contains($x,'/')">
       <xsl:call-template name="filename">
         <xsl:with-param name="x" select="substring-after($x,'/')"/>
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise>
       <xsl:value-of select="$x"/>
     </xsl:otherwise>
   </xsl:choose>
 </xsl:template>

and call it somehow like :

 <xsl:call-template name="filename">
  <xsl:with-param name="x" select="."/>
 </xsl:call-template>

Jens Lautenbacher completes the picture with

This template get's a filename and gives back the directory part of it. Giving back the filename part is achieved along the same line

You call it somehow like

<xsl:call-template name="strip">
<xsl:with-param name="relfile">foo/bar/baz.xml</xsl:with-param>
</xsl:call-template>
  <xsl:template name="strip">
    <xsl:param name="reldir"/>
    <xsl:param name="relfile"/>
    <xsl:choose>
      <xsl:when test="contains($relfile, '/')">
      <xsl:call-template name="strip">
        <xsl:with-param name="relfile">
          <xsl:value-of select="substring-after($relfile,'/')"/>
        </xsl:with-param>
        <xsl:with-param name="reldir">
          <xsl:value-of 
    select="concat($reldir, substring-before($relfile,'/'), '/')"/>
        </xsl:with-param>
      </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$reldir"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

3 Replacing a string by another which is found by reference in the same XML document

Jeni Tennison

Q expansion

>My source XML contains something like:
> <Root>
> <Class
>      Name="MyClass"
>      Uuid="80A2B3BD-0000-520C-383BE4980006BE67"
>      TargetRef="80A2B3BD-0000-520C-383BE4980006A75A">
></Class>
>
><AnotherObject
>Name="MyObject"
>Uuid="80A2B3BD-0000-520C-383BE4980006B687">
></AnotherObject>
></Root>
>
>I have an XSL style sheet  to display it on IE5 with apropriate style. I
>would like to get as output:
>
>Class:
>Name="MyClass"
>TargetRef="MyObject"
>
>Where the TargetRef string has been replaced by the value of the string
>with the same Uuid.


 

This seems to me to be a good instance to use xsl:key to identify the nodes that are uniquely identified through the 'Uuid' attribute. First, set up the key:

* name  - a name for the key, anything you like
* match - an XPath matching the nodes that you want to identify
* use   - an XPath (relative to the 'match' node) that identifies the node

In your case:

<xsl:key name="objects" match="*[@Uuid]" use="@Uuid" />

Note that I haven't named the (element) nodes that are identified by the key because it isn't clear to me whether your 'Class' and 'AnotherObject' elements are indicative of a whole range of possible element names in your input, but we can guarantee at least that they will have a 'Uuid' attribute if they're worth identifying!

Then you can access a particular node through its 'Uuid' attribute using the key() function, so try:

<xsl:template match ="Class">
  Class:
  Name="<xsl:value-of select="@Name" />"
  TargetRef="<xsl:value-of select="key('objects', @TargetRef)/@Name" />"
</xsl:template>

4 Quotes in a string

Mike Brown

How do you put ' or " into a string?

<xsl:variable name="apos">'</xsl:variable>
<xsl:variable name="quot">"</xsl:variable>
<xsl:variable name="foo" select="concat($quot,'Hello, world!',$quot)"/>

How do you test for the presence of ' or " in a string?

<!-- same $apos and $quot assignments, then... -->
<xsl:if test="contains($foo,$apos) or contains($foo,$quot)">
  ...
</xsl:if>

Jeni Tennison rounds out the discussion..

Good question! XML defines entities for ' and " (&amp;apos; and &amp;quot;, somewhat unsurprisingly). In certain situations, it is possible to use these. Your first example, for instance, could also be given as:

<xsl:variable name="foo" select="'&amp;quot;Hello, world!&amp;quot;'"/>

When this is parsed by the XML parser, the value of the 'select' attribute is set to (no extra quotes included): '"Hello, world!"'

When the XSLT Processor sees this, it recognises the external quotes as designating a string value, and so sets the variable $foo to the string (no extra quotes included): "Hello, world!"

The thing to remember is that you are escaping the " and ' *for the XML parser* and not for the XSLT processor. So your second example:

>How do you test for the presence of ' or " in a string?
>
><!-- same $apos and $quot assignments, then... -->
><xsl:if test="contains($foo,$apos) or contains($foo,$quot)">
>  ...
></xsl:if>

can be escaped as:

<xsl:if test="contains($foo, &amp;quot;'&amp;quot;) or contains($foo '&amp;quot;')">
...
</xsl:if>

As there are no unescaped "s within the attribute value, the XML parser can parse this and emerges with the value of the 'test' attribute as:

  contains($foo, "'") or contains($foo, '"')

The XSLT processor can again recognise that "'" designates a string with the value of the single character ' and that '"' designates a string with the value of the single character ".

Similarly, if you wanted single quotes rather than double quotes around your Hello, World!, then you should do:

<xsl:variable name="foo" select="&quot;'Hello, world!'&quot;" />

(-> "'Hello, world!'" att. value -> 'Hello, world!' string)

[Or, alternatively:

<xsl:variable name="foo" select='"&apos;Hello, world!&apos;"' />

(-> "'Hello, world!'" att. value -> 'Hello, world!' string)]

So, for fairly simple situations like this, it is enough to use the normal XML escaping to get the XSLT processor to see something that it can understand. However, difficulties arise when the quote nesting goes deeper than this. For example, if you wanted to see whether a string contains the string (no extra quotes): "You're here"

There is no way to wrap quotes around that string, and no way that I know of within XSLT/XPath to escape internal quotes like this (the XSLT processor is not an XML parser - it won't detect and recognise &quot;/&apos; itself). In these cases, your method, using variables, is the only solution.

(BTW, I'd personally declare the variables as:

<xsl:variable name="apos" select='"&apos;"' />
<xsl:variable name="quot" select="'&quot;'" />

so that they are set as strings rather than result tree fragments.)

5 splitting lines at n characters

Paul Tchistopolskii

To me that was not rudimentary ( taking into account that there could be exotic situations when some long word is really > 60, so space-scanning could fail ). Anyway.

The snippet below takes into account the whitespace not splitting the word in the middle. It looks back to the first space to prevent that. if it fails - it just splits. tune-width does it.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

 <xsl:template match="/doc">
 <HTML><BODY><PRE>
    <xsl:call-template name="format">
    <xsl:with-param select="normalize-space(para)" name="txt" /> 
     <xsl:with-param name="width">30</xsl:with-param> 
     </xsl:call-template>
  </PRE></BODY></HTML>
  </xsl:template>

  <xsl:template name="format">
   <xsl:param name="txt" /> 
   <xsl:param name="width" /> 

  <xsl:if test="$txt">
   <xsl:variable name="real-width">
    <xsl:call-template name="tune-width">
     <xsl:with-param select="$txt" name="txt" /> 
       <xsl:with-param select="$width" name="width" /> 
       <xsl:with-param select="$width" name="def" /> 
  </xsl:call-template>
   </xsl:variable>

   <xsl:value-of select="substring($txt, 1, $real-width)" /> 

<xsl:text>
</xsl:text> 

   <xsl:call-template name="format">
    <xsl:with-param select="substring($txt,$real-width + 1)" name="txt" /> 
    <xsl:with-param select="$width" name="width" /> 
   </xsl:call-template>

  </xsl:if>
  </xsl:template>


  <xsl:template name="tune-width">
  <xsl:param name="txt" /> 
  <xsl:param name="width" /> 
  <xsl:param name="def" /> 

  <xsl:choose>
  <xsl:when test="$width = 0">
  <xsl:value-of select="$def" /> 
  </xsl:when>
  <xsl:otherwise>
  <xsl:choose>
  <xsl:when test="substring($txt, $width, 1 ) = ' '">
  <xsl:value-of select="$width" /> 
  </xsl:when>
  <xsl:otherwise>
  <xsl:call-template name="tune-width">
  <xsl:with-param select="$txt" name="txt" /> 
  <xsl:with-param select="$width - 1" name="width" /> 
  <xsl:with-param select="$def" name="def" /> 
  </xsl:call-template>
  </xsl:otherwise>
  </xsl:choose>
  </xsl:otherwise>
  </xsl:choose>
  </xsl:template>

  </xsl:stylesheet>

Input:

<doc>

<para>
 123456 2345 343434 545454 43434 343 
 12345 343434 545454 43434 343 
 32345645 343434 545454 43434 343 
 3422222225 343434 545454 43434 343 
 llllllllllllllllllllllooooooooooooooonnnnnnnnnnnggggggggg
 345 343434 545454 43434 343 
</para>

</doc>

Output:

<HTML>
<BODY>
<PRE>123456 2345 343434 545454 
43434 343 12345 343434 545454 
43434 343 32345645 343434 
545454 43434 343 3422222225 
343434 545454 43434 343 
lllllllllllllllllllllloooooooo
ooooooonnnnnnnnnnnggggggggg 
345 343434 545454 43434 
343
</PRE>
</BODY>
</HTML>

6 String split into elements

Jarno Elovirta

I had need to split out an element content into cross references

<doc>
  <elem>5,6,7</elem>
</doc>

to produce <a href="#id5>5</a> etc.


<xsl:template match="doc/elem">
<body>
  <xsl:call-template name="links">
    <xsl:with-param name="str" select="."/>
  </xsl:call-template>
  </body>
</xsl:template>

<xsl:template name="links">
  <xsl:param name="str"/>
  <xsl:choose>
    <xsl:when test="contains($str,',')">
      <a href="#id{substring-before($str,',')}"><xsl:value-of
select="substring-before($str,',')"/></a>
      <xsl:text>&#xA0;</xsl:text>
      <xsl:call-template name="links">
        <xsl:with-param name="str" select="substring-after($str,',')"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <a href="#id{$str}"><xsl:value-of select="$str"/></a>
      <xsl:text>&#xA0;</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

7 Replacing newline with break.

Norman Walsh

These templates insert "<br/>"s between the lines of an address. Adapt at will :-)

Test input

   <address>
   some text on 
   multiple lines
   which is required to be split up
   into verbatim lines, for html output
  </address>

Stylesheet.

   
   
   
   
   <xsl:template match="address//text()">
     <xsl:call-template name="make-verbatim">
       <xsl:with-param name="text" select="."/>
     </xsl:call-template>
   </xsl:template>
   
   <xsl:template name="make-verbatim">
     <xsl:param name="text" select="''"/>
   
     <xsl:variable name="starts-with-space"
                   select="substring($text, 1, 1) = ' '"/>
   
     <xsl:variable name="starts-with-nl"
                   select="substring($text, 1, 1) = '&#xA;'"/>
   
     <xsl:variable name="before-space">
       <xsl:if test="contains($text, ' ')">
         <xsl:value-of select="substring-before($text, ' ')"/>
       </xsl:if>
     </xsl:variable>
   
     <xsl:variable name="before-nl">
       <xsl:if test="contains($text, '&#xA;')">
         <xsl:value-of 
	  select="substring-before($text, '&#xA;')"/>
       </xsl:if>
     </xsl:variable>
   
     <xsl:choose>
       <xsl:when test="$starts-with-space">
         <xsl:text>&#160;</xsl:text>
         <xsl:call-template name="make-verbatim">
           <xsl:with-param name="text" 
	  select="substring($text,2)"/>
         </xsl:call-template>
       </xsl:when>
   
       <xsl:when test="$starts-with-nl">
         <br/><xsl:text>&#xA;</xsl:text>
         <xsl:call-template name="make-verbatim">
           <xsl:with-param name="text" 
	  select="substring($text,2)"/>
         </xsl:call-template>
       </xsl:when>
   
       <!-- if the string before a space 
	  is shorter than the string before
            a newline, fix the space...-->
       <xsl:when test="$before-space != ''
                       and ((string-length($before-space)
                             &lt; string-length($before-nl))
                             or $before-nl = '')">
         <xsl:value-of select="$before-space"/>
         <xsl:text>&#160;</xsl:text>
         <xsl:call-template name="make-verbatim">
           <xsl:with-param name="text"
	  select="substring-after($text, ' ')"/>
         </xsl:call-template>
       </xsl:when>
   
       <!-- if the string before a newline 
	  is shorter than the string before
            a space, fix the newline...-->
       <xsl:when test="$before-nl != ''
                       and ((string-length($before-nl)
                             &lt; string-length($before-space))
                             or $before-space = '')">
         <xsl:value-of select="$before-nl"/>
         <br/><xsl:text>&#xA;</xsl:text>
         <xsl:call-template name="make-verbatim">
           <xsl:with-param name="text" 
	  select="substring-after($text, '&#xA;')"/>
         </xsl:call-template>
       </xsl:when>
   
       <!-- the string before the newline and the string before the
            space are the same; which means they must both be empty -->
       <xsl:otherwise>
         <xsl:value-of select="$text"/>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>

8 CRLF to BR

Jarno Elovirta

test.xml

<?xml version="1.0"?>
<Text>
    This is where the actual article starts. The article contains
    several paragraphs.

    Paragaraphs are separated by Carriage returns or Linefeeds, not by Tags
</Text>

test.xsl

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="xml" />

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="text()">
   <xsl:call-template name="break" />
</xsl:template>

<xsl:template name="break">
 <xsl:param name="text" select="."/>
 <xsl:choose>
   <xsl:when test="contains($text, '&#xA;')">
     <xsl:value-of select="substring-before($text, '&#xA;')"/>
     <br/>
     <xsl:call-template name="break">
       <xsl:with-param name="text" select="substring-after($text,'&#xA;')"/>
     </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
           <xsl:value-of select="$text"/>
   </xsl:otherwise>
 </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Output.

<?xml version="1.0" encoding="utf-8"?><Text><br/>    
This is where the actual article starts. The article contains<br/>    
several paragraphs.<br/>
<br/>    
Paragaraphs are separated by Carriage returns or Linefeeds, not by Tags<br/></Text>

9 Trim or pad a string to a fixed length

Wendell Piez

>Is there away to force string length? For example, my 
>output contains a string that must contain 10 characters but the 
>XML source is not guaranteed to supply that number of characters. 

In order to trim a long string to 10, or pad a short one with spaces:

substring(concat(string(.), '          '), 1, 10)

10 Computing string-length of nodesets

David Carlisle


<xsl:variable name="x"><xsl:copy-of select="[nodeset]"/></xsl:variable>
<xsl:value-of select="string-length(string($x))"/>
	

probably does what you want.

> So is there some way to construct a equivalent of sum(), but one that works
> on string values of a nodeset?

simple cases you can get by as above, but usually you have to use a node-set extension function for this sort of thing (until xslt 1.1)

for instance if you wanted to apply normalize-space to each of your nodes in the node set before computing your average, you'd do something like

<xsl:variable name="x">
  <xsl:for-each  select="[nodeset]" >
   <x><xsl:value-of select="string-length(normalize-space(.))"/></x>
  </xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(xt:node-set($x)/x)"/>

11 Substituting substrings in an element's text for HTML output

Steve Muench



| I have an element that looks like this:
| 
| <xyz>
|   this is the first line
|   this is the second line
|   this is the third line
| </xyz>
| 
| I'd like to transform it so that it could be outputted to html with
| the same line breaks, i.e. change all \n to <br />
	

Here are a couple of templates I use for this purpose. The "br-replace" replaces carriage returns with <br/> tags. The "sp-replace" replaces *pairs* of leading spaces with *pairs* of leading non-breaking spaces. By combining the two in series, you can achieve the affect of keeping code listings (e.g. XML or Java source code examples in a book) properly formatted without using the <pre> tag which tends to mess up the formatting of table cells (often pushing them wider than you'd like).

To use the templates, add them (or import them) into the stylesheet you're building, then at the right moment, just do:

    <!-- Call the "br-replace" template -->
    <xsl:call-template name="br-replace">

      <!-- 
       | Passing the result of calling the sp-replace template
       | as the value of the parameter named "text"
       +-->
      <xsl:with-param name="text">

        <!-- Call the "sp-replace" template -->
        <xsl:call-template name="sp-replace">

          <!-- Passing the value of the current node -->
          <xsl:with-param name="text" select="."/>
        </xsl:call-template>
      </xsl:with-param>
    </xsl:call-template>

Here are the templates...

  <!-- Replace new lines with html <br> tags -->
  <xsl:template name="br-replace">
    <xsl:param name="text"/>
    <xsl:variable name="cr" select="'&#xa;'"/>
    <xsl:choose>
      <!-- If the value of the $text parameter contains carriage ret -->
      <xsl:when test="contains($text,$cr)">
        <!-- Return the substring of $text before the carriage return -->
        <xsl:value-of select="substring-before($text,$cr)"/>
        <!-- And construct a <br/> element -->
        <br/>
        <!--
         | Then invoke this same br-replace template again, passing the
         | substring *after* the carriage return as the new "$text" to
         | consider for replacement
         +-->
        <xsl:call-template name="br-replace">
          <xsl:with-param name="text" select="substring-after($text,$cr)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
   </xsl:choose>
  </xsl:template>

  <!-- Replace two consecutive spaces w/ 2 non-breaking spaces -->
  <xsl:template name="sp-replace">
    <xsl:param name="text"/>
    <!-- NOTE: There are two spaces   ** here below -->
    <xsl:variable name="sp"><xsl:text>  </xsl:text></xsl:variable>
    <xsl:choose>
      <xsl:when test="contains($text,$sp)">
        <xsl:value-of select="substring-before($text,$sp)"/>
        <xsl:text>&#160;&#160;</xsl:text>
        <xsl:call-template name="sp-replace">
          <xsl:with-param name="text" select="substring-after($text,$sp)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

12 Substring-after, last occurrence

Paul Brown

> I read about the substring-after()-function, which can extract a substring
> after the FIRST occurance of a specified string.
>
> My problem: Have you got an idea, how I can get the substring-after of a
> string after the LAST occurance of a specified substring??
>
> For example: I want the substring after the last ".".
>
> String: "A.B.C.0.1.1.hgk"
>
> The solution should return: "hgk"
	

The answer is recursion:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template name="substring-after-last">
<xsl:param name="input" />
<xsl:param name="marker" />

<xsl:choose>
  <xsl:when test="contains($input,$marker)">
    <xsl:call-template name="substring-after-last">
      <xsl:with-param name="input" 
          select="substring-after($input,$marker)" />
      <xsl:with-param name="marker" select="$marker" />
    </xsl:call-template>
  </xsl:when>
  <xsl:otherwise>
   <xsl:value-of select="$input" />
  </xsl:otherwise>
 </xsl:choose>

</xsl:template>

<xsl:template match="FOO">
 <xsl:call-template 
  name="substring-after-last">
 <xsl:with-param name="input" select="'1.2.3.4.5.6.7'" />
 <xsl:with-param name="marker" select="'.'" />
 </xsl:call-template>
</xsl:template>

</xsl:stylesheet>

13 lastIndexOf('char')

Jeni Tennison


> i'm looking for a xslt method to identify the last iteration of a
> char into a string. For example, to extract automatically the name
> of the html page into the url.
>
> string : "h ttp://www.thesite.com/directory1/dir2/dir3../pageindex.htm"
>
> there are the functions substrings-before() et substring-after(),
> but they work on the first occurence of the marker-string. Is there
> a Xslt function which gives the last occurence of a marker-string
> (like lastIndexOf('/',"string")) into a string?
	

No, there isn't.

You can achieve what you want through recursion. Walk through the string, taking bits off the front of it until you get to a string which has no '/' in it whatsoever.

<!-- define a lastIndexOf named template -->
<xsl:template name="lastIndexOf">
   <!-- declare that it takes two parameters 
	  - the string and the char -->
   <xsl:param name="string" />
   <xsl:param name="char" />
   <xsl:choose>
      <!-- if the string contains the character... -->
      <xsl:when test="contains($string, $char)">
         <!-- call the template recursively... -->
         <xsl:call-template name="lastIndexOf">
            <!-- with the string being the string after the character
                 -->
            <xsl:with-param name="string"
                            select="substring-after($string, $char)" />
            <!-- and the character being the same as before -->
            <xsl:with-param name="char" select="$char" />
         </xsl:call-template>
      </xsl:when>
      <!-- otherwise, return the value of the string -->
      <xsl:otherwise><xsl:value-of select="$string" />
	  </xsl:otherwise>
   </xsl:choose>
</xsl:template>

To get the filename of a URL held in the URL child of the current node, you can call this template like:

  <xsl:call-template name="lastIndexOf">
     <xsl:with-param name="string" select="URL" />
     <xsl:with-param name="char" select="/" />
  </xsl:call-template>

It's pretty verbose, but I'm afraid that's the only way to do it in XSLT at the moment.

14 count the number of specific characters in a string.

Michael Kay

> If I can somehow count the number of periods "." in
> the string "id", then I can determine what level the
> <tab> element is at...anyone have any ideas how to
> count the number of occurences in a string with XSL?

Try:

string-length($x) - string-length(translate($x, '.', ''))

15 Reverse a string

Jeni Tennison

 <xsl:template name="reverse3">
    <xsl:param name="theString" />
    <xsl:param name="reversedString" />
    <xsl:choose>
       <xsl:when test="$theString">
          <xsl:call-template name="reverse3">
             <xsl:with-param name="theString"
                             select="substring($theString, 2)" />
             <xsl:with-param name="reversedString"
select="concat(substring($theString, 1, 1),
$reversedString)" />
          </xsl:call-template>
       </xsl:when>
       <xsl:otherwise>
          <xsl:value-of select="$reversedString" />
       </xsl:otherwise>
    </xsl:choose>
 </xsl:template>

Dimitre Novatchev offers

<xsl:template name="reverse">
 <xsl:param name="theString"/>
 <xsl:variable name="thisLength" select="string-length($theString)"/>
 <xsl:choose>
  <xsl:when test="$thisLength = 1">
   <xsl:value-of select="$theString"/>
  </xsl:when>
  <xsl:otherwise>
 <xsl:variable name="restReverse">
   <xsl:call-template name="reverse">
     <xsl:with-param name="theString"
select="substring($theString,  1, $thisLength -1)"/>
   </xsl:call-template>
 </xsl:variable>
 <xsl:value-of 
select="concat(substring($theString,$thisLength, 1) ,$restReverse)"/>
   </xsl:otherwise>
   </xsl:choose>
  </xsl:template>
  
  
  <xsl:template name="reverse2">
    <xsl:param name="theString"/>
    <xsl:variable name="thisLength" select="string-length($theString)"/>
   <xsl:choose>
   <xsl:when test="$thisLength = 1">
       <xsl:value-of select="$theString"/>
   </xsl:when>
   <xsl:otherwise>
       <xsl:variable name="length1" 
  select="floor($thisLength div 2)"/>
       <xsl:variable name="reverse1">
   <xsl:call-template name="reverse2">
       <xsl:with-param name="theString"
   select="substring($theString, 
  1, $length1)"/>
   </xsl:call-template>
       </xsl:variable>
       <xsl:variable name="reverse2">
   <xsl:call-template name="reverse2">
       <xsl:with-param name="theString"
   select="substring($theString, 
                                                    $length1+1, 
                                     $thisLength - $length1
                                                    )"/>
   </xsl:call-template>
       </xsl:variable>
       <xsl:value-of select="concat($reverse2, 
  $reverse1)"/>
  
   </xsl:otherwise>
   </xsl:choose>
  </xsl:template>
  

I compared times from the three templates on a 800MHz 128Mb RAM Pentium, running each test 10 times, averaging the times reported by MSXML run from the command line, and rounding to the nearest millisecond. Here are the results:

Length        Simple        Least Recursive         Tail Recursive
  ------------------------------------------------------------------
   100              22                    36                       5
   200              41                    61                      11
   400              95                   124                      24
   800             241                   249                      77
  1600             650                   485                     220
  3200            3465                   975                    1369

The tail recursive template is always substantially faster than the simple algorithm, but it suffers from the same problem in the end - the time taken increases exponentially rather than linearly based on the length of the string, so for really long strings the least recursive algorithm works best. I haven't taken detailed timings, but there's a similar pattern in Saxon (although Saxon bugs out with the simple algorithm and long strings, I guess a stack overflow). A processor that doesn't optimise tail recursion would probably have similar performance from both the simple and tail-recursive templates.