/* * $Id$ */ * Program...: SpellLib.prg * Author....: Joseph D. Booth * Copyright.: (C)1993, Joseph D. Booth, All Rights Reserved * * Purpose...: Library of functions to allow a Clipper program to * spell check a string and to also work with individual * words. * * SP_Add() - Add word to the dictionary * SP_Cache() - Add word to spelling cache * SP_Check() - Is word spelled correctly? * SP_Clear() - Clear the spelling cache * SP_Init() - Initialize the spelling dictionary * SP_GetSet() - Get/Set global parameters * SP_LoadAux() - Loads an auxiliary dictionary * SP_Suggest() - Offer list of sound alike suggestions * SP_Quick() - Offer list of suggested spellings * SP_WildCard() - List of wildcard matches * * DBF2Dic() - Convert a DBF file to a DIC file * Dic2DBF() - Convert a DIC file to a DBF file * **************************************************************************** #xtranslate StoreWord() => Xform() #xtranslate ExtractWord() => Xunform() #define NSIZE 26*26*6 #define EACH_WORD 6 #define CRLF chr(13)+chr(10) #define FOUR_BYTES chr(0)+chr(0)+chr(0)+chr(0) #define MAX_STRING 40000 #define COMMON_WORDS aGlobal[ 1] #define CACHE_WORDS aGlobal[ 2] #define DICTIONARY_PATH aGlobal[ 3] #define DICTIONARY_NAME aGlobal[ 4] #define AUXILIARY_DICTIONARY aGlobal[ 5] #define EXTRA_CODE_BLOCK aGlobal[ 6] #define ADD_SUFFIXES aGlobal[ 7] #define ADD_PREFIXES aGlobal[ 8] #define ADD_PLURALS aGlobal[ 9] #define SORT_SUGGESTIONS aGlobal[10] #define SUGGEST_PREFERENCE aGlobal[11] #define MINIMUM_WORD_LENGTH aGlobal[12] #define METAPHONE_SIZE aGlobal[13] #define MAX_DIFFERENCE aGlobal[14] #define THESAURUS_NAME aGlobal[15] #define CHECK_RUNONS aGlobal[16] * #define REDUCE_MEMORY_NEEDS THREAD STATIC aGlobal := { NIL,; "",; "",; "DICT.DIC",; "",; NIL,; .T.,; .T.,; .T.,; .T.,; "B",; // (M)etaphone, (A)lgorithmic, (B)oth 2,; 5,; 1,; "THES.DIC",; .T. } STATIC aContracts := { { "CAN'T" ,"CANNOT" },; { "WON'T" ,"WILL NOT" },; { "AREN'T" ,"ARE NOT" },; { "AIN'T" ,"ARE NOT" },; { "THEY'RE" ,"THEY ARE" },; { "THEY'VE" ,"THEY HAVE" },; { "IT'S" ,"IT IS" },; { "I'LL" ,"I WILL" },; { "I'D" ,"I WOULD" },; { "DON'T" ,"DO NOT" } } THREAD STATIC nHandle := 0 THREAD STATIC cOffsets // Function: SP_Add() // Purpose: Adds a word to the dictionary // Syntax: := SP_Add(cWord) // Arguments: cWord - Word to add to the auxiliary dictionary // Returns: - TRUE if added, // FALSE otherwise // // Notes: Does not check to see if the word already exists in the // dictionary. // //////////////////////////// function SP_Add(cWord) LOCAL was_added := .F. // Was word written to dictionary? LOCAL nAuxHandle // Dictionary file handle LOCAL nWritten // Number of bytes written // Initialize the spell checker and make sure // an auxiliary dictionary name was specified **DEBUG** @24,30 SAY "At SP_ADD" if SP_Init() .and. !empty( AUXILIARY_DICTIONARY ) cWord := upper(alltrim(cWord)) // // If the auxiliary dictionary does not exist, // we will create it for the user // if file( AUXILIARY_DICTIONARY ) nAuxHandle := fopen( AUXILIARY_DICTIONARY,2+32 ) else nAuxHandle := fcreate( AUXILIARY_DICTIONARY ) endif if nAuxHandle >= 0 Fseek(nAuxHandle,0,2) // Bottom of the file nWritten := Fwrite(nAuxHandle,; // Write word into file cWord+CRLF,len(cWord)+2) Fclose(nAuxHandle) // Close the file SP_Cache(cWord) // Add word to cache was_added := (nWritten = len(cWord)+2) endif endif return was_added ******************************************************************************* // // Function: Sp_cache() // Purpose: To add a word to the cache list // Syntax: := Sp_cache(cWord) // Arguments: cWord - upper case, all trimmed word to add // Returns: - TRUE if added, // FALSE otherwise // // Static: CACHE_WORDS - String of cache words // // Notes: Check to see if the word already exists in the cache // //////////////////////// function Sp_cache(cWord) LOCAL cTemp := "|"+upper(alltrim(cWord))+"|" LOCAL lAdded := .F. **DEBUG** @24,30 SAY "At SP_cache" if ! cTemp $ CACHE_WORDS .and. len(CACHE_WORDS) < MAX_STRING CACHE_WORDS += cTemp lAdded := .T. endif return lAdded ******************************************************************************* // // Function: Sp_check() // Purpose: To check the spelling of a word // Syntax: := Sp_check(cWord) // Arguments: cWord - upper case, all trimmed word to check // Returns: - TRUE if valid spelling // FALSE otherwise // // Static: CACHE_WORDS - String of cache words // COMMON_WORDS - String of common words // cOffSets - // nHandle - Handle DIC file is opened on // // Notes: // // //////////////////////// function SP_Check(cWord) STATIC cBuf := "" STATIC cLast STATIC nDicCount := 0 STATIC nCacheCount := 0 STATIC nBuffCount := 0 LOCAL ok := .T. LOCAL cLookup := upper(trim(cWord)) LOCAL nRow LOCAL nCol LOCAL x LOCAL y LOCAL z := 4 LOCAL cTemp **DEBUG** @24,30 SAY "At SP_init" if sp_init() if len(cLookup) == 1 .and. cLookup$"IA" return .T. endif if len(cLookup) < MINIMUM_WORD_LENGTH return .T. endif if right(cLookup,2)="'S" cLookUp := substr(cLookup,1, len(cLookup)-2 ) endif cTemp := "|"+cLookup+"|" if fat(cTemp, COMMON_WORDS) = 0 // Check the common words first if fat(cTemp,CACHE_WORDS) = 0 // then check the cache words ok := .F. nRow := asc(substr(cLookup,1,1))-64 nCol := asc(substr(cLookup,2,1))-64 if (nRow>0 .and. nRow<=26) .and. (nCol>0 .and. nCol<=26) #ifdef REDUCE_MEMORY_NEEDS cOffsets := space(6) fseek(nHandle,((nRow-1)*156)+((nCol-1)*6+1)+5,0) fread(nHandle,@cOffsets,6) x := bin2l(substr(cOffsets,1,4)) y := bin2w(substr(cOffsets,5,2)) #else x := bin2l(substr(cOffsets,((nRow-1)*156)+((nCol-1)*EACH_WORD+1),4)) y := bin2w(substr(cOffsets,((nRow-1)*156)+((nCol-1)*EACH_WORD+5),2)) #endif if !empty(x) if !(cLast==substr(cLookup,1,2)) cBuf := space(y) fseek(nHandle,x,0) fread(nHandle,@cBuf,y) nDicCount++ else nBuffCount++ endif if len(cLookup)=3 z := asc(substr(cLookup,3,1))-64 ok := bit(cBuf,z) elseif len(cLookup) < 3 ok := bit(cBuf,27) elseif y > 4 cTemp := StoreWord(cLookup) do while z < y z := fat( cTemp, cBuf,z ) if z < 6 exit elseif substr(cBuf,z-1,1) < "€" z++ else exit endif enddo ok := z > 4 .and. z NIL ok := eval( EXTRA_CODE_BLOCK,cWord ) endif return ok ******************************************************************************* // // Function: Sp_GetBuf() // Purpose: To get all words within the buffer // Syntax: Sp_GetBuf( ) // Arguments: - First two letters // Returns: cString - Buffer string from DIC file // //////////////////////// STATIC function sp_GetBuf(cLetters) LOCAL x LOCAL y LOCAL cBuf := "" LOCAL nRow := asc(substr(cLetters,1,1))-64 LOCAL nCol := asc(substr(cLetters,2,1))-64 **DEBUG** @24,30 SAY "At SP_getbuf" if (nRow>0 .and. nRow<=26) .and. (nCol>0 .and. nCol<=26) x := bin2l(substr(cOffsets,((nRow-1)*156)+((nCol-1)*EACH_WORD+1),4)) if !empty(x) y := bin2w(substr(cOffsets,((nRow-1)*156)+((nCol-1)*EACH_WORD+5),2)) cBuf := space(y) fseek(nHandle,x+4,0) fread(nHandle,@cBuf,y-4) endif endif return cBuf ****************************************************************************** // // Function: Sp_clear() // Purpose: To clear out the cache list // Syntax: Sp_clear() // Arguments: NONE // Returns: NIL // // Static: CACHE_WORDS - String of cache words // //////////////////////// function sp_clear **DEBUG** @24,30 SAY "At SP_clear" CACHE_WORDS := "" return NIL ****************************************************************************** // Function: Sp_GetSet() // Purpose: To get/set a parameter for the spell check function // Syntax: := Sp_GetSet( nWhich [,xNewSetting] ) // Parameters: nWhich - Which parameter to get/set // xNewSetting - Value to set parameter to // // Returns: TRUE if word succesfully added // FALSE if an error occurs, usually network lock // problem // ////////////////////////////////////// function Sp_GetSet(nWhich,xNewSetting) LOCAL xOld := NIL **DEBUG** @24,30 SAY "At SP_getset" if nWhich <> NIL .and. (nWhich>0 .and. nWhich<=len(aGlobal)) xOld := aGlobal[nWhich] if valtype(xNewSetting) == valtype(xOld) aGlobal[nWhich] := xNewSetting endif endif return xOld ****************************************************************************** // Function: Sp_LoadAux() // Purpose: To load an auxiliary dictionary of words // Syntax: // Parameters: // // // Returns: // // // ////////////////////////////////////// function Sp_LoadAux(cFile) LOCAL is_ok := .F. LOCAL x := 0 LOCAL cStr := "" LOCAL nSize **DEBUG** @24,30 SAY "At SP_loadaux" if !empty(cFile) if file(cFile) x := fopen(cFile,64) else x := fcreate(cFile) endif SP_GetSet(5,cFile) if x > 0 nSize := fseek(x,0,2) if nSize < MAX_STRING cStr := space(nSize) fseek(x,0,0) fread(x,@cStr,nSize) cStr := "|"+strtran(cStr,CRLF,"|") CACHE_WORDS += upper(cStr) is_ok := .T. endif fclose(x) endif endif return is_ok ****************************************************************************** // // Function: Sp_Suggest() // Purpose: To return an array of possible spellings // Syntax: aSuggest_ := Sp_Suggest(cWord [,lInclude] ) // Arguments: cWord - Word to look for suggestions for // lInclude - Should word be included in list? // Returns: aSuggest_ - List of suggested words // /////////////////////////////////// function Sp_Suggest(cWord,lInclude) STATIC aParts_ := { {"A" ,{"AI","AO","AU","AY","EA","EI","EIGH","ET","EY","E","I","O"}},; {"AIR" ,{"ARE"}},; {"AIT" ,{"ATE"}},; {"C" ,{"CK"}},; {"CH" ,{"TCH","TI","TU"}},; {"CKS" ,{"X"}},; {"D" ,{"ED"}},; {"E" ,{"A","AE","AI","AY","EA","EI","EO","IE","I","U","O"}},; {"EM" ,{"TEM"}},; {"ER" ,{"OUR","RE","URE","YR"}},; {"ERE" ,{"EIR","EAR","IER"}},; {"F" ,{"GH","LF","PH","G"}},; {"FIZ" ,{"PHYS"}},; {"G" ,{"GH","GU","GUE"}},; {"GZ" ,{"X"}},; {"H" ,{"WH"}},; {"I" ,{"A","E","EE","IA","IE","O","U","UI","Y","YE","UY","EI","IGH"}},; {"IS" ,{"US","ACE","ICE"}},; {"ISE" ,{"IZE"}},; {"J" ,{"D","DG","DI","DJ","G","GG"}},; {"K" ,{"C","CC","CH","CK","CQU","CU","LK","Q","QU","QUE"}},; {"KW" ,{"QU"}},; {"L" ,{"SL"}},; {"LE" ,{"TLE","AL"}},; {"M" ,{"CHM","GM","LM","MB","MN","N"}},; {"N" ,{"M","PN","GN","KN","MN"}},; {"NG" ,{"N","NGUE"}},; {"O" ,{"AU","EAU","EO","EW","OA","OE","OH","OU","OUGH","OW","A","AH","AW","UO","E"}},; {"OE" ,{"OWE","OUGH"}},; {"OO" ,{"O","EU","EW","OE","OU","OUGH","U","UE","UI"}},; {"OU" ,{"OUGH","OW"}},; {"PER" ,{"PRO","PRI","PRA","PRU"}},; {"PRE" ,{"PRO","PRI","PRA","PRU"}},; {"R" ,{"RH","WR"}},; {"S" ,{"C","CE","PS","SC","SCH"}},; {"SH" ,{"CE","CH","CI","S","SCH","SCI","SE","SI","SS","SSI","TI"}},; {"SI" ,{"PSY","CY"}},; {"T" ,{"ED","GHT","PT","TH"}},; {"TION" ,{"SION","CION","CEAN","CIAN"}},; {"TIOUS",{"SEOUS"}},; {"TURE" ,{"TEUR"}},; {"U" ,{"O","OE","OO","OU","EUA","EU","EUE","EW","IEU","IEW","UE","UI","YOU","YU"}},; {"UR" ,{"EAR","ER","IR","YR"}},; {"V" ,{"F","LV","PH"}},; {"W" ,{"O","U","WH"}},; {"X" ,{"CKS","GZ","K"}},; {"Y" ,{"I","J","IE","EI"}},; {"Z" ,{"S","SC","SS","X","GE","SI","ZI"}},; {"ZI" ,{"XY"}} } STATIC aEnds := { "ED","ER","ING","LY","AL","FUL","NESS","MENT","IVE" } STATIC aBegs := { "UN","IN","DIS","MIS","EN","WEL","AL" } LOCAL jj,kk,zz,ii LOCAL cHold LOCAL aRet_ := {} // List of suggested words LOCAL cTemp LOCAL nSugg LOCAL nSize := len(aParts_) LOCAL nSuffix := len(aEnds) LOCAL cMeta LOCAL cFirst LOCAL cKey LOCAL arr_ **DEBUG** @24,30 SAY "At SP_suggest" cWord := upper(trim(cWord)) zz := len(cWord) if zz =1 // Don't offer suggestions for return aRet_ // single letter words endif if lInclude == NIL ; lInclude := .F. ; endif // // Should the current word be included? // ////////////////////////////////// if Sp_Check(cWord) Aadd(aRet_,"A0AA"+cWord) endif if "'" $ cWord cHold := SP_Expand(cWord) if !empty(cHold) Aadd(aRet_,"A1AA"+cHold) endif endif if CHECK_RUNONS arr_ := SP_Split( cWord ) for jj := 1 to len(arr_) Aadd(aRet_,"A1AA"+arr_[jj] ) next endif if SUGGEST_PREFERENCE $ "AB" // // Step One - Do letter doubling // ////////////////////////////////// for jj := 2 to zz if substr(cWord,jj,1)$"BCDEFGKLMNOPRSTZ" cHold := left(cWord,jj)+substr(cWord,jj, 1)+; substr(cWord,jj+1) // // If the word is not already in the list, then check // to see if it is a valid word. // ////////////////////////////////////////// if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif endif next // // Step Two - Remove extra letters // ////////////////////////////////// for jj := 1 to zz cHold := left(cWord,jj-1)+substr(cWord,jj+1) // // If the word is not already in the list, then check // to see if it is a valid word. // ////////////////////////////////////////// if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif next // // Step Three - Transpose the letters // ////////////////////////// for jj := 2 to zz cHold := left(cWord,jj-2)+substr(cWord,jj, 1)+; substr(cWord,jj-1,1)+substr(cWord,jj+1) if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif next // // Step Four - Try adding a silent E to the end // ////////////////// cHold := cWord+"E" if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif // // Step Five - Do sound alike substitutions // ////////////////////////////////// for jj := 1 to nSize if aParts_[jj,1]$cWord if aParts_[jj,1]$"AEIOUT" ii := 0 do while (ii := fat(aParts_[jj,1],cWord,ii))>0 for kk := 1 to len(aParts_[jj,2]) cHold := substr(cWord,1,ii-1)+aParts_[jj,2,kk]+substr(cWord,ii+1) if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif next ii++ enddo else for kk := 1 to len(aParts_[jj,2]) cHold := strtran(cWord,aParts_[jj,1],aParts_[jj,2,kk]) if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 .and. Sp_Check(cHold) Aadd(aRet_,"B"+SP_Rate(cHold,cWord)+cHold) endif next endif endif next nSugg := len(aRet_) // // At this point, we have a list of words in aRet_, which now need to // be checked for suffixes, prefixes, and plurals. // for jj := 1 to nSugg cHold := trim(substr(aRet_[jj],5)) // Extract the word zz := len(cHold) // // Check suffixes // //////////////// if ADD_SUFFIXES for kk := 1 to nSuffix cTemp := cHold+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; Sp_Check(cTemp) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next // // Try doubling the last letter // ////////// for kk := 1 to nSuffix cTemp := cHold+substr(cHold,zz,1)+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next // // Accomodate words ending in C // ////////// if substr(cHold,-1,1)=="C" for kk := 1 to nSuffix cTemp := cHold+"K"+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next endif // // Accomodate words ending in ND // ////////// if substr(cHold,-2,2)=="ND" cTemp := substr(cHold,1,zz-1)+"SE" if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif for kk := 1 to nSuffix cTemp := substr(cHold,1,zz-1)+"SE"+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif cTemp := substr(cHold,1,zz-1)+"S"+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next endif // // Words ending in E, remove the E and try // /////////////////////////////////////// if substr(cHold,zz,1)=="E" for kk := 1 to nSuffix cTemp := substr(cHold,1,zz-1)+aEnds[kk] if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next endif endif if ADD_PREFIXES for kk := 1 to 7 cTemp := aBegs[kk]+cHold if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check( cTemp ) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif next endif if ADD_PLURALS cTemp := cHold+"S" if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check(cTemp) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif if substr(cHold,zz,1)=="Y" cTemp := substr(cHold,1,zz-1)+"IES" if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check(cTemp) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif elseif substr(cHold,zz,1)=="F" cTemp := substr(cHold,1,zz-1)+"VES" if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check(cTemp) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif elseif substr(cHold,zz,1)=="O" cTemp := cHold+"ES" if ascan(aRet_,{ |xx| substr(xx,5)==cTemp}) = 0 .and. ; SP_Check(cTemp) Aadd( aRet_,"C"+SP_Rate(cTemp,cWord)+cTemp ) endif endif endif next endif // // Check metaphone matches, only if SUGGEST_PREFERENCE is metafone or both, // because this search can slow things down a bit // //////////////////////////// if SUGGEST_PREFERENCE $ "MB" if METAPHONE_SIZE = 0 zz := min( 5,len(cWord)) if zz < 3 zz := 3 endif else zz := METAPHONE_SIZE endif cTemp := SP_GetBuf(cWord) ii := len(cTemp) cMeta := C_Metafone(cWord,zz) zz := len(cMeta) cFirst := substr(cWord,1,2) if ii > 0 if substr(cMeta,2,1) >= "N" kk := ii-5 jj := ii-4 do while kk > 0 if asc(substr(cTemp,kk,1)) >= 128 // End of word cHold := cFirst+xUnForm(substr(cTemp,kk+1,jj-kk)) cKey := c_metafone(cHold,zz) if cMeta == c_Metafone(cHold,zz) if MAX_DIFFERENCE < 0 .or. abs(len(cWord)-len(cHold)) <= MAX_DIFFERENCE if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 Aadd( aRet_,"B"+SP_Rate(cHold,cWord)+cHold ) endif endif elseif cKey < cMeta exit endif jj := kk endif kk-- enddo else kk := 1 jj := 1 do while kk < ii if asc(substr(cTemp,kk,1)) >= 128 // End of word cHold := cFirst+xUnForm(substr(cTemp,jj,kk-jj+1)) cKey := c_metafone(cHold,zz) if cMeta == c_Metafone(cHold,zz) if MAX_DIFFERENCE < 0 .or. abs(len(cWord)-len(cHold)) <= MAX_DIFFERENCE if ascan(aRet_,{ |xx| substr(xx,5)==cHold}) = 0 Aadd( aRet_,"B"+SP_Rate(cHold,cWord)+cHold ) endif endif elseif cKey > cMeta exit endif jj := kk+1 endif kk++ enddo endif endif endif nSugg := len(aRet_) if nSugg > 1 .and. SORT_SUGGESTIONS Asort(aRet_) endif for kk = 1 to nSugg aRet_[kk] := substr(aRet_[kk],5) next return aRet_ ******************************************************************************* ** ** Function: Sp_Quick() ** Purpose: To return an array of quick spellings ** Syntax: aSuggest = Sp_Quick( cWord ) ** Arguments: cWord - Word to look for suggestions for ** Returns: aSuggest - List of suggested words ** ****************************************************************************** function Sp_Quick( cWord ) STATIC aTryThese := { "AI$AO$AU$AY$EA$EI$EIGH$ET$EY$E$O$",; "A$AE$AI$AY$EA$EI$EO$IE$U$O$",; "A$E$EE$IA$IE$O$U$UI$Y$YE$UY$EI$IGH$",; "AU$EAU$EO$EW$OA$OE$OH$OU$OUGH$OW$A$AH$AW$UO$E$",; "O$OE$OO$OU$EUA$EU$EUE$EW$IEU$IEW$UE$UI$YOU$YU$",; "ED$GHT$PT$TH$" } LOCAL ii := 0 LOCAL nOld,jj,kk LOCAL zz := len(cWord) LOCAL arr_ := {} LOCAL cHold,ll,cTemp **DEBUG** @24,30 SAY "At SP_quick" cWord := upper(trim(cWord)) if zz < 3 && Don't offer suggestions for return {} && one or two letter words endif ** ** Step One - Do letter doubling ** ********************************** for jj = 2 to zz if substr(cWord,jj,1)$"BCDEFGKLMNOPRSTZ" cHold := left(cWord,jj)+substr(cWord,jj, 1)+; substr(cWord,jj+1) ** ** If the word is not already in the list, then check ** to see if it is a valid word. ** ****************************************** if ascan(arr_,cHold) = 0 .and. Sp_Check(cHold) Aadd(arr_,cHold) endif endif next ** ** Step Two - Remove extra letters ** ********************************** for jj = 1 to zz cHold := left(cWord,jj-1)+substr(cWord,jj+1) ** ** If the word is not already in the list, then check ** to see if it is a valid word. ** ****************************************** if ascan(arr_,cHold) = 0 .and. Sp_Check(cHold) Aadd(arr_,cHold) endif next ** ** Step Three - Transpose the letters ** ************************** for jj = 2 to zz cHold := left(cWord,jj-2)+substr(cWord,jj, 1)+; substr(cWord,jj-1,1)+substr(cWord,jj+1) if ascan(arr_,cHold) = 0 .and. Sp_Check(cHold) Aadd(arr_,cHold) endif next ** ** Step Four - Try adding a silent E to the end ** ****************** cHold := cWord+"E" if ascan(arr_,cHold) = 0 .and. Sp_Check(cHold) Aadd(arr_,cHold) endif ** ** Step Five - Do sound alike substitutions ** ********************************** for jj = 1 to 6 if substr("AEIOUT",jj,1)$cWord ii := fat(substr("AEIOUT",jj,1),cWord,ii) nold := 1 do while ii > 0 for kk = 1 to chrcount("$",aTryThese[jj]) ll := fat("$",aTryThese[jj],nOld) cTemp := substr(aTryThese[jj],nOld,ll-nOld) nOld := ll +1 cHold := substr(cWord,1,ii-1)+cTemp+substr(cWord,ii+1) if ascan(arr_,cHold) = 0 .and. Sp_Check(cHold) Aadd(arr_,cHold) endif next ii++ ii := fat(substr("AEIOUT",jj,1),cWord,ii) enddo endif next return arr_ STATIC function ChrCount(cChar,cString) **DEBUG** @24,30 SAY "At ChrCount" return len(cString)-len(strTran(cString,cChar,"")) ** ** Function: Sp_Split() ** Purpose: ** Syntax: ** Arguments: ** Returns: ** ****************************************************************************** function Sp_Split( cWord ) LOCAL arr_ := {} LOCAL x LOCAL cWord1 LOCAL cWord2 **DEBUG** @24,30 SAY "At SP_split" for x := 2 to len(cWord) cWord1 := left(cWord,x-1) cWord2 := substr(cWord,x) if len(cWord1) > 1 .and. len(cWord2) > 1 if SP_Check(cWord1) .and. SP_Check(cWord2) Aadd(arr_,cWord1+' '+cWord2) endif endif next return arr_ ****************************************************************************** ** ** Function: Sp_Expand() ** Purpose: ** Syntax: ** Arguments: ** Returns: ** ****************************************************************************** function SP_Expand(cWord) LOCAL x LOCAL cExpand **DEBUG** @24,30 SAY "At SP_expand" cWord := upper(alltrim(cWord)) x := ascan( aContracts, { |jj|jj[1]=cWord } ) if x > 0 cExpand := aContracts[x,2] endif return cExpand ****************************************************************************** ** ** Function: Sp_WildCard() ** Purpose: To return an array of wildcard matches ** Syntax: aSuggest = Sp_WildCard( cPattern ) ** Arguments: cPattern - Pattern to match using * or ?'s ** Returns: aSuggest - List of matching words ** ****************************************************************************** function Sp_Wildcard( cPattern ) LOCAL cTemp LOCAL ii,kk,jj,cFirst,cHold,x,z LOCAL arr_ := {} LOCAL nStart,nEnd **DEBUG** @24,30 SAY "At SP_wildcard" cPattern := upper(cPattern) if SP_Init() x := fat("*",cPattern) if x = 0 x := fat("?",cPattern) endif if x = 1 // Can't handle wildcards in first position return arr_ endif if x = 2 nStart := 1 nEnd := 26 else nStart := asc(substr(cPattern,2,1))-64 nEnd := nStart endif for z = nStart to nEnd cTemp := SP_GetBuf(substr(cPattern,1,1)+chr(z+64) ) ii := len(cTemp) cFirst := substr(cPattern,1,1)+chr(z+64) if ii > 0 kk := 1 jj := 1 do while kk < ii if asc(substr(cTemp,kk,1)) >= 128 // End of word cHold := cFirst+xUnForm(substr(cTemp,jj,kk-jj+1)) if WildCard(cPattern,cHold) Aadd( arr_,cHold ) endif jj := kk+1 endif kk++ enddo endif next endif return arr_ ****************************************************************************** * The following functions are internal and should not be modified. * ****************************************************************************** // Function: SP_Init() // Purpose: Internal function to initialize the dictionary // Arguments: NONE // Returns: - Was dictionary initialized? // // Notes: // //////////////////////////// function Sp_init LOCAL isok := .T. LOCAL cBuf LOCAL i LOCAL j LOCAL nOther := 0 LOCAL cOther := "" LOCAL nFileSize **DEBUG** @24,30 SAY "At SP_init" if cOffsets = NIL isok := .F. nHandle := fopen(DICTIONARY_PATH+DICTIONARY_NAME,32) if nHandle > 0 #ifdef REDUCE_MEMORY_NEEDS cBuf := space(6) fread(nHandle,@cBuf,6) #else cBuf := space(NSIZE+6) fread(nHandle,@cBuf,NSIZE+6) #endif if substr(cBuf,1,2)=="JJ" nOther := bin2l(substr(cBuf,3,4)) #ifndef REDUCE_MEMORY_NEEDS cOffSets := substr(cBuf,7) #endif nFileSize := fseek(nHandle,0,2) if nFileSize-nOther > 0 cOther := space(nFileSize-nOther) fseek(nHandle,nOther,0) fread(nHandle,@cOther,(nFileSize-nOther)) endif aGlobal[2] += cOther isok := .T. endif if !empty( AUXILIARY_DICTIONARY ) SP_LoadAux( AUXILIARY_DICTIONARY ) endif endif if aGlobal[1] = NIL SP_Common() endif ** Thesaurus comented out as not needed * if !empty( THESAURUS_NAME ) * if file( DICTIONARY_PATH+THESAURUS_NAME ) * SP_OpenThes( DICTIONARY_PATH+THESAURUS_NAME ) * elseif file( THESAURUS_NAME ) * SP_OpenThes( THESAURUS_NAME ) * endif * endif endif return isok ****************************************************************************** // // Function: DBF2Dic() // Purpose: To create a DIC file from a DBF list of words // Syntax: nStatus := DBF2Dic( , ) // Arguments: - DBF containing sorted list of upper // case words. The field name must be // WORD and of type character // - Name of DIC file to create, assumes // DICT.DIC // Returns: nStatus - 0 if successful // -1 DBF file does not exist // -2 Field WORD does not exist in DBF // >0 File error occurred, see FERROR() // // Calls: // ////////////////////////////////// function DBF2Dic(cDbf,cDictionary,lTalk) LOCAL nStatus := 0 // Status code LOCAL i // Loop counters LOCAL j LOCAL x LOCAL nH // File handle LOCAL nWhere LOCAL temp LOCAL cBits LOCAL cSave LOCAL nSize LOCAL cOther := "" LOCAL nCurRec := 0 // // DEFAULT name for dictionary file // ////////////////////////////////////// **DEBUG** @24,30 SAY "At DBF2DIC" if cDictionary = NIL cDictionary := "DICT.DIC" endif if lTalk = NIL lTalk := .F. endif // // See if the DBF file exists // ////////////////////////////////////// if .not. "."$cDBF cDBF += ".DBF" endif if !file( cDBF ) return -1 endif use (cDbf) alias DICT new if fieldpos("WORD") = 0 use return -2 endif if lTalk cSave := SaveScreen(8,30,11,48) DispBox(8,30,11,48,,"W+/R") @ 8,33 say " Creating DIC " color "W+/R" @ 9,31 say " Indexing words " color "W/R" @ 10,31 say " " color "W/R" nSize := DICT->(lastrec()) endif index on substr(DICT->word,1,2)+padr(c_metafone(alltrim(DICT->word),5),6) to $$temp go top if lTalk @ 9,31 say "Percent " color "W/R" @ 10,31 say "Complete: " color "W/R" endif // // Create the dictionary file // ////////////////////////////////////// && ADDED - 02/08/96 - JAMES IF nHandle > 0 && Is dictionary already open? FCLOSE(nHandle) && Yes, close it nHandle = 0 ENDIF && END OF ADDITION nH := fcreate(cDictionary) if nH >= 0 // // Write out enough bytes to hold the index information // ///////////////////////////////////////////////////////////////////////// fwrite(nH,"JJ"+l2bin(NSIZE+4)+replicate(chr(0),NSIZE)+space(10),NSIZE+16) for i := 1 to 26 do while substr(DICT->word,1,1)==chr(i+64) .and. !eof() for j := 1 to 26 temp := "" cBits := FOUR_BYTES do while !isAlpha(substr(DICT->word,2,1)) .and. !eof() if len(alltrim(DICT->word)) > 0 cOther += "|"+alltrim(DICT->word) endif skip +1 enddo do while substr(DICT->word,2,1)==chr(j+64) .and. !eof() if len(trim(DICT->word))=3 Bit(cBits,asc(substr(DICT->word,3,1))-64,.T.) elseif len(trim(DICT->word))==2 Bit(cBits,27,.T.) else temp += StoreWord(trim(DICT->word)) endif skip +1 if lTalk nCurRec++ @ 10,41 say (nCurRec/nSize) * 100 picture "999.9%" color "W+/R" endif enddo if !empty(temp) .or. cBits <> FOUR_BYTES nWhere := fseek(nH,0,2) Fseek(nH,((i-1)*26*EACH_WORD)+((j-1)*EACH_WORD)+6) Fwrite(nH,l2bin(nWhere)+i2bin(len(temp)+4),EACH_WORD) Fseek(nH,0,2) Fwrite(nH,cBits+temp,len(temp)+4) endif next enddo next j := fseek(nH,0,2) fseek(nH,2,0) fwrite(nH,l2bin(j),4) if !empty(cOther) fseek(nH,j,0) cOther += "|" fwrite(nH,cOther,len(cOther)) endif fclose(nH) else nStatus := nH endif if lTalk RestScreen(8,30,11,48,cSave) endif select DICT use // ferase("$$TEMP"+indexext()) return nStatus ******************************************************************************* // // Function: Dic2DBF() // Purpose: To create a DBF file from a DIC file // Syntax: nStatus := DIC2DBF( , ) // Arguments: - Name of DIC file to read, assumes // DICT.DIC // - Name of DBF to create, assumes DICT.DBF // It will contain a single field called // WORD of type character // Returns: nStatus - 0 if successful // -1 DIC file does not exist // -2 DIC file is not a valid dictionary // // Calls: // ////////////////////////////////// function DIC2DBF(cDictionary,cDBF,lTalk) LOCAL nStatus := 0 // Status code LOCAL i // Loop counters LOCAL j LOCAL x,y,z LOCAL temp LOCAL cBuf LOCAL cWord LOCAL cSave LOCAL nSize **DEBUG** @24,30 SAY "At DIC2DBF" // // DEFAULT name for dictionary file // ////////////////////////////////////// if cDictionary = NIL cDictionary := "DICT.DIC" endif if lTalk = NIL lTalk := .F. endif if !file(cDictionary) return -1 endif DICTIONARY_NAME = cDictionary // // Read the dictionary file // ////////////////////////////////////// if !SP_Init() return -2 endif // // See if the DBF file exists // ////////////////////////////////////// if .not. "."$cDBF cDBF += ".DBF" endif dbcreate( cDbf,{ {"WORD","C",25,0} } ) use (cDbf) alias DICT new if lTalk cSave := SaveScreen(8,30,12,48) DispBox(8,30,12,48,,"W+/R") @ 8,34 say " Creating DBF " color "W+/R" @ 9,31 say "Percent " color "W/R" @ 10,31 say "Complete: " color "W/R" @ 11,31 say " Record: " color "W/R" temp := directory(cDictionary) nSize := temp[1,2] endif j := 2 for i := 2 to len(CACHE_WORDS) if substr(CACHE_WORDS,i,1)=="|" append blank replace DICT->word with substr(CACHE_WORDS,j,i-j) j := i +1 endif next for i := 1 to 26 for j := 1 to 26 temp = chr(i+64)+chr(j+64) x := bin2l(substr(cOffsets,((i-1)*156)+((j-1)*EACH_WORD+1),4)) if !empty(x) y := bin2w(substr(cOffsets,((i-1)*156)+((j-1)*EACH_WORD+5),2)) if lTalk @ 10,43 say (x/nSize) * 100 picture "999%" color "W+/R" @ 11,41 say lastrec() picture "99,999" color "W+/R" endif cBuf := space(y) fseek(nHandle,x,0) fread(nHandle,@cBuf,y) for z = 1 to 26 if bit(cBuf,z) append blank replace DICT->word with temp+chr(z+64) endif next if bit(cBuf,27) append blank replace DICT->word with temp endif cBuf := substr(cBuf,5) z := 1 do while !empty(cBuf) if substr(cBuf,z,1)>="€" cWord := substr(cBuf,1,z) append blank replace DICT->word with temp+ExtractWord(cWord) cWord := "" cBuf := substr(cBuf,z+1) z := 1 if lTalk @ 11,41 say lastrec() picture "99,999" color "W+/R" endif else z++ endif enddo if !empty(cWord) endif cWord := "" endif next next if lTalk RestScreen(8,30,12,48,cSave) endif select DICT use return nStatus ******************************************************************************** // Function: SP_Common() // Purpose: Loads the COMMON word static array element // Syntax: SP_Common() // Arguments: // Returns: NIL // // Notes: The common word list represents some of the most commonly // used English words. They are stored in RAM to prevent a // dictionary lookup for 70-80% of the words. // /////////////////////////// STATIC function SP_common #ifDef REDUCE_MEMORY_NEEDS aGlobal_[1] := "|THE|OF|AND|TO|A|IN|THAT|FOR|IS|WAS|IT|HE|I|AS"+; "|WITH|ON|HIS|BE|AT|BY|NOT|THIS|HAD|HAVE|YOU|BUT"+; "|FROM|ARE|OR|WHICH|AN|THEY|WILL|ONE|WERE|ALL|WE"+; "|HER|SHE|WOULD|THERE|HAS|BEEN|HIM|THEIR|IF|WHEN"+; "|SO|MORE|NO|WHO|YOUR|OUT|MY|I'VE|I'D|I'LL|CAN'T|" #else aGlobal[1] := "|THE|OF|AND|TO|A|IN|THAT|FOR|IS|WAS|IT|HE|I|AS|WITH|ON|HIS|BE|AT|BY"+; "|NOT|THIS|HAD|HAVE|YOU|BUT|FROM|ARE|OR|WHICH|AN|THEY|WILL|ONE|WERE|ALL"+; "|WE|HER|SHE|WOULD|THERE|HAS|BEEN|HIM|THEIR|IF|WHEN|SO|MORE|NO|WHO|YOUR"+; "|OUT|MY|WHAT|UP|CAN|THEM|ABOUT|INTO|ITS|TIME|SOME|THAN|ME|OTHER|ONLY|NEW"+; "|COULD|SAID|ANY|THESE|MAY|TWO|THEN|DO|FIRST|NOW|MAN|SUCH|VERY|LIKE|OVER"+; "|OUR|EVEN|MOST|AFTER|MADE|ALSO|DID|MANY|SHOULD|BEFORE|MUST|THROUGH|YEARS"+; "|WHERE|MUCH|WAY|WELL|DOWN|GOOD|BECAUSE|HERE|EACH|THOSE|PEOPLE|STATE|HOW"+; "|TOO|LITTLE|WORLD|LAST|WORK|STILL|BACK|OWN|SEE|MEN|LONG|GET|JUST|GO|HOUSE"+; "|BETWEEN|BOTH|LIFE|BEING|UNDER|YEAR|NEVER|DAY|SAME|ANOTHER|KNOW|WHILE|MIGHT"+; "|US|GREAT|OLD|OFF|COME|AGAINST|SINCE|CAME|RIGHT|TAKE|USED|THREE|SMALL|MAKE"+; "|STATES|HIMSELF|FEW|DURING|WITHOUT|AGAIN|PLACE|AROUND|HOWEVER|HOME|FOUND"+; "|SAY|WHOSE|THOUGHT|NIGHT|DEAR|ONCE|WENT|GENERAL|HIGH|UPON|SCHOOL|EVERY|PART"+; "|DON'T|DOES|GOT|UNITED|LEFT|NUMBER|COURSE|WAR|GOING|UNTIL|ALWAYS|AWAY"+; "|SOMETHING|FACT|THOUGH|WATER|LESS|PUBLIC|PUT|THINK|ALMOST|HAND|ENOUGH|FAR"+; "|TOOK|YET|GOVERNMENT|SYSTEM|BETTER|EYES|SET|TOLD|NOTHING|PRESIDENT|END"+; "|HEAD|WHY|CALLED|DIDN'T|FIND|LOOK|ASKED|LATER|POINT|NEXT|PROGRAM|KNEW|CITY"+; "|BUSINESS|GIVE|GROUP|TOWARD|YOUNG|DAYS|LET|ROOM|WITHIN|CHILDREN|SIDE|SOCIAL"+; "|GIVEN|ORDER|PRESENT|SEVERAL|NATIONAL|USE|RATHER|SECOND|FACE|PER|POSSIBLE"+; "|AMONG|FORM|IMPORTANT|OFTEN|THINGS|LOOKED|EARLY|WHITE|BECOME|CASE|SEND"+; "|BIG|LARGE|NEED|FOUR|FELT|ALONG|GOD|SAW|BEST|CHURCH|EVER|LEAST|POWER"+; "|DEVELOPMENT|LIGHT|THING|FAMILY|INTEREST|SEEMED|WANT|TODAY|MEMBERS|MIND"+; "|COUNTRY|AREA|OTHERS|TURNED|ALTHOUGH|DONE|OPEN|SERVICE|CERTAIN|KIND|PROBLEM"+; "|BEGAN|DIFFERENT|DOOR|THUS|HELP|MEANS|SENSE|WHOLE|MATTER|PERHAPS|ITSELF"+; "|IT'S|TIMES|HUMAN|LAW|LINE|ABOVE|NAME|EXAMPLE|COMPANY|HANDS|LOCAL|SHOW"+; "|BODY|FIVE|HISTORY|WHETHER|GAVE|EITHER|ACROSS|ACT|ACTION|FEET|ANYTHING"+; "|PAST|QUITE|TAKEN|HAVING|SEEN|DEATH|EXPERIENCE|HALF|REALLY|WEEK|WORDS"+; "|CAR|FIELD|MONEY|WORD|ALREADY|THEMSELVES|INFORMATION|I'M|TELL|CLOSE"+; "|COLLEGE|PERIOD|SHALL|TOGETHER|HELD|KEEP|SURE|PROBABLY|FREE|REAL|BEHIND"+; "|SEEMS|CANNOT|POLITICAL|AIR|QUESTION|OFFICE|WOMAN|BROUGHT|MAJOR|MOTHER"+; "|SPECIAL|HEARD|PROBLEMS|AGO|BECAME|AVAILABLE|FEDERAL|MOMENT|STUDY"+; "|KNOWN|RESULT|STREET|BOY|ECONOMIC|CHANGE|POSITION|REASON|SOUTH|BOARD"+; "|INDIVIDUAL|JOB|SOCIETY|AREAS|WEST|SPACE|TURN|LOVE|COMMUNITY|TOWN|TRUE"+; "|COURT|FORCE|FULL|SEEM|AGE|AM|COST|WIFE|FUTURE|VOICE|WANTED|CAN'T|DEPARTMENT"+; "|USE|CENTER|COMMON|CONTROL|NECESSARY|POLICY|FOLLOWING|FRONTSOMETIMES|FATHER"+; "|GIRL|SIX|WOMEN|CLEAR|MILITARY|FURTHER|ABLE|FIGURE|LAND|FEEL|MUSIC|PARTY"+; "|PROVIDE|CENTURY|EDUCATION|UNIVERSITY|CHILD|EFFECT|STUDENTS|RUN|SHORT"+; "|STOOD|MORNING|TOTAL|OUTSIDE|ART|RATE|SAYS|YOU'RE|CLASS|TYPE|EVIDENCE"+; "|EXCEPT|LEAVE|MILLION|MISS|NORTH|PLAN|SOUND|THEREFORE|TOP|USUALLY|BLACK"+; "|HARD|SCHOOLS|STRONG|BELIEVE|PLAY|VARIOUS|SURFACE|MAKING|MEAN|SOON|VALUE"+; "|LINES|MODERN|NEAR|TABLE|PRIVATE|RED|ROAD|TAX|ALONE|MINUTES|PERSONAL|PROCESS"+; "|SITUATION|THAT'S|GONE|IDEA|INCREASE|NOR|PEACE|SECTION|LIVING|STARTED|BOOK"+; "|LONGER|CUT|FINALLY|NATURE|SECRETARY|MONTHS|THIRD|CALL|COMPLETE|GREATER" aGlobal[1] += "|EXPECTED|FIRE|NEEDED|KEPT|VALUES|VIEW|BASIS|DARK|EVERYTHING|PRESSURE"+; "|GROUND|EAST|RECENT|REQUIRED|SPIRIT|UNION|HOPE|I'LL|MOVED|NATIONS|WROTE"+; "|CONDITIONS|RETURN|SUPPORT|ATTENTION|LATE|PARTICULAR|BROWN|COSTS|ELSE|NATION"+; "|BEYOND|COULDN'T|FORCES|HOURS|NUCLEAR|PERSON|TAKING|COMING|DEAD|INSIDE|LOW"+; "|MATERIAL|REPORT|STAGE|ADDED|AMOUNT|BASIC|DATA|FEELING|FOLLOWED|HEART|INSTEAD"+; "|LOOKING|LOST|MILES|PAY|SINGLE|COLD|HUNDRED|INCLUDING|INDUSTRY|MOVE|RESEARCH"+; "|SHOWN|BRIDGE|DEVELOPED|SIMPLY|TRIED|HOLD|REACHED|SHARE|SORT|COMMITTEE|DEFENSE"+; "|EQUIPMENT|ISLAND|SENDING|ACTUALLY|EARTH|BEGINNING|RELIGIOUS|RIVER|TEN|CENTRAL"+; "|DOING|GETTING|LETTER|RECEIVED|REST|TERMS|TRYING|CARE|FRIENDS|INDEED|MEDICAL"+; "|PICTURE|DIFFICULT|FINE|SIMPLE|STATUS|SUBJECT|BUILDING|ESPECIALLY|HIGHER|RANGE"+; "|WALL|BRING|MEETING|WALKED|CENT|FLOOR|FOREIGN|NEEDS|PAPER|PASSED|SIMILAR"+; "|FINAL|NATURAL|PROPERTY|TRAINING|COUNTY|GROWTH|HOT|INTERNATIONAL|LIVE"+; "|MARKET|POLICE|START|TALK|WASN'T|WRITTEN|BOOKS|ANSWER|CONGRESS|HEAR|PRIMARY"+; "|STORY|SUDDENLY|CONSIDERED|COUNTRIES|HALL|ISSUE|LIKELY|PARTICULARLY|TRULY"+; "|WORKING|EFFORT|SAT|ENTIRE|HAPPENED|LABOR|PURPOSE|RESULTS|CASES|DIFFERENCE"+; "|HAIR|PRODUCTION|STAND|I'VE|I'D|WON'T|" #endif **DEBUG** @24,30 SAY "At SP_common" return NIL ****************************************************************************** function WildCard(cPattern,cString) LOCAL lMatch := .F. LOCAL x := at("*",cPattern) LOCAL cBefore LOCAL cAfter LOCAL y LOCAL nStrSize,nPatSize **DEBUG** @24,30 SAY "At wildcard" cString := upper(alltrim(cString)) if cPattern == "*" return .T. endif // // Do a * match // if x > 0 cBefore := upper(substr(cPattern,1,x-1)) cAfter := upper(substr(cPattern,x+1)) do case case empty(cBefore) lMatch := right(cString,len(cAfter))==cAfter case empty(cAfter) lMatch := left(cString,len(cBefore))==cBefore otherwise lMatch := ( left(cString,len(cBefore))==cBefore ) .and. ; right(cString,len(cAfter))==cAfter endcase else x := at("?",cPattern) if x > 0 nPatSize := len(cPattern) nStrSize := len(cString) if nPatSize == nStrSize lMatch := .T. for y := 1 to nPatSize if substr(cPattern,y,1)<>"?" .and. ; substr(cPattern,y,1)<> substr(cString,y,1) lMatch := .F. exit endif next endif else lMatch := upper(cPattern)=upper(cString) endif endif return lMatch ****************************************************************************** #ifndef CLIP52 function aWords(cLine) LOCAL aWords_ := {} LOCAL nSize := len(trim(cLine)) LOCAL x,y,z LOCAL cWord := "" LOCAL nOffset **DEBUG** @24,30 SAY "At clip52" z := 0 do while z <= nSize z++ y := asc(substr(cLine,z,1)) if y >=48 .and. ! chr(y)$ ":;<=>?@[\^]_`{|}~" nOffset := z cWord := chr(y) z++ y := asc(substr(cLine,z,1)) while ( y >=48 .and. ! chr(y)$ ":;<=>?@[\^]_`{|}~" ) .or. y == 39 cWord += chr(y) z++ if z > nSize exit endif y := asc(substr(cLine,z,1)) enddo Aadd(aWords_,{cWord,nOffset}) endif enddo return aWords_ #endif * Find an occurrence of 'f_str' in 'l_str' starting from position 'f_rom' FUNCTION FAT(f_str, l_str, f_rom) LOCAL ret_val **DEBUG** @24,30 SAY "At FAT" IF PCOUNT() < 3 && Is f_rom passed? f_rom = 1 ENDIF ret_val = AT(f_str, SUBSTR(l_str, f_rom)) RETURN(ret_val)