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

  <!--
  whitespace trimming demonstration
  by Mike J. Brown

  last updated 2004-03-03

  no license; use freely

  ltrim = remove leading whitespace
  rtrim = remove trailing whitespace
  trim = remove both leading and trailing whitespace

  ...for when XPath's normalize-space() is too much!

  Also see the similar functions described in http://fxsl.sourceforge.net/

  -->

  <xsl:output method="text" indent="yes" encoding="us-ascii"/>

  <xsl:param name="s" select="'   a test     '"/>

  <xsl:template match="/">

    <xsl:value-of select="concat('original string: [',$s,']&#10;')"/>

    <!--
      ltrim can be done entirely with XPath 1.0 functions:
      concat(substring(translate($s,' &#9;&#10;&#13;',''),1,1),substring-after($s,substring(translate($s,' &#9;&#10;&#13;',''),1,1)))
    -->

    <xsl:value-of select="concat('ltrimmed: [',substring(translate($s,' &#9;&#10;&#13;',''),1,1),substring-after($s,substring(translate($s,' &#9;&#10;&#13;',''),1,1)),']&#10;')"/>

    <!--

      this version make it more clear what is going on:

      <xsl:value-of select="'ltrimmed: ['"/>
      <xsl:variable name="s-no-ws" select="translate($s,' &#9;&#10;&#13;','')"/>
      <xsl:variable name="s-first-non-ws" select="substring($s-no-ws,1,1)"/>
      <xsl:variable name="s-no-leading-ws" select="concat($s-first-non-ws,substring-after($s,$s-first-non-ws))"/>
      <xsl:value-of select="$s-no-leading-ws"/>
      <xsl:value-of select="']&#10;'"/>
    -->

    <!--
      rtrim requires recursion
    -->
    <xsl:value-of select="'rtrimmed: ['"/>
    <xsl:call-template name="rtrim">
      <xsl:with-param name="s" select="$s"/>
    </xsl:call-template>
    <xsl:value-of select="']&#10;'"/>

    <!--
      for trim, just rtrim the ltrimmed string
    -->
    <xsl:value-of select="'trimmed: ['"/>
    <xsl:call-template name="rtrim">
      <xsl:with-param name="s" select="concat(substring(translate($s,' &#9;&#10;&#13;',''),1,1),substring-after($s,substring(translate($s,' &#9;&#10;&#13;',''),1,1)))"/>
    </xsl:call-template>
    <xsl:value-of select="']&#10;'"/>

  </xsl:template>

  <!--
    the placement of the recursive call allows the processor to
    optimize tail recursion. Not all processors optimize, though.
  -->
  <xsl:template name="rtrim">
    <xsl:param name="s"/>
    <xsl:param name="i" select="string-length($s)"/>
    <xsl:choose>
      <xsl:when test="translate(substring($s,$i,1),' &#9;&#10;&#13;','')">
        <xsl:value-of select="substring($s,1,$i)"/>
      </xsl:when>
      <xsl:when test="$i&lt;2"/>
      <xsl:otherwise>
        <xsl:call-template name="rtrim">
          <xsl:with-param name="s" select="$s"/>
          <xsl:with-param name="i" select="$i - 1"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>
