LCOV - code coverage report
Current view: top level - contrib/fuzzystrmatch - dmetaphone.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 17.8 % 645 115
Test Date: 2026-08-15 13:15:45 Functions: 73.3 % 15 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 10.4 % 452 47

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * This is a port of the Double Metaphone algorithm for use in PostgreSQL.
       3                 :             :  *
       4                 :             :  * contrib/fuzzystrmatch/dmetaphone.c
       5                 :             :  *
       6                 :             :  * Double Metaphone computes 2 "sounds like" strings - a primary and an
       7                 :             :  * alternate. In most cases they are the same, but for foreign names
       8                 :             :  * especially they can be a bit different, depending on pronunciation.
       9                 :             :  *
      10                 :             :  * Information on using Double Metaphone can be found at
      11                 :             :  *   http://www.codeproject.com/string/dmetaphone1.asp
      12                 :             :  * and the original article describing it can be found at
      13                 :             :  *   http://drdobbs.com/184401251
      14                 :             :  *
      15                 :             :  * For PostgreSQL we provide 2 functions - one for the primary and one for
      16                 :             :  * the alternate. That way the functions are pure text->text mappings that
      17                 :             :  * are useful in functional indexes. These are 'dmetaphone' for the
      18                 :             :  * primary and 'dmetaphone_alt' for the alternate.
      19                 :             :  *
      20                 :             :  * Assuming that dmetaphone.so is in $libdir, the SQL to set up the
      21                 :             :  * functions looks like this:
      22                 :             :  *
      23                 :             :  * CREATE FUNCTION dmetaphone (text) RETURNS text
      24                 :             :  *    LANGUAGE C IMMUTABLE STRICT
      25                 :             :  *    AS '$libdir/dmetaphone', 'dmetaphone';
      26                 :             :  *
      27                 :             :  * CREATE FUNCTION dmetaphone_alt (text) RETURNS text
      28                 :             :  *    LANGUAGE C IMMUTABLE STRICT
      29                 :             :  *    AS '$libdir/dmetaphone', 'dmetaphone_alt';
      30                 :             :  *
      31                 :             :  * Note that you have to declare the functions IMMUTABLE if you want to
      32                 :             :  * use them in functional indexes, and you have to declare them as STRICT
      33                 :             :  * as they do not check for NULL input, and will segfault if given NULL input.
      34                 :             :  * (See below for alternative ) Declaring them as STRICT means PostgreSQL
      35                 :             :  * will never call them with NULL, but instead assume the result is NULL,
      36                 :             :  * which is what we (I) want.
      37                 :             :  *
      38                 :             :  * Alternatively, compile with -DDMETAPHONE_NOSTRICT and the functions
      39                 :             :  * will detect NULL input and return NULL. The you don't have to declare them
      40                 :             :  * as STRICT.
      41                 :             :  *
      42                 :             :  * There is a small inefficiency here - each function call actually computes
      43                 :             :  * both the primary and the alternate and then throws away the one it doesn't
      44                 :             :  * need. That's the way the perl module was written, because perl can handle
      45                 :             :  * a list return more easily than we can in PostgreSQL. The result has been
      46                 :             :  * fast enough for my needs, but it could maybe be optimized a bit to remove
      47                 :             :  * that behaviour.
      48                 :             :  *
      49                 :             :  */
      50                 :             : 
      51                 :             : 
      52                 :             : /***************************** COPYRIGHT NOTICES ***********************
      53                 :             : 
      54                 :             : Most of this code is directly from the Text::DoubleMetaphone perl module
      55                 :             : version 0.05 available from https://www.cpan.org/.
      56                 :             : It bears this copyright notice:
      57                 :             : 
      58                 :             : 
      59                 :             :   Copyright 2000, Maurice Aubrey <maurice@hevanet.com>.
      60                 :             :   All rights reserved.
      61                 :             : 
      62                 :             :   This code is based heavily on the C++ implementation by
      63                 :             :   Lawrence Philips and incorporates several bug fixes courtesy
      64                 :             :   of Kevin Atkinson <kevina@users.sourceforge.net>.
      65                 :             : 
      66                 :             :   This module is free software; you may redistribute it and/or
      67                 :             :   modify it under the same terms as Perl itself.
      68                 :             : 
      69                 :             : The remaining code is authored by Andrew Dunstan <amdunstan@ncshp.org> and
      70                 :             : <andrew@dunslane.net> and is covered this copyright:
      71                 :             : 
      72                 :             :   Copyright 2003, North Carolina State Highway Patrol.
      73                 :             :   All rights reserved.
      74                 :             : 
      75                 :             :   Permission to use, copy, modify, and distribute this software and its
      76                 :             :   documentation for any purpose, without fee, and without a written agreement
      77                 :             :   is hereby granted, provided that the above copyright notice and this
      78                 :             :   paragraph and the following two paragraphs appear in all copies.
      79                 :             : 
      80                 :             :   IN NO EVENT SHALL THE NORTH CAROLINA STATE HIGHWAY PATROL BE LIABLE TO ANY
      81                 :             :   PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
      82                 :             :   INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
      83                 :             :   DOCUMENTATION, EVEN IF THE NORTH CAROLINA STATE HIGHWAY PATROL HAS BEEN
      84                 :             :   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      85                 :             : 
      86                 :             :   THE NORTH CAROLINA STATE HIGHWAY PATROL SPECIFICALLY DISCLAIMS ANY
      87                 :             :   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
      88                 :             :   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED
      89                 :             :   HEREUNDER IS ON AN "AS IS" BASIS, AND THE NORTH CAROLINA STATE HIGHWAY PATROL
      90                 :             :   HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
      91                 :             :   MODIFICATIONS.
      92                 :             : 
      93                 :             : ***********************************************************************/
      94                 :             : 
      95                 :             : 
      96                 :             : /* include these first, according to the docs */
      97                 :             : #ifndef DMETAPHONE_MAIN
      98                 :             : 
      99                 :             : #include "postgres.h"
     100                 :             : 
     101                 :             : #include "utils/builtins.h"
     102                 :             : #include "utils/formatting.h"
     103                 :             : 
     104                 :             : /* turn off assertions for embedded function */
     105                 :             : #define NDEBUG
     106                 :             : 
     107                 :             : #else                           /* DMETAPHONE_MAIN */
     108                 :             : 
     109                 :             : /* we need these if we didn't get them from postgres.h */
     110                 :             : #include <stdio.h>
     111                 :             : #include <stdlib.h>
     112                 :             : #include <string.h>
     113                 :             : #include <stdarg.h>
     114                 :             : 
     115                 :             : #endif                          /* DMETAPHONE_MAIN */
     116                 :             : 
     117                 :             : #include <assert.h>
     118                 :             : #include <ctype.h>
     119                 :             : 
     120                 :             : /* prototype for the main function we got from the perl module */
     121                 :             : static void DoubleMetaphone(const char *str, Oid collid, char **codes);
     122                 :             : 
     123                 :             : #ifndef DMETAPHONE_MAIN
     124                 :             : 
     125                 :             : /*
     126                 :             :  * The PostgreSQL visible dmetaphone function.
     127                 :             :  */
     128                 :             : 
     129                 :           2 : PG_FUNCTION_INFO_V1(dmetaphone);
     130                 :             : 
     131                 :             : Datum
     132                 :           1 : dmetaphone(PG_FUNCTION_ARGS)
     133                 :             : {
     134                 :             :     text       *arg;
     135                 :             :     char       *aptr,
     136                 :             :                *codes[2],
     137                 :             :                *code;
     138                 :             : 
     139                 :             : #ifdef DMETAPHONE_NOSTRICT
     140                 :             :     if (PG_ARGISNULL(0))
     141                 :             :         PG_RETURN_NULL();
     142                 :             : #endif
     143                 :           1 :     arg = PG_GETARG_TEXT_PP(0);
     144                 :           1 :     aptr = text_to_cstring(arg);
     145                 :             : 
     146                 :           1 :     DoubleMetaphone(aptr, PG_GET_COLLATION(), codes);
     147                 :           1 :     code = codes[0];
     148         [ -  + ]:           1 :     if (!code)
     149                 :           0 :         code = "";
     150                 :             : 
     151                 :           1 :     PG_RETURN_TEXT_P(cstring_to_text(code));
     152                 :             : }
     153                 :             : 
     154                 :             : /*
     155                 :             :  * The PostgreSQL visible dmetaphone_alt function.
     156                 :             :  */
     157                 :             : 
     158                 :           2 : PG_FUNCTION_INFO_V1(dmetaphone_alt);
     159                 :             : 
     160                 :             : Datum
     161                 :           1 : dmetaphone_alt(PG_FUNCTION_ARGS)
     162                 :             : {
     163                 :             :     text       *arg;
     164                 :             :     char       *aptr,
     165                 :             :                *codes[2],
     166                 :             :                *code;
     167                 :             : 
     168                 :             : #ifdef DMETAPHONE_NOSTRICT
     169                 :             :     if (PG_ARGISNULL(0))
     170                 :             :         PG_RETURN_NULL();
     171                 :             : #endif
     172                 :           1 :     arg = PG_GETARG_TEXT_PP(0);
     173                 :           1 :     aptr = text_to_cstring(arg);
     174                 :             : 
     175                 :           1 :     DoubleMetaphone(aptr, PG_GET_COLLATION(), codes);
     176                 :           1 :     code = codes[1];
     177         [ -  + ]:           1 :     if (!code)
     178                 :           0 :         code = "";
     179                 :             : 
     180                 :           1 :     PG_RETURN_TEXT_P(cstring_to_text(code));
     181                 :             : }
     182                 :             : 
     183                 :             : 
     184                 :             : /* here is where we start the code imported from the perl module */
     185                 :             : 
     186                 :             : /* all memory handling is done with these macros */
     187                 :             : 
     188                 :             : #define META_MALLOC(v,n,t) \
     189                 :             :           (v = (t*)palloc(((n)*sizeof(t))))
     190                 :             : 
     191                 :             : #define META_REALLOC(v,n,t) \
     192                 :             :                       (v = (t*)repalloc((v),((n)*sizeof(t))))
     193                 :             : 
     194                 :             : /*
     195                 :             :  * Don't do pfree - it seems to cause a SIGSEGV sometimes - which might have just
     196                 :             :  * been caused by reloading the module in development.
     197                 :             :  * So we rely on context cleanup - Tom Lane says pfree shouldn't be necessary
     198                 :             :  * in a case like this.
     199                 :             :  */
     200                 :             : 
     201                 :             : #define META_FREE(x) ((void)true)   /* pfree((x)) */
     202                 :             : #else                           /* not defined DMETAPHONE_MAIN */
     203                 :             : 
     204                 :             : /* use the standard malloc library when not running in PostgreSQL */
     205                 :             : 
     206                 :             : #define META_MALLOC(v,n,t) \
     207                 :             :           (v = (t*)malloc(((n)*sizeof(t))))
     208                 :             : 
     209                 :             : #define META_REALLOC(v,n,t) \
     210                 :             :                       (v = (t*)realloc((v),((n)*sizeof(t))))
     211                 :             : 
     212                 :             : #define META_FREE(x) free((x))
     213                 :             : #endif                          /* defined DMETAPHONE_MAIN */
     214                 :             : 
     215                 :             : 
     216                 :             : 
     217                 :             : /* this typedef was originally in the perl module's .h file */
     218                 :             : 
     219                 :             : typedef struct
     220                 :             : {
     221                 :             :     char       *str;
     222                 :             :     int         length;
     223                 :             :     int         bufsize;
     224                 :             :     int         free_string_on_destroy;
     225                 :             : }
     226                 :             : 
     227                 :             : metastring;
     228                 :             : 
     229                 :             : /*
     230                 :             :  * remaining perl module funcs unchanged except for declaring them static
     231                 :             :  * and reformatting to PostgreSQL indentation and to fit in 80 cols.
     232                 :             :  *
     233                 :             :  */
     234                 :             : 
     235                 :             : static metastring *
     236                 :           8 : NewMetaString(const char *init_str)
     237                 :             : {
     238                 :             :     metastring *s;
     239                 :           8 :     char        empty_string[] = "";
     240                 :             : 
     241                 :           8 :     META_MALLOC(s, 1, metastring);
     242                 :             :     assert(s != NULL);
     243                 :             : 
     244         [ -  + ]:           8 :     if (init_str == NULL)
     245                 :           0 :         init_str = empty_string;
     246                 :           8 :     s->length = strlen(init_str);
     247                 :             :     /* preallocate a bit more for potential growth */
     248                 :           8 :     s->bufsize = s->length + 7;
     249                 :             : 
     250                 :           8 :     META_MALLOC(s->str, s->bufsize, char);
     251                 :             :     assert(s->str != NULL);
     252                 :             : 
     253                 :           8 :     memcpy(s->str, init_str, s->length + 1);
     254                 :           8 :     s->free_string_on_destroy = 1;
     255                 :             : 
     256                 :           8 :     return s;
     257                 :             : }
     258                 :             : 
     259                 :             : 
     260                 :             : static void
     261                 :           8 : DestroyMetaString(metastring *s)
     262                 :             : {
     263         [ -  + ]:           8 :     if (s == NULL)
     264                 :           0 :         return;
     265                 :             : 
     266         [ +  + ]:           8 :     if (s->free_string_on_destroy && (s->str != NULL))
     267                 :             :         META_FREE(s->str);
     268                 :             : 
     269                 :             :     META_FREE(s);
     270                 :             : }
     271                 :             : 
     272                 :             : 
     273                 :             : static void
     274                 :           0 : IncreaseBuffer(metastring *s, int chars_needed)
     275                 :             : {
     276                 :           0 :     META_REALLOC(s->str, (s->bufsize + chars_needed + 10), char);
     277                 :             :     assert(s->str != NULL);
     278                 :           0 :     s->bufsize = s->bufsize + chars_needed + 10;
     279                 :           0 : }
     280                 :             : 
     281                 :             : 
     282                 :             : static metastring *
     283                 :           2 : MakeUpper(metastring *s, Oid collid)
     284                 :             : {
     285                 :             :     char       *newstr;
     286                 :             :     metastring *newms;
     287                 :             : 
     288                 :           2 :     newstr = str_toupper(s->str, s->length, collid);
     289                 :           2 :     newms = NewMetaString(newstr);
     290                 :           2 :     DestroyMetaString(s);
     291                 :             : 
     292                 :           2 :     return newms;
     293                 :             : }
     294                 :             : 
     295                 :             : 
     296                 :             : static int
     297                 :           0 : IsVowel(metastring *s, int pos)
     298                 :             : {
     299                 :             :     char        c;
     300                 :             : 
     301   [ #  #  #  # ]:           0 :     if ((pos < 0) || (pos >= s->length))
     302                 :           0 :         return 0;
     303                 :             : 
     304                 :           0 :     c = *(s->str + pos);
     305   [ #  #  #  #  :           0 :     if ((c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') ||
          #  #  #  #  #  
                      # ]
     306         [ #  # ]:           0 :         (c == 'U') || (c == 'Y'))
     307                 :           0 :         return 1;
     308                 :             : 
     309                 :           0 :     return 0;
     310                 :             : }
     311                 :             : 
     312                 :             : 
     313                 :             : static int
     314                 :           0 : SlavoGermanic(metastring *s)
     315                 :             : {
     316         [ #  # ]:           0 :     if (strstr(s->str, "W"))
     317                 :           0 :         return 1;
     318         [ #  # ]:           0 :     else if (strstr(s->str, "K"))
     319                 :           0 :         return 1;
     320         [ #  # ]:           0 :     else if (strstr(s->str, "CZ"))
     321                 :           0 :         return 1;
     322         [ #  # ]:           0 :     else if (strstr(s->str, "WITZ"))
     323                 :           0 :         return 1;
     324                 :             :     else
     325                 :           0 :         return 0;
     326                 :             : }
     327                 :             : 
     328                 :             : 
     329                 :             : static char
     330                 :          26 : GetAt(metastring *s, int pos)
     331                 :             : {
     332   [ +  -  -  + ]:          26 :     if ((pos < 0) || (pos >= s->length))
     333                 :           0 :         return '\0';
     334                 :             : 
     335                 :          26 :     return *(s->str + pos);
     336                 :             : }
     337                 :             : 
     338                 :             : 
     339                 :             : static void
     340                 :           0 : SetAt(metastring *s, int pos, char c)
     341                 :             : {
     342   [ #  #  #  # ]:           0 :     if ((pos < 0) || (pos >= s->length))
     343                 :           0 :         return;
     344                 :             : 
     345                 :           0 :     *(s->str + pos) = c;
     346                 :             : }
     347                 :             : 
     348                 :             : 
     349                 :             : /*
     350                 :             :  * Caveats: the START value is 0 based
     351                 :             :  */
     352                 :             : static int
     353                 :          16 : StringAt(metastring *s, int start, int length, ...)
     354                 :             : {
     355                 :             :     char       *test;
     356                 :             :     char       *pos;
     357                 :             :     va_list     ap;
     358                 :             : 
     359   [ +  +  -  + ]:          16 :     if ((start < 0) || (start >= s->length))
     360                 :           2 :         return 0;
     361                 :             : 
     362                 :          14 :     pos = (s->str + start);
     363                 :          14 :     va_start(ap, length);
     364                 :             : 
     365                 :             :     do
     366                 :             :     {
     367                 :          58 :         test = va_arg(ap, char *);
     368   [ +  +  +  + ]:          58 :         if (*test && (strncmp(pos, test, length) == 0))
     369                 :             :         {
     370                 :           2 :             va_end(ap);
     371                 :           2 :             return 1;
     372                 :             :         }
     373                 :             :     }
     374         [ +  + ]:          56 :     while (strcmp(test, "") != 0);
     375                 :             : 
     376                 :          12 :     va_end(ap);
     377                 :             : 
     378                 :          12 :     return 0;
     379                 :             : }
     380                 :             : 
     381                 :             : 
     382                 :             : static void
     383                 :          14 : MetaphAdd(metastring *s, const char *new_str)
     384                 :             : {
     385                 :             :     int         add_length;
     386                 :             : 
     387         [ -  + ]:          14 :     if (new_str == NULL)
     388                 :           0 :         return;
     389                 :             : 
     390                 :          14 :     add_length = strlen(new_str);
     391         [ -  + ]:          14 :     if ((s->length + add_length) > (s->bufsize - 1))
     392                 :           0 :         IncreaseBuffer(s, add_length);
     393                 :             : 
     394                 :          14 :     strcat(s->str, new_str);
     395                 :          14 :     s->length += add_length;
     396                 :             : }
     397                 :             : 
     398                 :             : 
     399                 :             : static void
     400                 :           2 : DoubleMetaphone(const char *str, Oid collid, char **codes)
     401                 :             : {
     402                 :             :     int         length;
     403                 :             :     metastring *original;
     404                 :             :     metastring *primary;
     405                 :             :     metastring *secondary;
     406                 :             :     int         current;
     407                 :             :     int         last;
     408                 :             : 
     409                 :           2 :     current = 0;
     410                 :             :     /* we need the real length and last prior to padding */
     411                 :           2 :     length = strlen(str);
     412                 :           2 :     last = length - 1;
     413                 :           2 :     original = NewMetaString(str);
     414                 :             :     /* Pad original so we can index beyond end */
     415                 :           2 :     MetaphAdd(original, "     ");
     416                 :             : 
     417                 :           2 :     primary = NewMetaString("");
     418                 :           2 :     secondary = NewMetaString("");
     419                 :           2 :     primary->free_string_on_destroy = 0;
     420                 :           2 :     secondary->free_string_on_destroy = 0;
     421                 :             : 
     422                 :           2 :     original = MakeUpper(original, collid);
     423                 :             : 
     424                 :             :     /* skip these when at start of word */
     425         [ -  + ]:           2 :     if (StringAt(original, 0, 2, "GN", "KN", "PN", "WR", "PS", ""))
     426                 :           0 :         current += 1;
     427                 :             : 
     428                 :             :     /* Initial 'X' is pronounced 'Z' e.g. 'Xavier' */
     429         [ -  + ]:           2 :     if (GetAt(original, 0) == 'X')
     430                 :             :     {
     431                 :           0 :         MetaphAdd(primary, "S");  /* 'Z' maps to 'S' */
     432                 :           0 :         MetaphAdd(secondary, "S");
     433                 :           0 :         current += 1;
     434                 :             :     }
     435                 :             : 
     436                 :             :     /* main loop */
     437   [ +  -  -  - ]:          12 :     while ((primary->length < 4) || (secondary->length < 4))
     438                 :             :     {
     439         [ +  + ]:          12 :         if (current >= length)
     440                 :           2 :             break;
     441                 :             : 
     442   [ +  +  -  -  :          10 :         switch (GetAt(original, current))
          -  -  +  -  -  
          -  -  +  -  -  
          -  -  -  -  -  
             -  -  -  -  
                      - ]
     443                 :             :         {
     444                 :           4 :             case 'A':
     445                 :             :             case 'E':
     446                 :             :             case 'I':
     447                 :             :             case 'O':
     448                 :             :             case 'U':
     449                 :             :             case 'Y':
     450         [ -  + ]:           4 :                 if (current == 0)
     451                 :             :                 {
     452                 :             :                     /* all init vowels now map to 'A' */
     453                 :           0 :                     MetaphAdd(primary, "A");
     454                 :           0 :                     MetaphAdd(secondary, "A");
     455                 :             :                 }
     456                 :           4 :                 current += 1;
     457                 :           4 :                 break;
     458                 :             : 
     459                 :           2 :             case 'B':
     460                 :             : 
     461                 :             :                 /* "-mb", e.g", "dumb", already skipped over... */
     462                 :           2 :                 MetaphAdd(primary, "P");
     463                 :           2 :                 MetaphAdd(secondary, "P");
     464                 :             : 
     465         [ -  + ]:           2 :                 if (GetAt(original, current + 1) == 'B')
     466                 :           0 :                     current += 2;
     467                 :             :                 else
     468                 :           2 :                     current += 1;
     469                 :           2 :                 break;
     470                 :             : 
     471                 :           0 :             case '\xc7':        /* C with cedilla */
     472                 :           0 :                 MetaphAdd(primary, "S");
     473                 :           0 :                 MetaphAdd(secondary, "S");
     474                 :           0 :                 current += 1;
     475                 :           0 :                 break;
     476                 :             : 
     477                 :           0 :             case 'C':
     478                 :             :                 /* various germanic */
     479         [ #  # ]:           0 :                 if ((current > 1)
     480         [ #  # ]:           0 :                     && !IsVowel(original, current - 2)
     481         [ #  # ]:           0 :                     && StringAt(original, (current - 1), 3, "ACH", "")
     482         [ #  # ]:           0 :                     && ((GetAt(original, current + 2) != 'I')
     483         [ #  # ]:           0 :                         && ((GetAt(original, current + 2) != 'E')
     484         [ #  # ]:           0 :                             || StringAt(original, (current - 2), 6, "BACHER",
     485                 :             :                                         "MACHER", ""))))
     486                 :             :                 {
     487                 :           0 :                     MetaphAdd(primary, "K");
     488                 :           0 :                     MetaphAdd(secondary, "K");
     489                 :           0 :                     current += 2;
     490                 :           0 :                     break;
     491                 :             :                 }
     492                 :             : 
     493                 :             :                 /* special case 'caesar' */
     494         [ #  # ]:           0 :                 if ((current == 0)
     495         [ #  # ]:           0 :                     && StringAt(original, current, 6, "CAESAR", ""))
     496                 :             :                 {
     497                 :           0 :                     MetaphAdd(primary, "S");
     498                 :           0 :                     MetaphAdd(secondary, "S");
     499                 :           0 :                     current += 2;
     500                 :           0 :                     break;
     501                 :             :                 }
     502                 :             : 
     503                 :             :                 /* italian 'chianti' */
     504         [ #  # ]:           0 :                 if (StringAt(original, current, 4, "CHIA", ""))
     505                 :             :                 {
     506                 :           0 :                     MetaphAdd(primary, "K");
     507                 :           0 :                     MetaphAdd(secondary, "K");
     508                 :           0 :                     current += 2;
     509                 :           0 :                     break;
     510                 :             :                 }
     511                 :             : 
     512         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "CH", ""))
     513                 :             :                 {
     514                 :             :                     /* find 'michael' */
     515         [ #  # ]:           0 :                     if ((current > 0)
     516         [ #  # ]:           0 :                         && StringAt(original, current, 4, "CHAE", ""))
     517                 :             :                     {
     518                 :           0 :                         MetaphAdd(primary, "K");
     519                 :           0 :                         MetaphAdd(secondary, "X");
     520                 :           0 :                         current += 2;
     521                 :           0 :                         break;
     522                 :             :                     }
     523                 :             : 
     524                 :             :                     /* greek roots e.g. 'chemistry', 'chorus' */
     525         [ #  # ]:           0 :                     if ((current == 0)
     526         [ #  # ]:           0 :                         && (StringAt(original, (current + 1), 5,
     527                 :             :                                      "HARAC", "HARIS", "")
     528         [ #  # ]:           0 :                             || StringAt(original, (current + 1), 3, "HOR",
     529                 :             :                                         "HYM", "HIA", "HEM", ""))
     530         [ #  # ]:           0 :                         && !StringAt(original, 0, 5, "CHORE", ""))
     531                 :             :                     {
     532                 :           0 :                         MetaphAdd(primary, "K");
     533                 :           0 :                         MetaphAdd(secondary, "K");
     534                 :           0 :                         current += 2;
     535                 :           0 :                         break;
     536                 :             :                     }
     537                 :             : 
     538                 :             :                     /* germanic, greek, or otherwise 'ch' for 'kh' sound */
     539         [ #  # ]:           0 :                     if ((StringAt(original, 0, 4, "VAN ", "VON ", "")
     540         [ #  # ]:           0 :                          || StringAt(original, 0, 3, "SCH", ""))
     541                 :             :                     /* 'architect but not 'arch', 'orchestra', 'orchid' */
     542         [ #  # ]:           0 :                         || StringAt(original, (current - 2), 6, "ORCHES",
     543                 :             :                                     "ARCHIT", "ORCHID", "")
     544         [ #  # ]:           0 :                         || StringAt(original, (current + 2), 1, "T", "S",
     545                 :             :                                     "")
     546         [ #  # ]:           0 :                         || ((StringAt(original, (current - 1), 1,
     547                 :             :                                       "A", "O", "U", "E", "")
     548         [ #  # ]:           0 :                              || (current == 0))
     549                 :             : 
     550                 :             :                     /*
     551                 :             :                      * e.g., 'wachtler', 'wechsler', but not 'tichner'
     552                 :             :                      */
     553         [ #  # ]:           0 :                             && StringAt(original, (current + 2), 1, "L", "R",
     554                 :             :                                         "N", "M", "B", "H", "F", "V", "W",
     555                 :             :                                         " ", "")))
     556                 :             :                     {
     557                 :           0 :                         MetaphAdd(primary, "K");
     558                 :           0 :                         MetaphAdd(secondary, "K");
     559                 :             :                     }
     560                 :             :                     else
     561                 :             :                     {
     562         [ #  # ]:           0 :                         if (current > 0)
     563                 :             :                         {
     564         [ #  # ]:           0 :                             if (StringAt(original, 0, 2, "MC", ""))
     565                 :             :                             {
     566                 :             :                                 /* e.g., "McHugh" */
     567                 :           0 :                                 MetaphAdd(primary, "K");
     568                 :           0 :                                 MetaphAdd(secondary, "K");
     569                 :             :                             }
     570                 :             :                             else
     571                 :             :                             {
     572                 :           0 :                                 MetaphAdd(primary, "X");
     573                 :           0 :                                 MetaphAdd(secondary, "K");
     574                 :             :                             }
     575                 :             :                         }
     576                 :             :                         else
     577                 :             :                         {
     578                 :           0 :                             MetaphAdd(primary, "X");
     579                 :           0 :                             MetaphAdd(secondary, "X");
     580                 :             :                         }
     581                 :             :                     }
     582                 :           0 :                     current += 2;
     583                 :           0 :                     break;
     584                 :             :                 }
     585                 :             :                 /* e.g, 'czerny' */
     586         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "CZ", "")
     587         [ #  # ]:           0 :                     && !StringAt(original, (current - 2), 4, "WICZ", ""))
     588                 :             :                 {
     589                 :           0 :                     MetaphAdd(primary, "S");
     590                 :           0 :                     MetaphAdd(secondary, "X");
     591                 :           0 :                     current += 2;
     592                 :           0 :                     break;
     593                 :             :                 }
     594                 :             : 
     595                 :             :                 /* e.g., 'focaccia' */
     596         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 3, "CIA", ""))
     597                 :             :                 {
     598                 :           0 :                     MetaphAdd(primary, "X");
     599                 :           0 :                     MetaphAdd(secondary, "X");
     600                 :           0 :                     current += 3;
     601                 :           0 :                     break;
     602                 :             :                 }
     603                 :             : 
     604                 :             :                 /* double 'C', but not if e.g. 'McClellan' */
     605         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "CC", "")
     606   [ #  #  #  # ]:           0 :                     && !((current == 1) && (GetAt(original, 0) == 'M')))
     607                 :             :                 {
     608                 :             :                     /* 'bellocchio' but not 'bacchus' */
     609         [ #  # ]:           0 :                     if (StringAt(original, (current + 2), 1, "I", "E", "H", "")
     610         [ #  # ]:           0 :                         && !StringAt(original, (current + 2), 2, "HU", ""))
     611                 :             :                     {
     612                 :             :                         /* 'accident', 'accede' 'succeed' */
     613         [ #  # ]:           0 :                         if (((current == 1)
     614         [ #  # ]:           0 :                              && (GetAt(original, current - 1) == 'A'))
     615         [ #  # ]:           0 :                             || StringAt(original, (current - 1), 5, "UCCEE",
     616                 :             :                                         "UCCES", ""))
     617                 :             :                         {
     618                 :           0 :                             MetaphAdd(primary, "KS");
     619                 :           0 :                             MetaphAdd(secondary, "KS");
     620                 :             :                             /* 'bacci', 'bertucci', other italian */
     621                 :             :                         }
     622                 :             :                         else
     623                 :             :                         {
     624                 :           0 :                             MetaphAdd(primary, "X");
     625                 :           0 :                             MetaphAdd(secondary, "X");
     626                 :             :                         }
     627                 :           0 :                         current += 3;
     628                 :           0 :                         break;
     629                 :             :                     }
     630                 :             :                     else
     631                 :             :                     {           /* Pierce's rule */
     632                 :           0 :                         MetaphAdd(primary, "K");
     633                 :           0 :                         MetaphAdd(secondary, "K");
     634                 :           0 :                         current += 2;
     635                 :           0 :                         break;
     636                 :             :                     }
     637                 :             :                 }
     638                 :             : 
     639         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "CK", "CG", "CQ", ""))
     640                 :             :                 {
     641                 :           0 :                     MetaphAdd(primary, "K");
     642                 :           0 :                     MetaphAdd(secondary, "K");
     643                 :           0 :                     current += 2;
     644                 :           0 :                     break;
     645                 :             :                 }
     646                 :             : 
     647         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "CI", "CE", "CY", ""))
     648                 :             :                 {
     649                 :             :                     /* italian vs. english */
     650         [ #  # ]:           0 :                     if (StringAt
     651                 :             :                         (original, current, 3, "CIO", "CIE", "CIA", ""))
     652                 :             :                     {
     653                 :           0 :                         MetaphAdd(primary, "S");
     654                 :           0 :                         MetaphAdd(secondary, "X");
     655                 :             :                     }
     656                 :             :                     else
     657                 :             :                     {
     658                 :           0 :                         MetaphAdd(primary, "S");
     659                 :           0 :                         MetaphAdd(secondary, "S");
     660                 :             :                     }
     661                 :           0 :                     current += 2;
     662                 :           0 :                     break;
     663                 :             :                 }
     664                 :             : 
     665                 :             :                 /* else */
     666                 :           0 :                 MetaphAdd(primary, "K");
     667                 :           0 :                 MetaphAdd(secondary, "K");
     668                 :             : 
     669                 :             :                 /* name sent in 'mac caffrey', 'mac gregor */
     670         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 2, " C", " Q", " G", ""))
     671                 :           0 :                     current += 3;
     672         [ #  # ]:           0 :                 else if (StringAt(original, (current + 1), 1, "C", "K", "Q", "")
     673         [ #  # ]:           0 :                          && !StringAt(original, (current + 1), 2,
     674                 :             :                                       "CE", "CI", ""))
     675                 :           0 :                     current += 2;
     676                 :             :                 else
     677                 :           0 :                     current += 1;
     678                 :           0 :                 break;
     679                 :             : 
     680                 :           0 :             case 'D':
     681         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "DG", ""))
     682                 :             :                 {
     683         [ #  # ]:           0 :                     if (StringAt(original, (current + 2), 1,
     684                 :             :                                  "I", "E", "Y", ""))
     685                 :             :                     {
     686                 :             :                         /* e.g. 'edge' */
     687                 :           0 :                         MetaphAdd(primary, "J");
     688                 :           0 :                         MetaphAdd(secondary, "J");
     689                 :           0 :                         current += 3;
     690                 :           0 :                         break;
     691                 :             :                     }
     692                 :             :                     else
     693                 :             :                     {
     694                 :             :                         /* e.g. 'edgar' */
     695                 :           0 :                         MetaphAdd(primary, "TK");
     696                 :           0 :                         MetaphAdd(secondary, "TK");
     697                 :           0 :                         current += 2;
     698                 :           0 :                         break;
     699                 :             :                     }
     700                 :             :                 }
     701                 :             : 
     702         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "DT", "DD", ""))
     703                 :             :                 {
     704                 :           0 :                     MetaphAdd(primary, "T");
     705                 :           0 :                     MetaphAdd(secondary, "T");
     706                 :           0 :                     current += 2;
     707                 :           0 :                     break;
     708                 :             :                 }
     709                 :             : 
     710                 :             :                 /* else */
     711                 :           0 :                 MetaphAdd(primary, "T");
     712                 :           0 :                 MetaphAdd(secondary, "T");
     713                 :           0 :                 current += 1;
     714                 :           0 :                 break;
     715                 :             : 
     716                 :           0 :             case 'F':
     717         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'F')
     718                 :           0 :                     current += 2;
     719                 :             :                 else
     720                 :           0 :                     current += 1;
     721                 :           0 :                 MetaphAdd(primary, "F");
     722                 :           0 :                 MetaphAdd(secondary, "F");
     723                 :           0 :                 break;
     724                 :             : 
     725                 :           2 :             case 'G':
     726         [ -  + ]:           2 :                 if (GetAt(original, current + 1) == 'H')
     727                 :             :                 {
     728   [ #  #  #  # ]:           0 :                     if ((current > 0) && !IsVowel(original, current - 1))
     729                 :             :                     {
     730                 :           0 :                         MetaphAdd(primary, "K");
     731                 :           0 :                         MetaphAdd(secondary, "K");
     732                 :           0 :                         current += 2;
     733                 :           0 :                         break;
     734                 :             :                     }
     735                 :             : 
     736         [ #  # ]:           0 :                     if (current < 3)
     737                 :             :                     {
     738                 :             :                         /* 'ghislane', ghiradelli */
     739         [ #  # ]:           0 :                         if (current == 0)
     740                 :             :                         {
     741         [ #  # ]:           0 :                             if (GetAt(original, current + 2) == 'I')
     742                 :             :                             {
     743                 :           0 :                                 MetaphAdd(primary, "J");
     744                 :           0 :                                 MetaphAdd(secondary, "J");
     745                 :             :                             }
     746                 :             :                             else
     747                 :             :                             {
     748                 :           0 :                                 MetaphAdd(primary, "K");
     749                 :           0 :                                 MetaphAdd(secondary, "K");
     750                 :             :                             }
     751                 :           0 :                             current += 2;
     752                 :           0 :                             break;
     753                 :             :                         }
     754                 :             :                     }
     755                 :             : 
     756                 :             :                     /*
     757                 :             :                      * Parker's rule (with some further refinements) - e.g.,
     758                 :             :                      * 'hugh'
     759                 :             :                      */
     760         [ #  # ]:           0 :                     if (((current > 1)
     761         [ #  # ]:           0 :                          && StringAt(original, (current - 2), 1,
     762                 :             :                                      "B", "H", "D", ""))
     763                 :             :                     /* e.g., 'bough' */
     764         [ #  # ]:           0 :                         || ((current > 2)
     765         [ #  # ]:           0 :                             && StringAt(original, (current - 3), 1,
     766                 :             :                                         "B", "H", "D", ""))
     767                 :             :                     /* e.g., 'broughton' */
     768         [ #  # ]:           0 :                         || ((current > 3)
     769         [ #  # ]:           0 :                             && StringAt(original, (current - 4), 1,
     770                 :             :                                         "B", "H", "")))
     771                 :             :                     {
     772                 :           0 :                         current += 2;
     773                 :           0 :                         break;
     774                 :             :                     }
     775                 :             :                     else
     776                 :             :                     {
     777                 :             :                         /*
     778                 :             :                          * e.g., 'laugh', 'McLaughlin', 'cough', 'gough',
     779                 :             :                          * 'rough', 'tough'
     780                 :             :                          */
     781         [ #  # ]:           0 :                         if ((current > 2)
     782         [ #  # ]:           0 :                             && (GetAt(original, current - 1) == 'U')
     783         [ #  # ]:           0 :                             && StringAt(original, (current - 3), 1, "C",
     784                 :             :                                         "G", "L", "R", "T", ""))
     785                 :             :                         {
     786                 :           0 :                             MetaphAdd(primary, "F");
     787                 :           0 :                             MetaphAdd(secondary, "F");
     788                 :             :                         }
     789         [ #  # ]:           0 :                         else if ((current > 0)
     790         [ #  # ]:           0 :                                  && GetAt(original, current - 1) != 'I')
     791                 :             :                         {
     792                 :             : 
     793                 :             : 
     794                 :           0 :                             MetaphAdd(primary, "K");
     795                 :           0 :                             MetaphAdd(secondary, "K");
     796                 :             :                         }
     797                 :             : 
     798                 :           0 :                         current += 2;
     799                 :           0 :                         break;
     800                 :             :                     }
     801                 :             :                 }
     802                 :             : 
     803         [ -  + ]:           2 :                 if (GetAt(original, current + 1) == 'N')
     804                 :             :                 {
     805   [ #  #  #  # ]:           0 :                     if ((current == 1) && IsVowel(original, 0)
     806         [ #  # ]:           0 :                         && !SlavoGermanic(original))
     807                 :             :                     {
     808                 :           0 :                         MetaphAdd(primary, "KN");
     809                 :           0 :                         MetaphAdd(secondary, "N");
     810                 :             :                     }
     811                 :             :                     /* not e.g. 'cagney' */
     812         [ #  # ]:           0 :                     else if (!StringAt(original, (current + 2), 2, "EY", "")
     813         [ #  # ]:           0 :                              && (GetAt(original, current + 1) != 'Y')
     814         [ #  # ]:           0 :                              && !SlavoGermanic(original))
     815                 :             :                     {
     816                 :           0 :                         MetaphAdd(primary, "N");
     817                 :           0 :                         MetaphAdd(secondary, "KN");
     818                 :             :                     }
     819                 :             :                     else
     820                 :             :                     {
     821                 :           0 :                         MetaphAdd(primary, "KN");
     822                 :           0 :                         MetaphAdd(secondary, "KN");
     823                 :             :                     }
     824                 :           0 :                     current += 2;
     825                 :           0 :                     break;
     826                 :             :                 }
     827                 :             : 
     828                 :             :                 /* 'tagliaro' */
     829         [ -  + ]:           2 :                 if (StringAt(original, (current + 1), 2, "LI", "")
     830         [ #  # ]:           0 :                     && !SlavoGermanic(original))
     831                 :             :                 {
     832                 :           0 :                     MetaphAdd(primary, "KL");
     833                 :           0 :                     MetaphAdd(secondary, "L");
     834                 :           0 :                     current += 2;
     835                 :           0 :                     break;
     836                 :             :                 }
     837                 :             : 
     838                 :             :                 /* -ges-,-gep-,-gel-, -gie- at beginning */
     839         [ +  - ]:           2 :                 if ((current == 0)
     840         [ +  - ]:           2 :                     && ((GetAt(original, current + 1) == 'Y')
     841         [ -  + ]:           2 :                         || StringAt(original, (current + 1), 2, "ES", "EP",
     842                 :             :                                     "EB", "EL", "EY", "IB", "IL", "IN", "IE",
     843                 :             :                                     "EI", "ER", "")))
     844                 :             :                 {
     845                 :           0 :                     MetaphAdd(primary, "K");
     846                 :           0 :                     MetaphAdd(secondary, "J");
     847                 :           0 :                     current += 2;
     848                 :           0 :                     break;
     849                 :             :                 }
     850                 :             : 
     851                 :             :                 /* -ger-,  -gy- */
     852         [ +  - ]:           2 :                 if ((StringAt(original, (current + 1), 2, "ER", "")
     853         [ -  + ]:           2 :                      || (GetAt(original, current + 1) == 'Y'))
     854         [ #  # ]:           0 :                     && !StringAt(original, 0, 6,
     855                 :             :                                  "DANGER", "RANGER", "MANGER", "")
     856         [ #  # ]:           0 :                     && !StringAt(original, (current - 1), 1, "E", "I", "")
     857         [ #  # ]:           0 :                     && !StringAt(original, (current - 1), 3, "RGY", "OGY", ""))
     858                 :             :                 {
     859                 :           0 :                     MetaphAdd(primary, "K");
     860                 :           0 :                     MetaphAdd(secondary, "J");
     861                 :           0 :                     current += 2;
     862                 :           0 :                     break;
     863                 :             :                 }
     864                 :             : 
     865                 :             :                 /* italian e.g, 'biaggi' */
     866         [ +  - ]:           2 :                 if (StringAt(original, (current + 1), 1, "E", "I", "Y", "")
     867         [ -  + ]:           2 :                     || StringAt(original, (current - 1), 4,
     868                 :             :                                 "AGGI", "OGGI", ""))
     869                 :             :                 {
     870                 :             :                     /* obvious germanic */
     871         [ #  # ]:           0 :                     if ((StringAt(original, 0, 4, "VAN ", "VON ", "")
     872         [ #  # ]:           0 :                          || StringAt(original, 0, 3, "SCH", ""))
     873         [ #  # ]:           0 :                         || StringAt(original, (current + 1), 2, "ET", ""))
     874                 :             :                     {
     875                 :           0 :                         MetaphAdd(primary, "K");
     876                 :           0 :                         MetaphAdd(secondary, "K");
     877                 :             :                     }
     878                 :             :                     else
     879                 :             :                     {
     880                 :             :                         /* always soft if french ending */
     881         [ #  # ]:           0 :                         if (StringAt
     882                 :             :                             (original, (current + 1), 4, "IER ", ""))
     883                 :             :                         {
     884                 :           0 :                             MetaphAdd(primary, "J");
     885                 :           0 :                             MetaphAdd(secondary, "J");
     886                 :             :                         }
     887                 :             :                         else
     888                 :             :                         {
     889                 :           0 :                             MetaphAdd(primary, "J");
     890                 :           0 :                             MetaphAdd(secondary, "K");
     891                 :             :                         }
     892                 :             :                     }
     893                 :           0 :                     current += 2;
     894                 :           0 :                     break;
     895                 :             :                 }
     896                 :             : 
     897         [ -  + ]:           2 :                 if (GetAt(original, current + 1) == 'G')
     898                 :           0 :                     current += 2;
     899                 :             :                 else
     900                 :           2 :                     current += 1;
     901                 :           2 :                 MetaphAdd(primary, "K");
     902                 :           2 :                 MetaphAdd(secondary, "K");
     903                 :           2 :                 break;
     904                 :             : 
     905                 :           0 :             case 'H':
     906                 :             :                 /* only keep if first & before vowel or btw. 2 vowels */
     907   [ #  #  #  # ]:           0 :                 if (((current == 0) || IsVowel(original, current - 1))
     908         [ #  # ]:           0 :                     && IsVowel(original, current + 1))
     909                 :             :                 {
     910                 :           0 :                     MetaphAdd(primary, "H");
     911                 :           0 :                     MetaphAdd(secondary, "H");
     912                 :           0 :                     current += 2;
     913                 :             :                 }
     914                 :             :                 else
     915                 :             :                     /* also takes care of 'HH' */
     916                 :           0 :                     current += 1;
     917                 :           0 :                 break;
     918                 :             : 
     919                 :           0 :             case 'J':
     920                 :             :                 /* obvious spanish, 'jose', 'san jacinto' */
     921         [ #  # ]:           0 :                 if (StringAt(original, current, 4, "JOSE", "")
     922         [ #  # ]:           0 :                     || StringAt(original, 0, 4, "SAN ", ""))
     923                 :             :                 {
     924         [ #  # ]:           0 :                     if (((current == 0)
     925         [ #  # ]:           0 :                          && (GetAt(original, current + 4) == ' '))
     926         [ #  # ]:           0 :                         || StringAt(original, 0, 4, "SAN ", ""))
     927                 :             :                     {
     928                 :           0 :                         MetaphAdd(primary, "H");
     929                 :           0 :                         MetaphAdd(secondary, "H");
     930                 :             :                     }
     931                 :             :                     else
     932                 :             :                     {
     933                 :           0 :                         MetaphAdd(primary, "J");
     934                 :           0 :                         MetaphAdd(secondary, "H");
     935                 :             :                     }
     936                 :           0 :                     current += 1;
     937                 :           0 :                     break;
     938                 :             :                 }
     939                 :             : 
     940         [ #  # ]:           0 :                 if ((current == 0)
     941         [ #  # ]:           0 :                     && !StringAt(original, current, 4, "JOSE", ""))
     942                 :             :                 {
     943                 :           0 :                     MetaphAdd(primary, "J");  /* Yankelovich/Jankelowicz */
     944                 :           0 :                     MetaphAdd(secondary, "A");
     945                 :             :                 }
     946                 :             :                 else
     947                 :             :                 {
     948                 :             :                     /* spanish pron. of e.g. 'bajador' */
     949         [ #  # ]:           0 :                     if (IsVowel(original, current - 1)
     950         [ #  # ]:           0 :                         && !SlavoGermanic(original)
     951         [ #  # ]:           0 :                         && ((GetAt(original, current + 1) == 'A')
     952         [ #  # ]:           0 :                             || (GetAt(original, current + 1) == 'O')))
     953                 :             :                     {
     954                 :           0 :                         MetaphAdd(primary, "J");
     955                 :           0 :                         MetaphAdd(secondary, "H");
     956                 :             :                     }
     957                 :             :                     else
     958                 :             :                     {
     959         [ #  # ]:           0 :                         if (current == last)
     960                 :             :                         {
     961                 :           0 :                             MetaphAdd(primary, "J");
     962                 :           0 :                             MetaphAdd(secondary, "");
     963                 :             :                         }
     964                 :             :                         else
     965                 :             :                         {
     966         [ #  # ]:           0 :                             if (!StringAt(original, (current + 1), 1, "L", "T",
     967                 :             :                                           "K", "S", "N", "M", "B", "Z", "")
     968         [ #  # ]:           0 :                                 && !StringAt(original, (current - 1), 1,
     969                 :             :                                              "S", "K", "L", ""))
     970                 :             :                             {
     971                 :           0 :                                 MetaphAdd(primary, "J");
     972                 :           0 :                                 MetaphAdd(secondary, "J");
     973                 :             :                             }
     974                 :             :                         }
     975                 :             :                     }
     976                 :             :                 }
     977                 :             : 
     978         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'J')    /* it could happen! */
     979                 :           0 :                     current += 2;
     980                 :             :                 else
     981                 :           0 :                     current += 1;
     982                 :           0 :                 break;
     983                 :             : 
     984                 :           0 :             case 'K':
     985         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'K')
     986                 :           0 :                     current += 2;
     987                 :             :                 else
     988                 :           0 :                     current += 1;
     989                 :           0 :                 MetaphAdd(primary, "K");
     990                 :           0 :                 MetaphAdd(secondary, "K");
     991                 :           0 :                 break;
     992                 :             : 
     993                 :           0 :             case 'L':
     994         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'L')
     995                 :             :                 {
     996                 :             :                     /* spanish e.g. 'cabrillo', 'gallegos' */
     997         [ #  # ]:           0 :                     if (((current == (length - 3))
     998         [ #  # ]:           0 :                          && StringAt(original, (current - 1), 4, "ILLO",
     999                 :             :                                      "ILLA", "ALLE", ""))
    1000         [ #  # ]:           0 :                         || ((StringAt(original, (last - 1), 2, "AS", "OS", "")
    1001         [ #  # ]:           0 :                              || StringAt(original, last, 1, "A", "O", ""))
    1002         [ #  # ]:           0 :                             && StringAt(original, (current - 1), 4,
    1003                 :             :                                         "ALLE", "")))
    1004                 :             :                     {
    1005                 :           0 :                         MetaphAdd(primary, "L");
    1006                 :           0 :                         MetaphAdd(secondary, "");
    1007                 :           0 :                         current += 2;
    1008                 :           0 :                         break;
    1009                 :             :                     }
    1010                 :           0 :                     current += 2;
    1011                 :             :                 }
    1012                 :             :                 else
    1013                 :           0 :                     current += 1;
    1014                 :           0 :                 MetaphAdd(primary, "L");
    1015                 :           0 :                 MetaphAdd(secondary, "L");
    1016                 :           0 :                 break;
    1017                 :             : 
    1018                 :           2 :             case 'M':
    1019         [ +  - ]:           2 :                 if ((StringAt(original, (current - 1), 3, "UMB", "")
    1020         [ +  - ]:           2 :                      && (((current + 1) == last)
    1021         [ +  - ]:           2 :                          || StringAt(original, (current + 2), 2, "ER", "")))
    1022                 :             :                 /* 'dumb','thumb' */
    1023         [ -  + ]:           2 :                     || (GetAt(original, current + 1) == 'M'))
    1024                 :           0 :                     current += 2;
    1025                 :             :                 else
    1026                 :           2 :                     current += 1;
    1027                 :           2 :                 MetaphAdd(primary, "M");
    1028                 :           2 :                 MetaphAdd(secondary, "M");
    1029                 :           2 :                 break;
    1030                 :             : 
    1031                 :           0 :             case 'N':
    1032         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'N')
    1033                 :           0 :                     current += 2;
    1034                 :             :                 else
    1035                 :           0 :                     current += 1;
    1036                 :           0 :                 MetaphAdd(primary, "N");
    1037                 :           0 :                 MetaphAdd(secondary, "N");
    1038                 :           0 :                 break;
    1039                 :             : 
    1040                 :           0 :             case '\xd1':        /* N with tilde */
    1041                 :           0 :                 current += 1;
    1042                 :           0 :                 MetaphAdd(primary, "N");
    1043                 :           0 :                 MetaphAdd(secondary, "N");
    1044                 :           0 :                 break;
    1045                 :             : 
    1046                 :           0 :             case 'P':
    1047         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'H')
    1048                 :             :                 {
    1049                 :           0 :                     MetaphAdd(primary, "F");
    1050                 :           0 :                     MetaphAdd(secondary, "F");
    1051                 :           0 :                     current += 2;
    1052                 :           0 :                     break;
    1053                 :             :                 }
    1054                 :             : 
    1055                 :             :                 /* also account for "campbell", "raspberry" */
    1056         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 1, "P", "B", ""))
    1057                 :           0 :                     current += 2;
    1058                 :             :                 else
    1059                 :           0 :                     current += 1;
    1060                 :           0 :                 MetaphAdd(primary, "P");
    1061                 :           0 :                 MetaphAdd(secondary, "P");
    1062                 :           0 :                 break;
    1063                 :             : 
    1064                 :           0 :             case 'Q':
    1065         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'Q')
    1066                 :           0 :                     current += 2;
    1067                 :             :                 else
    1068                 :           0 :                     current += 1;
    1069                 :           0 :                 MetaphAdd(primary, "K");
    1070                 :           0 :                 MetaphAdd(secondary, "K");
    1071                 :           0 :                 break;
    1072                 :             : 
    1073                 :           0 :             case 'R':
    1074                 :             :                 /* french e.g. 'rogier', but exclude 'hochmeier' */
    1075         [ #  # ]:           0 :                 if ((current == last)
    1076         [ #  # ]:           0 :                     && !SlavoGermanic(original)
    1077         [ #  # ]:           0 :                     && StringAt(original, (current - 2), 2, "IE", "")
    1078         [ #  # ]:           0 :                     && !StringAt(original, (current - 4), 2, "ME", "MA", ""))
    1079                 :             :                 {
    1080                 :           0 :                     MetaphAdd(primary, "");
    1081                 :           0 :                     MetaphAdd(secondary, "R");
    1082                 :             :                 }
    1083                 :             :                 else
    1084                 :             :                 {
    1085                 :           0 :                     MetaphAdd(primary, "R");
    1086                 :           0 :                     MetaphAdd(secondary, "R");
    1087                 :             :                 }
    1088                 :             : 
    1089         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'R')
    1090                 :           0 :                     current += 2;
    1091                 :             :                 else
    1092                 :           0 :                     current += 1;
    1093                 :           0 :                 break;
    1094                 :             : 
    1095                 :           0 :             case 'S':
    1096                 :             :                 /* special cases 'island', 'isle', 'carlisle', 'carlysle' */
    1097         [ #  # ]:           0 :                 if (StringAt(original, (current - 1), 3, "ISL", "YSL", ""))
    1098                 :             :                 {
    1099                 :           0 :                     current += 1;
    1100                 :           0 :                     break;
    1101                 :             :                 }
    1102                 :             : 
    1103                 :             :                 /* special case 'sugar-' */
    1104         [ #  # ]:           0 :                 if ((current == 0)
    1105         [ #  # ]:           0 :                     && StringAt(original, current, 5, "SUGAR", ""))
    1106                 :             :                 {
    1107                 :           0 :                     MetaphAdd(primary, "X");
    1108                 :           0 :                     MetaphAdd(secondary, "S");
    1109                 :           0 :                     current += 1;
    1110                 :           0 :                     break;
    1111                 :             :                 }
    1112                 :             : 
    1113         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "SH", ""))
    1114                 :             :                 {
    1115                 :             :                     /* germanic */
    1116         [ #  # ]:           0 :                     if (StringAt
    1117                 :             :                         (original, (current + 1), 4, "HEIM", "HOEK", "HOLM",
    1118                 :             :                          "HOLZ", ""))
    1119                 :             :                     {
    1120                 :           0 :                         MetaphAdd(primary, "S");
    1121                 :           0 :                         MetaphAdd(secondary, "S");
    1122                 :             :                     }
    1123                 :             :                     else
    1124                 :             :                     {
    1125                 :           0 :                         MetaphAdd(primary, "X");
    1126                 :           0 :                         MetaphAdd(secondary, "X");
    1127                 :             :                     }
    1128                 :           0 :                     current += 2;
    1129                 :           0 :                     break;
    1130                 :             :                 }
    1131                 :             : 
    1132                 :             :                 /* italian & armenian */
    1133         [ #  # ]:           0 :                 if (StringAt(original, current, 3, "SIO", "SIA", "")
    1134         [ #  # ]:           0 :                     || StringAt(original, current, 4, "SIAN", ""))
    1135                 :             :                 {
    1136         [ #  # ]:           0 :                     if (!SlavoGermanic(original))
    1137                 :             :                     {
    1138                 :           0 :                         MetaphAdd(primary, "S");
    1139                 :           0 :                         MetaphAdd(secondary, "X");
    1140                 :             :                     }
    1141                 :             :                     else
    1142                 :             :                     {
    1143                 :           0 :                         MetaphAdd(primary, "S");
    1144                 :           0 :                         MetaphAdd(secondary, "S");
    1145                 :             :                     }
    1146                 :           0 :                     current += 3;
    1147                 :           0 :                     break;
    1148                 :             :                 }
    1149                 :             : 
    1150                 :             :                 /*
    1151                 :             :                  * german & anglicisations, e.g. 'smith' match 'schmidt',
    1152                 :             :                  * 'snider' match 'schneider' also, -sz- in slavic language
    1153                 :             :                  * although in hungarian it is pronounced 's'
    1154                 :             :                  */
    1155         [ #  # ]:           0 :                 if (((current == 0)
    1156         [ #  # ]:           0 :                      && StringAt(original, (current + 1), 1,
    1157                 :             :                                  "M", "N", "L", "W", ""))
    1158         [ #  # ]:           0 :                     || StringAt(original, (current + 1), 1, "Z", ""))
    1159                 :             :                 {
    1160                 :           0 :                     MetaphAdd(primary, "S");
    1161                 :           0 :                     MetaphAdd(secondary, "X");
    1162         [ #  # ]:           0 :                     if (StringAt(original, (current + 1), 1, "Z", ""))
    1163                 :           0 :                         current += 2;
    1164                 :             :                     else
    1165                 :           0 :                         current += 1;
    1166                 :           0 :                     break;
    1167                 :             :                 }
    1168                 :             : 
    1169         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "SC", ""))
    1170                 :             :                 {
    1171                 :             :                     /* Schlesinger's rule */
    1172         [ #  # ]:           0 :                     if (GetAt(original, current + 2) == 'H')
    1173                 :             :                     {
    1174                 :             :                         /* dutch origin, e.g. 'school', 'schooner' */
    1175         [ #  # ]:           0 :                         if (StringAt(original, (current + 3), 2,
    1176                 :             :                                      "OO", "ER", "EN",
    1177                 :             :                                      "UY", "ED", "EM", ""))
    1178                 :             :                         {
    1179                 :             :                             /* 'schermerhorn', 'schenker' */
    1180         [ #  # ]:           0 :                             if (StringAt(original, (current + 3), 2,
    1181                 :             :                                          "ER", "EN", ""))
    1182                 :             :                             {
    1183                 :           0 :                                 MetaphAdd(primary, "X");
    1184                 :           0 :                                 MetaphAdd(secondary, "SK");
    1185                 :             :                             }
    1186                 :             :                             else
    1187                 :             :                             {
    1188                 :           0 :                                 MetaphAdd(primary, "SK");
    1189                 :           0 :                                 MetaphAdd(secondary, "SK");
    1190                 :             :                             }
    1191                 :           0 :                             current += 3;
    1192                 :           0 :                             break;
    1193                 :             :                         }
    1194                 :             :                         else
    1195                 :             :                         {
    1196   [ #  #  #  # ]:           0 :                             if ((current == 0) && !IsVowel(original, 3)
    1197         [ #  # ]:           0 :                                 && (GetAt(original, 3) != 'W'))
    1198                 :             :                             {
    1199                 :           0 :                                 MetaphAdd(primary, "X");
    1200                 :           0 :                                 MetaphAdd(secondary, "S");
    1201                 :             :                             }
    1202                 :             :                             else
    1203                 :             :                             {
    1204                 :           0 :                                 MetaphAdd(primary, "X");
    1205                 :           0 :                                 MetaphAdd(secondary, "X");
    1206                 :             :                             }
    1207                 :           0 :                             current += 3;
    1208                 :           0 :                             break;
    1209                 :             :                         }
    1210                 :             :                     }
    1211                 :             : 
    1212         [ #  # ]:           0 :                     if (StringAt(original, (current + 2), 1,
    1213                 :             :                                  "I", "E", "Y", ""))
    1214                 :             :                     {
    1215                 :           0 :                         MetaphAdd(primary, "S");
    1216                 :           0 :                         MetaphAdd(secondary, "S");
    1217                 :           0 :                         current += 3;
    1218                 :           0 :                         break;
    1219                 :             :                     }
    1220                 :             :                     /* else */
    1221                 :           0 :                     MetaphAdd(primary, "SK");
    1222                 :           0 :                     MetaphAdd(secondary, "SK");
    1223                 :           0 :                     current += 3;
    1224                 :           0 :                     break;
    1225                 :             :                 }
    1226                 :             : 
    1227                 :             :                 /* french e.g. 'resnais', 'artois' */
    1228         [ #  # ]:           0 :                 if ((current == last)
    1229         [ #  # ]:           0 :                     && StringAt(original, (current - 2), 2, "AI", "OI", ""))
    1230                 :             :                 {
    1231                 :           0 :                     MetaphAdd(primary, "");
    1232                 :           0 :                     MetaphAdd(secondary, "S");
    1233                 :             :                 }
    1234                 :             :                 else
    1235                 :             :                 {
    1236                 :           0 :                     MetaphAdd(primary, "S");
    1237                 :           0 :                     MetaphAdd(secondary, "S");
    1238                 :             :                 }
    1239                 :             : 
    1240         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 1, "S", "Z", ""))
    1241                 :           0 :                     current += 2;
    1242                 :             :                 else
    1243                 :           0 :                     current += 1;
    1244                 :           0 :                 break;
    1245                 :             : 
    1246                 :           0 :             case 'T':
    1247         [ #  # ]:           0 :                 if (StringAt(original, current, 4, "TION", ""))
    1248                 :             :                 {
    1249                 :           0 :                     MetaphAdd(primary, "X");
    1250                 :           0 :                     MetaphAdd(secondary, "X");
    1251                 :           0 :                     current += 3;
    1252                 :           0 :                     break;
    1253                 :             :                 }
    1254                 :             : 
    1255         [ #  # ]:           0 :                 if (StringAt(original, current, 3, "TIA", "TCH", ""))
    1256                 :             :                 {
    1257                 :           0 :                     MetaphAdd(primary, "X");
    1258                 :           0 :                     MetaphAdd(secondary, "X");
    1259                 :           0 :                     current += 3;
    1260                 :           0 :                     break;
    1261                 :             :                 }
    1262                 :             : 
    1263         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "TH", "")
    1264         [ #  # ]:           0 :                     || StringAt(original, current, 3, "TTH", ""))
    1265                 :             :                 {
    1266                 :             :                     /* special case 'thomas', 'thames' or germanic */
    1267         [ #  # ]:           0 :                     if (StringAt(original, (current + 2), 2, "OM", "AM", "")
    1268         [ #  # ]:           0 :                         || StringAt(original, 0, 4, "VAN ", "VON ", "")
    1269         [ #  # ]:           0 :                         || StringAt(original, 0, 3, "SCH", ""))
    1270                 :             :                     {
    1271                 :           0 :                         MetaphAdd(primary, "T");
    1272                 :           0 :                         MetaphAdd(secondary, "T");
    1273                 :             :                     }
    1274                 :             :                     else
    1275                 :             :                     {
    1276                 :           0 :                         MetaphAdd(primary, "0");
    1277                 :           0 :                         MetaphAdd(secondary, "T");
    1278                 :             :                     }
    1279                 :           0 :                     current += 2;
    1280                 :           0 :                     break;
    1281                 :             :                 }
    1282                 :             : 
    1283         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 1, "T", "D", ""))
    1284                 :           0 :                     current += 2;
    1285                 :             :                 else
    1286                 :           0 :                     current += 1;
    1287                 :           0 :                 MetaphAdd(primary, "T");
    1288                 :           0 :                 MetaphAdd(secondary, "T");
    1289                 :           0 :                 break;
    1290                 :             : 
    1291                 :           0 :             case 'V':
    1292         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'V')
    1293                 :           0 :                     current += 2;
    1294                 :             :                 else
    1295                 :           0 :                     current += 1;
    1296                 :           0 :                 MetaphAdd(primary, "F");
    1297                 :           0 :                 MetaphAdd(secondary, "F");
    1298                 :           0 :                 break;
    1299                 :             : 
    1300                 :           0 :             case 'W':
    1301                 :             :                 /* can also be in middle of word */
    1302         [ #  # ]:           0 :                 if (StringAt(original, current, 2, "WR", ""))
    1303                 :             :                 {
    1304                 :           0 :                     MetaphAdd(primary, "R");
    1305                 :           0 :                     MetaphAdd(secondary, "R");
    1306                 :           0 :                     current += 2;
    1307                 :           0 :                     break;
    1308                 :             :                 }
    1309                 :             : 
    1310         [ #  # ]:           0 :                 if ((current == 0)
    1311         [ #  # ]:           0 :                     && (IsVowel(original, current + 1)
    1312         [ #  # ]:           0 :                         || StringAt(original, current, 2, "WH", "")))
    1313                 :             :                 {
    1314                 :             :                     /* Wasserman should match Vasserman */
    1315         [ #  # ]:           0 :                     if (IsVowel(original, current + 1))
    1316                 :             :                     {
    1317                 :           0 :                         MetaphAdd(primary, "A");
    1318                 :           0 :                         MetaphAdd(secondary, "F");
    1319                 :             :                     }
    1320                 :             :                     else
    1321                 :             :                     {
    1322                 :             :                         /* need Uomo to match Womo */
    1323                 :           0 :                         MetaphAdd(primary, "A");
    1324                 :           0 :                         MetaphAdd(secondary, "A");
    1325                 :             :                     }
    1326                 :             :                 }
    1327                 :             : 
    1328                 :             :                 /* Arnow should match Arnoff */
    1329   [ #  #  #  # ]:           0 :                 if (((current == last) && IsVowel(original, current - 1))
    1330         [ #  # ]:           0 :                     || StringAt(original, (current - 1), 5, "EWSKI", "EWSKY",
    1331                 :             :                                 "OWSKI", "OWSKY", "")
    1332         [ #  # ]:           0 :                     || StringAt(original, 0, 3, "SCH", ""))
    1333                 :             :                 {
    1334                 :           0 :                     MetaphAdd(primary, "");
    1335                 :           0 :                     MetaphAdd(secondary, "F");
    1336                 :           0 :                     current += 1;
    1337                 :           0 :                     break;
    1338                 :             :                 }
    1339                 :             : 
    1340                 :             :                 /* polish e.g. 'filipowicz' */
    1341         [ #  # ]:           0 :                 if (StringAt(original, current, 4, "WICZ", "WITZ", ""))
    1342                 :             :                 {
    1343                 :           0 :                     MetaphAdd(primary, "TS");
    1344                 :           0 :                     MetaphAdd(secondary, "FX");
    1345                 :           0 :                     current += 4;
    1346                 :           0 :                     break;
    1347                 :             :                 }
    1348                 :             : 
    1349                 :             :                 /* else skip it */
    1350                 :           0 :                 current += 1;
    1351                 :           0 :                 break;
    1352                 :             : 
    1353                 :           0 :             case 'X':
    1354                 :             :                 /* french e.g. breaux */
    1355   [ #  #  #  # ]:           0 :                 if (!((current == last)
    1356                 :           0 :                       && (StringAt(original, (current - 3), 3,
    1357                 :             :                                    "IAU", "EAU", "")
    1358         [ #  # ]:           0 :                           || StringAt(original, (current - 2), 2,
    1359                 :             :                                       "AU", "OU", ""))))
    1360                 :             :                 {
    1361                 :           0 :                     MetaphAdd(primary, "KS");
    1362                 :           0 :                     MetaphAdd(secondary, "KS");
    1363                 :             :                 }
    1364                 :             : 
    1365                 :             : 
    1366         [ #  # ]:           0 :                 if (StringAt(original, (current + 1), 1, "C", "X", ""))
    1367                 :           0 :                     current += 2;
    1368                 :             :                 else
    1369                 :           0 :                     current += 1;
    1370                 :           0 :                 break;
    1371                 :             : 
    1372                 :           0 :             case 'Z':
    1373                 :             :                 /* chinese pinyin e.g. 'zhao' */
    1374         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'H')
    1375                 :             :                 {
    1376                 :           0 :                     MetaphAdd(primary, "J");
    1377                 :           0 :                     MetaphAdd(secondary, "J");
    1378                 :           0 :                     current += 2;
    1379                 :           0 :                     break;
    1380                 :             :                 }
    1381         [ #  # ]:           0 :                 else if (StringAt(original, (current + 1), 2,
    1382                 :             :                                   "ZO", "ZI", "ZA", "")
    1383         [ #  # ]:           0 :                          || (SlavoGermanic(original)
    1384         [ #  # ]:           0 :                              && ((current > 0)
    1385         [ #  # ]:           0 :                                  && GetAt(original, current - 1) != 'T')))
    1386                 :             :                 {
    1387                 :           0 :                     MetaphAdd(primary, "S");
    1388                 :           0 :                     MetaphAdd(secondary, "TS");
    1389                 :             :                 }
    1390                 :             :                 else
    1391                 :             :                 {
    1392                 :           0 :                     MetaphAdd(primary, "S");
    1393                 :           0 :                     MetaphAdd(secondary, "S");
    1394                 :             :                 }
    1395                 :             : 
    1396         [ #  # ]:           0 :                 if (GetAt(original, current + 1) == 'Z')
    1397                 :           0 :                     current += 2;
    1398                 :             :                 else
    1399                 :           0 :                     current += 1;
    1400                 :           0 :                 break;
    1401                 :             : 
    1402                 :           0 :             default:
    1403                 :           0 :                 current += 1;
    1404                 :             :         }
    1405                 :             : 
    1406                 :             :         /*
    1407                 :             :          * printf("PRIMARY: %s\n", primary->str); printf("SECONDARY: %s\n",
    1408                 :             :          * secondary->str);
    1409                 :             :          */
    1410                 :             :     }
    1411                 :             : 
    1412                 :             : 
    1413         [ -  + ]:           2 :     if (primary->length > 4)
    1414                 :           0 :         SetAt(primary, 4, '\0');
    1415                 :             : 
    1416         [ -  + ]:           2 :     if (secondary->length > 4)
    1417                 :           0 :         SetAt(secondary, 4, '\0');
    1418                 :             : 
    1419                 :           2 :     *codes = primary->str;
    1420                 :           2 :     *++codes = secondary->str;
    1421                 :             : 
    1422                 :           2 :     DestroyMetaString(original);
    1423                 :           2 :     DestroyMetaString(primary);
    1424                 :           2 :     DestroyMetaString(secondary);
    1425                 :           2 : }
    1426                 :             : 
    1427                 :             : #ifdef DMETAPHONE_MAIN
    1428                 :             : 
    1429                 :             : /* just for testing - not part of the perl code */
    1430                 :             : 
    1431                 :             : main(int argc, char **argv)
    1432                 :             : {
    1433                 :             :     char       *codes[2];
    1434                 :             : 
    1435                 :             :     if (argc > 1)
    1436                 :             :     {
    1437                 :             :         DoubleMetaphone(argv[1], DEFAULT_COLLATION_OID, codes);
    1438                 :             :         printf("%s|%s\n", codes[0], codes[1]);
    1439                 :             :     }
    1440                 :             : }
    1441                 :             : 
    1442                 :             : #endif
        

Generated by: LCOV version 2.0-1