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

  <!-- This stylesheet contains common library function implemented
  as XSLT named template.

  Function implemented : 

    - trim
    - trim-start
    - trim-end
    - is-char-in-class
    - join
  -->

  <xsl:variable name="chars.to.trim.default" select="'&#9;&#10;&#13; '"/>

  <xsl:variable name="trim.debug"		select="false()"/>
  <xsl:variable name="trim-start.debug"		select="false()"/>
  <xsl:variable name="trim-end.debug"		select="false()"/>
  <xsl:variable name="is-char-in-class.debug"	select="false()"/>

  <!-- Since return value from template is a string. False is
  the empty string and true is the string 'true' -->
  <xsl:template name="is-char-in-class">
    <xsl:param name="c" select="''"/>
    <xsl:param name="char.class" select="''"/>

    <xsl:if test="$is-char-in-class.debug">
      <xsl:message>is-char-in-class:</xsl:message>
      <xsl:message>  $c='<xsl:value-of select="$c"/>'</xsl:message>
      <xsl:message>  $char.class='<xsl:value-of select="$char.class"/>'</xsl:message>
    </xsl:if>

    <xsl:choose>
      <!-- Make sure that our parameters aren't empty string -->
      <xsl:when test="not(string-length($c)) or 
		      not(string-length($char.class))">
	<xsl:value-of select="''"/>
      </xsl:when>

      <xsl:otherwise>
	<xsl:choose>
	  <xsl:when test="starts-with( $char.class, $c )">
	    <xsl:if test="$is-char-in-class.debug">
	      <xsl:message>  => true()</xsl:message>
	    </xsl:if>
	    <xsl:value-of select="true()"/>
	  </xsl:when>

	  <!-- This was the last character -->
	  <xsl:when test="string-length( $char.class ) = 1">
	    <xsl:if test="$is-char-in-class.debug">
	      <xsl:message>  => false()</xsl:message>
	    </xsl:if>
	    <xsl:value-of select="''"/>
	  </xsl:when>

	  <!-- Recurse -->
	  <xsl:otherwise>
	    <xsl:call-template name="is-char-in-class">
	      <xsl:with-param  name="c" select="$c"/>
	      <xsl:with-param  name="char.class" select="substring( $char.class, 2 )"/>
	    </xsl:call-template>
	  </xsl:otherwise>
	</xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="trim-start">
    <xsl:param name="string"/>
    <xsl:param name="chars.to.trim" select="$chars.to.trim.default"/>

    <xsl:if test="$trim-start.debug">
      <xsl:message>trim-start:</xsl:message>
      <xsl:message>  $string='<xsl:value-of select="$string"/>'</xsl:message>
      <xsl:message>  $char.to.trim='<xsl:value-of select="$chars.to.trim"/>'</xsl:message>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="not(string-length($string)) or 
		      not(string-length($chars.to.trim))">
	<xsl:value-of select="$string"/>
      </xsl:when>

      <xsl:otherwise>
	<xsl:variable name="should.trim">
	  <xsl:call-template name="is-char-in-class">
	    <xsl:with-param name="c" select="substring( $string, 1, 1 )"/>
	    <xsl:with-param name="char.class" select="$chars.to.trim"/>
	  </xsl:call-template>
	</xsl:variable>

	<xsl:if test="$trim-start.debug">
	  <xsl:message>  $should.trim=<xsl:value-of
	      select="boolean($should.trim)"/> ('<xsl:value-of
	      select="$should.trim"/>')</xsl:message>
	</xsl:if>

	<xsl:choose>
	  <!-- NOTE: We are using not($should.trim) instead of
	   the more obvious $should.trim, so that the recursion
	   goes into the otherwise element. This way a good XSLT
	   processor could implement tail-recursion optimization. -->

	  <!-- WORKAROUND: There seems to be a bug in xsltproc which
	  where boolean( '' ) gives true -->

	  <!-- Trimming is finished -->
	  <xsl:when test="not(string-length($should.trim))">
	    <xsl:if test="$trim-start.debug">
	      <xsl:message>  => '<xsl:value-of select="$string"/>'</xsl:message>
	    </xsl:if>
	    
	    <xsl:value-of select="$string"/>
	  </xsl:when>
	  
	  <xsl:otherwise>
	    <xsl:choose>
	      <!-- Is this the last character ? -->
	      <xsl:when test="string-length($string) = 1">
		<!-- Trimming return the empty string -->
		<xsl:if test="$trim-start.debug">
		  <xsl:message>  => ''</xsl:message>
		</xsl:if>
		<xsl:value-of select="''"/>
	      </xsl:when>
		
	      <!-- Recurse while we can trim -->
	      <xsl:otherwise>
		<xsl:call-template name="trim-start">
		  <xsl:with-param name="string" 
		    select="substring($string, 2)"/>
		  <xsl:with-param name="chars.to.trim" 
		    select="$chars.to.trim"/>
		</xsl:call-template>
	      </xsl:otherwise>
	    </xsl:choose>
	  </xsl:otherwise>
	</xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="trim-end">
    <xsl:param name="string"/>
    <xsl:param name="chars.to.trim" select="$chars.to.trim.default"/>
    
    <xsl:if test="$trim-end.debug">
      <xsl:message>trim-end:</xsl:message>
      <xsl:message>  $string='<xsl:value-of select="$string"/>'</xsl:message>
      <xsl:message>  $char.to.trim='<xsl:value-of select="$chars.to.trim"/>'</xsl:message>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="not(string-length($string)) or 
		      not(string-length($chars.to.trim))">
	<xsl:value-of select="$string"/>
      </xsl:when>

      <xsl:otherwise>
	<xsl:variable name="should.trim">
	  <xsl:call-template name="is-char-in-class">
	    <xsl:with-param name="c" select="substring( $string, string-length($string), 1 )"/>
	    <xsl:with-param name="char.class" select="$chars.to.trim"/>
	  </xsl:call-template>
	</xsl:variable>

	<xsl:if test="$trim-end.debug">
	  <xsl:message>  $should.trim=<xsl:value-of
	      select="boolean($should.trim)"/> ('<xsl:value-of
	      select="$should.trim"/>')</xsl:message>
	</xsl:if>

	<xsl:choose>
	  <!-- NOTE: We are using not($should.trim) instead of
	   the more obvious $should.trim, so that the recursion
	   goes into the otherwise element. This way a good XSLT
	   processor could implement tail-recursion optimization. -->
	  
	  <!-- WORKAROUND: There seems to be a bug in xsltproc which
	  where boolean( '' ) gives true -->

	  <!-- Trimming is finished -->
	  <xsl:when test="not(string-length($should.trim))">
	    <xsl:if test="$trim-end.debug">
	      <xsl:message>  => '<xsl:value-of select="$string"/>'</xsl:message>
	    </xsl:if>
	    <xsl:value-of select="$string"/>
	  </xsl:when>
	  
	  <xsl:otherwise>
	    <xsl:choose>
	      <!-- Is this the last character ? -->
	      <xsl:when test="string-length($string) = 1">
		<!-- Trimming return the empty string -->
		<xsl:if test="$trim-end.debug">
		  <xsl:message>  => ''</xsl:message>
		</xsl:if>
		<xsl:value-of select="''"/>
	      </xsl:when>
		
	      <!-- Recurse while we can trim -->
	      <xsl:otherwise>
		<xsl:call-template name="trim-end">
		  <xsl:with-param name="string" 
		    select="substring($string, 1, (string-length($string)-1))"/>
		  <xsl:with-param name="chars.to.trim" select="$chars.to.trim"/>
		</xsl:call-template>
	      </xsl:otherwise>
	    </xsl:choose>
	  </xsl:otherwise>
	</xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="trim">
    <xsl:param name="string"/>
    <xsl:param name="chars.to.trim"	  select="$chars.to.trim.default"/>
    <xsl:param name="chars.to.trim.start" select="$chars.to.trim"/>
    <xsl:param name="chars.to.trim.end"	  select="$chars.to.trim"/>

    <xsl:if test="$trim.debug">
      <xsl:message>trim:</xsl:message>
      <xsl:message>  $string='<xsl:value-of select="$string"/>'</xsl:message>
      <xsl:message>  $char.to.trim='<xsl:value-of select="$chars.to.trim"/>'</xsl:message>
      <xsl:message>  $char.to.trim.start='<xsl:value-of select="$chars.to.trim.start"/>'</xsl:message>
      <xsl:message>  $char.to.trim.end='<xsl:value-of select="$chars.to.trim.end"/>'</xsl:message>
    </xsl:if>

    <xsl:call-template name="trim-start">
      <xsl:with-param name="string">
	<xsl:call-template name="trim-end">
	  <xsl:with-param name="string"	    select="$string"/>
	  <xsl:with-param name="chars.to.trim" select="$chars.to.trim.end"/>
	</xsl:call-template>
      </xsl:with-param>
      <xsl:with-param name="chars.to.trim" select="$chars.to.trim.start"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="join">
    <xsl:param name="sep" select="' '"/>
    <xsl:param name="elements" select="*"/>

    <!-- Used in recursion -->
    <xsl:param name="str" select="''"/>

    <xsl:choose>
      <xsl:when test="count($elements) = 0">
	<!-- Empty set -->
	<xsl:value-of select="''"/>
      </xsl:when>

      <xsl:when test="count($elements) &gt; 1">
	<!-- Recurse -->
	<xsl:call-template name="join">
	  <xsl:with-param name="sep" select="$sep"/>
	  <xsl:with-param name="elements" select="$elements[position()!=1]"/>
	  <xsl:with-param name="str" 
	    select="concat($str, $sep, $elements[position()=1])"/>
	</xsl:call-template>
      </xsl:when>

      <xsl:when test="string-length($str)">
	<!-- Last element --> 
	<xsl:value-of select="concat($str, $sep, $elements)"/>
      </xsl:when>

      <xsl:otherwise>
	<!-- Only one element -->
	<xsl:value-of select="$elements"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

<!-- 
Keep this comment at the end of the file
Local variables:
mode: xml
sgml-indent-step: 2
End:
-->
