Re: [xsl] comparing strings

Subject: Re: [xsl] comparing strings
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 2 Jan 2001 11:13:13 +0000
Hi Oliver,

> Is there someone out there who can answer my simple question:
> How to compare strings with XPath/XSLT, something like strcmp() in C
> or compareTo() in Java?
> I want a pure XSLT solution without using extensions.

The short answer is that there's no XPath function or operator for
comparing strings. Depending on the context of the problem, you might
be able to use xsl:sort to order strings alphabetically.  If you
can't, then you have to use a named template to do the comparison for
you.

Looking at your particular example, I think that the easiest way to
approach this is to apply templates to the term that either is the
input term or the term immediately following the input term in
alphabetical order, with a template that operates on the term by
outputting it and the next 9 terms, i.e.:

<xsl:template match="term">
  <xsl:for-each select=". |
                        following-sibling::term[position() &lt; 10]">
     <!-- display in some appropriate way -->
  </xsl:for-each>
</xsl:template>

So the problem (which is what you're getting at anyway) is in
identifying the term element that has a value that is the same as or
follows alphabetically the input term.

If you were allowing XSLT 1.1, then you could use xsl:sort to list the
terms, including the input term, and select the one you're after:

<xsl:template match="longman">
   <!-- create a list of terms that includes the input term -->
   <xsl:variable name="terms">
     <input><xsl:value-of select="$input" /></input>
     <xsl:copy-of select="term" />
   </xsl:variable>
   <xsl:for-each select="$terms">
     <!-- sort the terms (including the input term) alphabetically -->
     <xsl:sort select="." />
     <!-- select the term that immediately following the input term
          within the list -->
     <xsl:apply-templates select="input/following-sibling::term" />
   </xsl:for-each>
</xsl:template>

As you're not, you have to use a named template to carry out the
comparison. Here is a rough-and-ready one that takes two parameters
($first and $second) and returns 'true' if $first comes before $second
and 'false' if not (if they're the same, it returns 'true'):

<xsl:variable name="charorder" select="'abcdefghijklmnopqrstuvwxyz'" />

<xsl:template name="strcmp">
   <xsl:param name="first" />
   <xsl:param name="second" />
   <xsl:variable name="nFirstFirst"
                 select="string-length(
                           substring-before($charorder,
                                            substring($first, 1, 1)))" />
   <xsl:variable name="nFirstSecond"
                 select="string-length(
                           substring-before($charorder,
                                            substring($second, 1, 1)))" />
   <xsl:variable name="restFirst" select="substring($first, 2)" />
   <xsl:variable name="restSecond" select="substring($second, 2)" />
   <xsl:choose>
      <xsl:when test="$nFirstFirst &lt; $nFirstSecond">true</xsl:when>
      <xsl:when test="$nFirstFirst &gt; $nFirstSecond">false</xsl:when>
      <xsl:when test="not($restFirst)">true</xsl:when>
      <xsl:when test="not($restSecond)">false</xsl:when>
      <xsl:otherwise>
         <xsl:call-template name="strcmp">
            <xsl:with-param name="first" select="$restFirst" />
            <xsl:with-param name="second" select="$restSecond" />
         </xsl:call-template>
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>

The $charorder variable is particularly important as it forms the
basis for comparison of the two strings.  The above solution lacks
support for case-insensitive comparison, and won't deal with hyphens
and so on, but it's a start.  Supplemented with the following
templates:

<xsl:template match="longman">
   <terms>
     <!-- apply templates to only the first term in compare mode -->
     <xsl:apply-templates select="term[1]" mode="compare" />
   </terms>
</xsl:template>

<xsl:template match="term" mode="compare">
   <!-- $comparison holds true if the input term is alphabetically
        before the current term -->
   <xsl:variable name="comparison">
      <xsl:call-template name="strcmp">
         <xsl:with-param name="first" select="$inputTerm" />
         <xsl:with-param name="second" select="." />
      </xsl:call-template>
   </xsl:variable>
   <xsl:choose>
      <xsl:when test="$comparison = 'false'">
         <!-- apply templates to the next term in the list in
              compare mode -->
         <xsl:apply-templates select="following-sibling::*[1]"
                              mode="compare" />
      </xsl:when>
      <xsl:otherwise>
         <!-- apply templates to this term, in the default mode, to
              get output -->
         <xsl:apply-templates select="." />
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>

you have your solution.

I hope that helps,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread