[xsl] Re: OFFLIST: Re: XSLT to find missing characters?

Subject: [xsl] Re: OFFLIST: Re: XSLT to find missing characters?
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Sun, 14 Jul 2002 01:23:00 -0700 (PDT)
--- "Kirk Allen Evans" <kaevans at xmlandasp dot net> wrote:

> Thank you all for your suggestions.
> 
> As usual, the problem was only half-stated by the client.  So, here
> is
> a
> curveball.  They want this to act kind of like a dynamic list box
> where
> they
> can enter parts of a phrase.  Kind of like the index search in MSDN
> where
> you type part of a phrase and it finds the next entry for you.  A
> more
> precise requirement is that they want some kind of less-than or
> greater-than
> operator for strings.
> 
> Given the corrected input XML (I had "Bud Lite" and "Budweiser"
> ordered
> incorrectly):
> 
> <beers>
> 	<beer name="Amstel Light"/>
>  	<beer name="Bud Lite"/>
>  	<beer name="Budweiser"/>
>  	<beer name="Buffalo Beer"/>
>  	<beer name="Buzz Beer"/>
>  	<beer name="Cool Creek"/>
>  	<beer name="Cooper&apos;s Reserve"/>
>  	<beer name="Coors"/>
>  	<beer name="Michelob"/>
>  	<beer name="Miller Lite"/>
>  	<beer name="Sam Adams"/>
> </beers>
> 
> The node list will be pre-sorted based on the name attribute.  They
> want to
> enter "Bud" and get "Bud Lite", or "Budw" to get "Budweiser".  If no
> nodes
> are found (like "Budx"), then the next node alphabetically is
> returned
> ("Buffalo Beer").  There is no limitation on the last match position
> in
> the
> string.  For instance, when working with "Budx", the third character
> is
> significant.  When working with "Cooq", the fourth character is
> significant
> (and should return "Coors").  "Apqrstuvwxyz" makes the second
> character
> significant and would return "Bud Lite".
> 
> Here is my first stab, modifying Dimitre's XPath solution with an
> additional
> union:
> 
> (
> 	/*/*[starts-with(@name,$vStartPhrase)][1]
> 	|
> 	/*/*[contains(substring-after($vAlphabet, $vStartLetter),
> 			  substring(@name,1,1))][1]
> )[1]
> 
> | /*/*[1][contains(substring-before($vAlphabet, $vStartLetter ),
> 			 substring(/*/*[last()]/@name,1,1))]
> 
> 
> My change works, but has a flaw in that "Buffalo Beer" is not
> returned:
> "Cool Creek" is.  I asked if it would be OK to get the last node that
> matches the first 2 letters (ie, "Buzz Beer"), they said no:  the
> business
> case says that it should return "Buffalo Beer".
> 
> I think I can conceive a solution using XSLT that uses the same
> recursive
> approach as the original post.  But they want to implement this using
> a
> single XPath statement in order to shove the solution into their
> existing
> framework using MSXML and selectSingleNode.
> 
> Kirk

Hi Kirk,

The first thing I'd do in your case would be to ask the customers to
stick with specifying user requirements only and not to advise the
implementors how to implement.

I would not tell them that what they want is possible, given that
certain additional assumptions are true, because the resulting
"solution" is really ugly.

In case we know the maximum length of the user-entered phrase and of
the possible choices (lookup keywords), then we could normalise the
user-enterd phrase and the lookup keywords to be of the same length by
padding them with spaces.

Then an XPath expression exists, that returns the first lookup keyword,
which fully contains the user-entered phrase, or otherwise, the first
lookup keyword, which is greater than the user-entered phrase.

The solution matches characters to numbers, then constructs the
difference between each pair of characters (kwd(n) - uphrase(n)),
then constructs from these differences a number in a number system with
base the number of different possible values for the characters used.

The above is done between every lookup keyword and the user-enterd
phrase, and the first lookup keyword, for which the result is positive,
is returned.

To illustrate with a simple example:

source xml:

<lookup>
	<kwd>abab </kwd>
	<kwd>acad </kwd>
	<kwd>baba </kwd>
	<kwd>baca </kwd>
	<kwd>caca </kwd>
</lookup>


Transformation:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
   
  <xsl:variable name="vPhrase1" select="'aca '"/>
  <xsl:variable name="vPhrase2" select="'babd '"/>

  <xsl:template match="lookup">
    <xsl:value-of select="concat('For ', $vPhrase1, ' found ')"/>
    
    <xsl:call-template name="lookup">
      <xsl:with-param name="vPhrase" select="$vPhrase1"/>
    </xsl:call-template>

    <xsl:text>&#xA;</xsl:text>

    <xsl:value-of select="concat('For ', $vPhrase2, ' found ')"/>
    <xsl:call-template name="lookup">
      <xsl:with-param name="vPhrase" select="$vPhrase2"/>
    </xsl:call-template>
  </xsl:template>
  
  <xsl:variable name="vChars" select="' abcd'"/>
  
  <xsl:template name="lookup">
    <xsl:param name="vPhrase"/>
    
    <xsl:variable name="vCase1" 
      select="/*/kwd
              [starts-with(., 
                           substring-before($vPhrase, ' '))][1]"/>
    
    <xsl:choose>
      <xsl:when test="$vCase1">
        <xsl:value-of select="$vCase1"/>
      </xsl:when>

      <xsl:otherwise>
        <xsl:variable name="vCase2" 
         select="/*/kwd
                  [
                   (
                    string-length(substring-before($vChars, 
                                                   substring(.,1,1)
                                                   )
                                 )
                 - string-length(substring-before($vChars, 
                                                  substring($vPhrase, 
                                                            1,1
                                                            )
                                                  )
                                 )
                    ) * 125
                 +
                   (
                    string-length(substring-before($vChars, 
                                                   substring(.,2,1)
                                                   )
                                  )
                 - string-length(substring-before($vChars,
                                                  substring($vPhrase,
                                                            2,1
                                                            )
                                                  )
                                 )
                    ) * 25
                 +
                   (
                    string-length(substring-before($vChars, 
                                                   substring(.,3,1)
                                                   )
                                  )
                 - string-length(substring-before($vChars, 
                                                  substring($vPhrase, 
                                                            3,1
                                                            )
                                                  )
                                 )
                    ) * 5
                 +
                   (
                    string-length(substring-before($vChars, 
                                                   substring(.,4,1)
                                                   )
                                  )
                 - string-length(substring-before($vChars, 
                                                  substring($vPhrase, 
                                                            4,1
                                                            )
                                                  )
                                )
                    ) 
                 
                 > 0   
                 ][1]"/>
                 
                 <xsl:value-of select="$vCase2"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>


When applied on the source xml above this transformation produces the
following result:

For aca  found acad 
For babd  found baca 

The result can be returned as a single XPath expression: 

$vCase1 | $vCase2


and this is really what your customers want.






=====
Cheers,

Dimitre Novatchev.
http://fxsl.sourceforge.net/ -- the home of FXSL

__________________________________________________
Do You Yahoo!?
Yahoo! Autos - Get free new car price quotes
http://autos.yahoo.com

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


Current Thread