~ubuntu-branches/ubuntu/warty/swish-e/warty

« back to all changes in this revision

Viewing changes to src/double_metaphone.c

  • Committer: Bazaar Package Importer
  • Author(s): Ludovic Drolez
  • Date: 2004-03-11 08:41:07 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20040311084107-7vp0mu82blq1qjvo
Tags: 2.4.1-3
Oops ! A comment was not removed to disable interactive compilation.
Closes: Bug#237332

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
$Id: double_metaphone.c,v 1.2 2002/09/30 21:23:31 whmoseley Exp $
 
3
**
 
4
**
 
5
** August 20, 2002 moseley - first added to swish-e
 
6
**
 
7
** this is a very slightly modified version of the double_metaphone.c code
 
8
** from the Perl module Text::DoubleMetaphone by Maurice Aubrey, and based
 
9
** on the work of Lawrence Philips.
 
10
** See http://aspell.sourceforge.net/metaphone
 
11
**
 
12
** From the Text::DoubleMetaphone README file:
 
13
 
 
14
DESCRIPTION
 
15
 
 
16
  This module implements a "sounds like" algorithm developed
 
17
  by Lawrence Philips which he published in the June, 2000 issue
 
18
  of C/C++ Users Journal.  Double Metaphone is an improved
 
19
  version of Philips' original Metaphone algorithm.  
 
20
 
 
21
COPYRIGHT
 
22
 
 
23
  Copyright 2000, Maurice Aubrey <maurice@hevanet.com>. 
 
24
  All rights reserved.
 
25
 
 
26
  This code is based heavily on the C++ implementation by
 
27
  Lawrence Philips and incorporates several bug fixes courtesy
 
28
  of Kevin Atkinson <kevina@users.sourceforge.net>.
 
29
 
 
30
  This module is free software; you may redistribute it and/or
 
31
  modify it under the same terms as Perl itself.
 
32
 
 
33
 
 
34
**
 
35
**
 
36
*/
 
37
 
 
38
 
 
39
 
 
40
#include <stdio.h>
 
41
#include <ctype.h>
 
42
#include <stdlib.h>
 
43
#include <string.h>
 
44
#include <stdarg.h>
 
45
#include <assert.h>
 
46
#include "swish.h"  // $$$ yikes, sure brings in a lot
 
47
#include "double_metaphone.h"
 
48
#include "mem.h"
 
49
 
 
50
 
 
51
#define META_MALLOC(v,n,t) (v = (t*)emalloc(((n)*sizeof(t))))
 
52
 
 
53
#define META_REALLOC(v,n,t) (v = (t*)erealloc((v),((n)*sizeof(t))))
 
54
 
 
55
#define META_FREE(x) efree((x))
 
56
         
 
57
 
 
58
metastring *
 
59
NewMetaString(char *init_str)
 
60
{
 
61
    metastring *s;
 
62
    char empty_string[] = "";
 
63
 
 
64
    META_MALLOC(s, 1, metastring);
 
65
    assert( s != NULL );
 
66
 
 
67
    if (init_str == NULL)
 
68
        init_str = empty_string;
 
69
    s->length  = strlen(init_str);
 
70
    /* preallocate a bit more for potential growth */
 
71
    s->bufsize = s->length + 7;
 
72
 
 
73
    META_MALLOC(s->str, s->bufsize, char);
 
74
    assert( s->str != NULL );
 
75
    
 
76
    strncpy(s->str, init_str, s->length + 1);
 
77
    s->free_string_on_destroy = 1;
 
78
 
 
79
    return s;
 
80
}
 
81
 
 
82
 
 
83
void
 
84
DestroyMetaString(metastring * s)
 
85
{
 
86
    if (s == NULL)
 
87
        return;
 
88
 
 
89
    if (s->free_string_on_destroy && (s->str != NULL))
 
90
        META_FREE(s->str);
 
91
 
 
92
    META_FREE(s);
 
93
}
 
94
 
 
95
 
 
96
void
 
97
IncreaseBuffer(metastring * s, int chars_needed)
 
98
{
 
99
    META_REALLOC(s->str, (s->bufsize + chars_needed + 10), char);
 
100
    assert( s->str != NULL );
 
101
    s->bufsize = s->bufsize + chars_needed + 10;
 
102
}
 
103
 
 
104
 
 
105
void
 
106
MakeUpper(metastring * s)
 
107
{
 
108
    char *i;
 
109
 
 
110
    for (i = s->str; *i; i++)
 
111
      {
 
112
          *i = toupper(*i);
 
113
      }
 
114
}
 
115
 
 
116
 
 
117
int
 
118
IsVowel(metastring * s, int pos)
 
119
{
 
120
    char c;
 
121
 
 
122
    if ((pos < 0) || (pos >= s->length))
 
123
        return 0;
 
124
 
 
125
    c = *(s->str + pos);
 
126
    if ((c == 'A') || (c == 'E') || (c == 'I') || (c =='O') || 
 
127
        (c =='U')  || (c == 'Y'))
 
128
        return 1;
 
129
 
 
130
    return 0;
 
131
}
 
132
 
 
133
 
 
134
int
 
135
SlavoGermanic(metastring * s)
 
136
{
 
137
    if ((char *) strstr(s->str, "W"))
 
138
        return 1;
 
139
    else if ((char *) strstr(s->str, "K"))
 
140
        return 1;
 
141
    else if ((char *) strstr(s->str, "CZ"))
 
142
        return 1;
 
143
    else if ((char *) strstr(s->str, "WITZ"))
 
144
        return 1;
 
145
    else
 
146
        return 0;
 
147
}
 
148
 
 
149
 
 
150
int
 
151
GetLength(metastring * s)
 
152
{
 
153
    return s->length;
 
154
}
 
155
 
 
156
 
 
157
char
 
158
GetAt(metastring * s, int pos)
 
159
{
 
160
    if ((pos < 0) || (pos >= s->length))
 
161
        return '\0';
 
162
 
 
163
    return ((char) *(s->str + pos));
 
164
}
 
165
 
 
166
 
 
167
void
 
168
SetAt(metastring * s, int pos, char c)
 
169
{
 
170
    if ((pos < 0) || (pos >= s->length))
 
171
        return;
 
172
 
 
173
    *(s->str + pos) = c;
 
174
}
 
175
 
 
176
 
 
177
/* 
 
178
   Caveats: the START value is 0 based
 
179
*/
 
180
int
 
181
StringAt(metastring * s, int start, int length, ...)
 
182
{
 
183
    char *test;
 
184
    char *pos;
 
185
    va_list ap;
 
186
 
 
187
    if ((start < 0) || (start >= s->length))
 
188
        return 0;
 
189
 
 
190
    pos = (s->str + start);
 
191
    va_start(ap, length);
 
192
 
 
193
    do
 
194
      {
 
195
          test = va_arg(ap, char *);
 
196
          if (*test && (strncmp(pos, test, length) == 0))
 
197
              return 1;
 
198
      }
 
199
    while (strcmp(test, ""));
 
200
 
 
201
    va_end(ap);
 
202
 
 
203
    return 0;
 
204
}
 
205
 
 
206
 
 
207
void
 
208
MetaphAdd(metastring * s, char *new_str)
 
209
{
 
210
    int add_length;
 
211
 
 
212
    if (new_str == NULL)
 
213
        return;
 
214
 
 
215
    add_length = strlen(new_str);
 
216
    if ((s->length + add_length) > (s->bufsize - 1))
 
217
      {
 
218
          IncreaseBuffer(s, add_length);
 
219
      }
 
220
 
 
221
    strcat(s->str, new_str);
 
222
    s->length += add_length;
 
223
}
 
224
 
 
225
 
 
226
void
 
227
DoubleMetaphone(char *str, char **codes)
 
228
{
 
229
    int        length;
 
230
    metastring *original;
 
231
    metastring *primary;
 
232
    metastring *secondary;
 
233
    int        current;
 
234
    int        last;
 
235
 
 
236
    current = 0;
 
237
    /* we need the real length and last prior to padding */
 
238
    length  = strlen(str); 
 
239
    last    = length - 1; 
 
240
    original = NewMetaString(str);
 
241
    /* Pad original so we can index beyond end */
 
242
    MetaphAdd(original, "     ");
 
243
 
 
244
    primary = NewMetaString("");
 
245
    secondary = NewMetaString("");
 
246
    primary->free_string_on_destroy = 0;
 
247
    secondary->free_string_on_destroy = 0;
 
248
 
 
249
    MakeUpper(original);
 
250
 
 
251
    /* skip these when at start of word */
 
252
    if (StringAt(original, 0, 2, "GN", "KN", "PN", "WR", "PS", ""))
 
253
        current += 1;
 
254
 
 
255
    /* Initial 'X' is pronounced 'Z' e.g. 'Xavier' */
 
256
    if (GetAt(original, 0) == 'X')
 
257
      {
 
258
          MetaphAdd(primary, "S");      /* 'Z' maps to 'S' */
 
259
          MetaphAdd(secondary, "S");
 
260
          current += 1;
 
261
      }
 
262
 
 
263
    /* main loop */
 
264
    while ((primary->length < 4) || (secondary->length < 4))  
 
265
      {
 
266
          if (current >= length)
 
267
              break;
 
268
 
 
269
          switch (GetAt(original, current))
 
270
            {
 
271
            case 'A':
 
272
            case 'E':
 
273
            case 'I':
 
274
            case 'O':
 
275
            case 'U':
 
276
            case 'Y':
 
277
                if (current == 0)
 
278
                  {
 
279
                    /* all init vowels now map to 'A' */
 
280
                    MetaphAdd(primary, "A");
 
281
                    MetaphAdd(secondary, "A");
 
282
                  }
 
283
                current += 1;
 
284
                break;
 
285
 
 
286
            case 'B':
 
287
 
 
288
                /* "-mb", e.g", "dumb", already skipped over... */
 
289
                MetaphAdd(primary, "P");
 
290
                MetaphAdd(secondary, "P");
 
291
 
 
292
                if (GetAt(original, current + 1) == 'B')
 
293
                    current += 2;
 
294
                else
 
295
                    current += 1;
 
296
                break;
 
297
 
 
298
            case '�':
 
299
                MetaphAdd(primary, "S");
 
300
                MetaphAdd(secondary, "S");
 
301
                current += 1;
 
302
                break;
 
303
 
 
304
            case 'C':
 
305
                /* various germanic */
 
306
                if ((current > 1)
 
307
                    && !IsVowel(original, current - 2)
 
308
                    && StringAt(original, (current - 1), 3, "ACH", "")
 
309
                    && ((GetAt(original, current + 2) != 'I')
 
310
                        && ((GetAt(original, current + 2) != 'E')
 
311
                            || StringAt(original, (current - 2), 6, "BACHER",
 
312
                                        "MACHER", ""))))
 
313
                  {
 
314
                      MetaphAdd(primary, "K");
 
315
                      MetaphAdd(secondary, "K");
 
316
                      current += 2;
 
317
                      break;
 
318
                  }
 
319
 
 
320
                /* special case 'caesar' */
 
321
                if ((current == 0)
 
322
                    && StringAt(original, current, 6, "CAESAR", ""))
 
323
                  {
 
324
                      MetaphAdd(primary, "S");
 
325
                      MetaphAdd(secondary, "S");
 
326
                      current += 2;
 
327
                      break;
 
328
                  }
 
329
 
 
330
                /* italian 'chianti' */
 
331
                if (StringAt(original, current, 4, "CHIA", ""))
 
332
                  {
 
333
                      MetaphAdd(primary, "K");
 
334
                      MetaphAdd(secondary, "K");
 
335
                      current += 2;
 
336
                      break;
 
337
                  }
 
338
 
 
339
                if (StringAt(original, current, 2, "CH", ""))
 
340
                  {
 
341
                      /* find 'michael' */
 
342
                      if ((current > 0)
 
343
                          && StringAt(original, current, 4, "CHAE", ""))
 
344
                        {
 
345
                            MetaphAdd(primary, "K");
 
346
                            MetaphAdd(secondary, "X");
 
347
                            current += 2;
 
348
                            break;
 
349
                        }
 
350
 
 
351
                      /* greek roots e.g. 'chemistry', 'chorus' */
 
352
                      if ((current == 0)
 
353
                          && (StringAt(original, (current + 1), 5, "HARAC", "HARIS", "")
 
354
                           || StringAt(original, (current + 1), 3, "HOR",
 
355
                                       "HYM", "HIA", "HEM", ""))
 
356
                          && !StringAt(original, 0, 5, "CHORE", ""))
 
357
                        {
 
358
                            MetaphAdd(primary, "K");
 
359
                            MetaphAdd(secondary, "K");
 
360
                            current += 2;
 
361
                            break;
 
362
                        }
 
363
 
 
364
                      /* germanic, greek, or otherwise 'ch' for 'kh' sound */
 
365
                      if (
 
366
                          (StringAt(original, 0, 4, "VAN ", "VON ", "")
 
367
                           || StringAt(original, 0, 3, "SCH", ""))
 
368
                          /*  'architect but not 'arch', 'orchestra', 'orchid' */
 
369
                          || StringAt(original, (current - 2), 6, "ORCHES",
 
370
                                      "ARCHIT", "ORCHID", "")
 
371
                          || StringAt(original, (current + 2), 1, "T", "S",
 
372
                                      "")
 
373
                          || ((StringAt(original, (current - 1), 1, "A", "O", "U", "E", "") 
 
374
                          || (current == 0))
 
375
                           /* e.g., 'wachtler', 'wechsler', but not 'tichner' */
 
376
                          && StringAt(original, (current + 2), 1, "L", "R",
 
377
                                      "N", "M", "B", "H", "F", "V", "W", " ", "")))
 
378
                        {
 
379
                            MetaphAdd(primary, "K");
 
380
                            MetaphAdd(secondary, "K");
 
381
                        }
 
382
                      else
 
383
                        {
 
384
                            if (current > 0)
 
385
                              {
 
386
                                  if (StringAt(original, 0, 2, "MC", ""))
 
387
                                    {
 
388
                                        /* e.g., "McHugh" */
 
389
                                        MetaphAdd(primary, "K");
 
390
                                        MetaphAdd(secondary, "K");
 
391
                                    }
 
392
                                  else
 
393
                                    {
 
394
                                        MetaphAdd(primary, "X");
 
395
                                        MetaphAdd(secondary, "K");
 
396
                                    }
 
397
                              }
 
398
                            else
 
399
                              {
 
400
                                  MetaphAdd(primary, "X");
 
401
                                  MetaphAdd(secondary, "X");
 
402
                              }
 
403
                        }
 
404
                      current += 2;
 
405
                      break;
 
406
                  }
 
407
                /* e.g, 'czerny' */
 
408
                if (StringAt(original, current, 2, "CZ", "")
 
409
                    && !StringAt(original, (current - 2), 4, "WICZ", ""))
 
410
                  {
 
411
                      MetaphAdd(primary, "S");
 
412
                      MetaphAdd(secondary, "X");
 
413
                      current += 2;
 
414
                      break;
 
415
                  }
 
416
 
 
417
                /* e.g., 'focaccia' */
 
418
                if (StringAt(original, (current + 1), 3, "CIA", ""))
 
419
                  {
 
420
                      MetaphAdd(primary, "X");
 
421
                      MetaphAdd(secondary, "X");
 
422
                      current += 3;
 
423
                      break;
 
424
                  }
 
425
 
 
426
                /* double 'C', but not if e.g. 'McClellan' */
 
427
                if (StringAt(original, current, 2, "CC", "")
 
428
                    && !((current == 1) && (GetAt(original, 0) == 'M')))
 
429
                {
 
430
                    /* 'bellocchio' but not 'bacchus' */
 
431
                    if (StringAt(original, (current + 2), 1, "I", "E", "H", "")
 
432
                        && !StringAt(original, (current + 2), 2, "HU", ""))
 
433
                      {
 
434
                          /* 'accident', 'accede' 'succeed' */
 
435
                          if (
 
436
                              ((current == 1)
 
437
                               && (GetAt(original, current - 1) == 'A'))
 
438
                              || StringAt(original, (current - 1), 5, "UCCEE",
 
439
                                          "UCCES", ""))
 
440
                            {
 
441
                                MetaphAdd(primary, "KS");
 
442
                                MetaphAdd(secondary, "KS");
 
443
                                /* 'bacci', 'bertucci', other italian */
 
444
                            }
 
445
                          else
 
446
                            {
 
447
                                MetaphAdd(primary, "X");
 
448
                                MetaphAdd(secondary, "X");
 
449
                            }
 
450
                          current += 3;
 
451
                          break;
 
452
                      }
 
453
                    else
 
454
                      {   /* Pierce's rule */
 
455
                          MetaphAdd(primary, "K");
 
456
                          MetaphAdd(secondary, "K");
 
457
                          current += 2;
 
458
                          break;
 
459
                      }
 
460
                }
 
461
 
 
462
                if (StringAt(original, current, 2, "CK", "CG", "CQ", ""))
 
463
                  {
 
464
                      MetaphAdd(primary, "K");
 
465
                      MetaphAdd(secondary, "K");
 
466
                      current += 2;
 
467
                      break;
 
468
                  }
 
469
 
 
470
                if (StringAt(original, current, 2, "CI", "CE", "CY", ""))
 
471
                  {
 
472
                      /* italian vs. english */
 
473
                      if (StringAt
 
474
                          (original, current, 3, "CIO", "CIE", "CIA", ""))
 
475
                        {
 
476
                            MetaphAdd(primary, "S");
 
477
                            MetaphAdd(secondary, "X");
 
478
                        }
 
479
                      else
 
480
                        {
 
481
                            MetaphAdd(primary, "S");
 
482
                            MetaphAdd(secondary, "S");
 
483
                        }
 
484
                      current += 2;
 
485
                      break;
 
486
                  }
 
487
 
 
488
                /* else */
 
489
                MetaphAdd(primary, "K");
 
490
                MetaphAdd(secondary, "K");
 
491
 
 
492
                /* name sent in 'mac caffrey', 'mac gregor */
 
493
                if (StringAt(original, (current + 1), 2, " C", " Q", " G", ""))
 
494
                    current += 3;
 
495
                else
 
496
                    if (StringAt(original, (current + 1), 1, "C", "K", "Q", "")
 
497
                        && !StringAt(original, (current + 1), 2, "CE", "CI", ""))
 
498
                    current += 2;
 
499
                else
 
500
                    current += 1;
 
501
                break;
 
502
 
 
503
            case 'D':
 
504
                if (StringAt(original, current, 2, "DG", ""))
 
505
                  {
 
506
                      if (StringAt(original, (current + 2), 1, "I", "E", "Y", ""))
 
507
                        {
 
508
                            /* e.g. 'edge' */
 
509
                            MetaphAdd(primary, "J");
 
510
                            MetaphAdd(secondary, "J");
 
511
                            current += 3;
 
512
                            break;
 
513
                        }
 
514
                      else
 
515
                        {
 
516
                            /* e.g. 'edgar' */
 
517
                            MetaphAdd(primary, "TK");
 
518
                            MetaphAdd(secondary, "TK");
 
519
                            current += 2;
 
520
                            break;
 
521
                        }
 
522
                  }
 
523
 
 
524
                if (StringAt(original, current, 2, "DT", "DD", ""))
 
525
                  {
 
526
                      MetaphAdd(primary, "T");
 
527
                      MetaphAdd(secondary, "T");
 
528
                      current += 2;
 
529
                      break;
 
530
                  }
 
531
 
 
532
                /* else */
 
533
                MetaphAdd(primary, "T");
 
534
                MetaphAdd(secondary, "T");
 
535
                current += 1;
 
536
                break;
 
537
 
 
538
            case 'F':
 
539
                if (GetAt(original, current + 1) == 'F')
 
540
                    current += 2;
 
541
                else
 
542
                    current += 1;
 
543
                MetaphAdd(primary, "F");
 
544
                MetaphAdd(secondary, "F");
 
545
                break;
 
546
 
 
547
            case 'G':
 
548
                if (GetAt(original, current + 1) == 'H')
 
549
                  {
 
550
                      if ((current > 0) && !IsVowel(original, current - 1))
 
551
                        {
 
552
                            MetaphAdd(primary, "K");
 
553
                            MetaphAdd(secondary, "K");
 
554
                            current += 2;
 
555
                            break;
 
556
                        }
 
557
 
 
558
                      if (current < 3)
 
559
                        {
 
560
                            /* 'ghislane', ghiradelli */
 
561
                            if (current == 0)
 
562
                              {
 
563
                                  if (GetAt(original, current + 2) == 'I')
 
564
                                    {
 
565
                                        MetaphAdd(primary, "J");
 
566
                                        MetaphAdd(secondary, "J");
 
567
                                    }
 
568
                                  else
 
569
                                    {
 
570
                                        MetaphAdd(primary, "K");
 
571
                                        MetaphAdd(secondary, "K");
 
572
                                    }
 
573
                                  current += 2;
 
574
                                  break;
 
575
                              }
 
576
                        }
 
577
                      /* Parker's rule (with some further refinements) - e.g., 'hugh' */
 
578
                      if (
 
579
                          ((current > 1)
 
580
                           && StringAt(original, (current - 2), 1, "B", "H", "D", ""))
 
581
                          /* e.g., 'bough' */
 
582
                          || ((current > 2)
 
583
                              && StringAt(original, (current - 3), 1, "B", "H", "D", ""))
 
584
                          /* e.g., 'broughton' */
 
585
                          || ((current > 3)
 
586
                              && StringAt(original, (current - 4), 1, "B", "H", "")))
 
587
                        {
 
588
                            current += 2;
 
589
                            break;
 
590
                        }
 
591
                      else
 
592
                        {
 
593
                            /* e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' */
 
594
                            if ((current > 2)
 
595
                                && (GetAt(original, current - 1) == 'U')
 
596
                                && StringAt(original, (current - 3), 1, "C",
 
597
                                            "G", "L", "R", "T", ""))
 
598
                              {
 
599
                                  MetaphAdd(primary, "F");
 
600
                                  MetaphAdd(secondary, "F");
 
601
                              }
 
602
                            else if ((current > 0)
 
603
                                     && GetAt(original, current - 1) != 'I')
 
604
                              {
 
605
 
 
606
 
 
607
                                  MetaphAdd(primary, "K");
 
608
                                  MetaphAdd(secondary, "K");
 
609
                              }
 
610
 
 
611
                            current += 2;
 
612
                            break;
 
613
                        }
 
614
                  }
 
615
 
 
616
                if (GetAt(original, current + 1) == 'N')
 
617
                  {
 
618
                      if ((current == 1) && IsVowel(original, 0)
 
619
                          && !SlavoGermanic(original))
 
620
                        {
 
621
                            MetaphAdd(primary, "KN");
 
622
                            MetaphAdd(secondary, "N");
 
623
                        }
 
624
                      else
 
625
                          /* not e.g. 'cagney' */
 
626
                          if (!StringAt(original, (current + 2), 2, "EY", "")
 
627
                              && (GetAt(original, current + 1) != 'Y')
 
628
                              && !SlavoGermanic(original))
 
629
                        {
 
630
                            MetaphAdd(primary, "N");
 
631
                            MetaphAdd(secondary, "KN");
 
632
                        }
 
633
                      else
 
634
                        {
 
635
                            MetaphAdd(primary, "KN");
 
636
                            MetaphAdd(secondary, "KN");
 
637
                        }
 
638
                      current += 2;
 
639
                      break;
 
640
                  }
 
641
 
 
642
                /* 'tagliaro' */
 
643
                if (StringAt(original, (current + 1), 2, "LI", "")
 
644
                    && !SlavoGermanic(original))
 
645
                  {
 
646
                      MetaphAdd(primary, "KL");
 
647
                      MetaphAdd(secondary, "L");
 
648
                      current += 2;
 
649
                      break;
 
650
                  }
 
651
 
 
652
                /* -ges-,-gep-,-gel-, -gie- at beginning */
 
653
                if ((current == 0)
 
654
                    && ((GetAt(original, current + 1) == 'Y')
 
655
                        || StringAt(original, (current + 1), 2, "ES", "EP",
 
656
                                    "EB", "EL", "EY", "IB", "IL", "IN", "IE",
 
657
                                    "EI", "ER", "")))
 
658
                  {
 
659
                      MetaphAdd(primary, "K");
 
660
                      MetaphAdd(secondary, "J");
 
661
                      current += 2;
 
662
                      break;
 
663
                  }
 
664
 
 
665
                /*  -ger-,  -gy- */
 
666
                if (
 
667
                    (StringAt(original, (current + 1), 2, "ER", "")
 
668
                     || (GetAt(original, current + 1) == 'Y'))
 
669
                    && !StringAt(original, 0, 6, "DANGER", "RANGER", "MANGER", "")
 
670
                    && !StringAt(original, (current - 1), 1, "E", "I", "")
 
671
                    && !StringAt(original, (current - 1), 3, "RGY", "OGY",
 
672
                                 ""))
 
673
                  {
 
674
                      MetaphAdd(primary, "K");
 
675
                      MetaphAdd(secondary, "J");
 
676
                      current += 2;
 
677
                      break;
 
678
                  }
 
679
 
 
680
                /*  italian e.g, 'biaggi' */
 
681
                if (StringAt(original, (current + 1), 1, "E", "I", "Y", "")
 
682
                    || StringAt(original, (current - 1), 4, "AGGI", "OGGI", ""))
 
683
                  {
 
684
                      /* obvious germanic */
 
685
                      if (
 
686
                          (StringAt(original, 0, 4, "VAN ", "VON ", "")
 
687
                           || StringAt(original, 0, 3, "SCH", ""))
 
688
                          || StringAt(original, (current + 1), 2, "ET", ""))
 
689
                        {
 
690
                            MetaphAdd(primary, "K");
 
691
                            MetaphAdd(secondary, "K");
 
692
                        }
 
693
                      else
 
694
                        {
 
695
                            /* always soft if french ending */
 
696
                            if (StringAt
 
697
                                (original, (current + 1), 4, "IER ", ""))
 
698
                              {
 
699
                                  MetaphAdd(primary, "J");
 
700
                                  MetaphAdd(secondary, "J");
 
701
                              }
 
702
                            else
 
703
                              {
 
704
                                  MetaphAdd(primary, "J");
 
705
                                  MetaphAdd(secondary, "K");
 
706
                              }
 
707
                        }
 
708
                      current += 2;
 
709
                      break;
 
710
                  }
 
711
 
 
712
                if (GetAt(original, current + 1) == 'G')
 
713
                    current += 2;
 
714
                else
 
715
                    current += 1;
 
716
                MetaphAdd(primary, "K");
 
717
                MetaphAdd(secondary, "K");
 
718
                break;
 
719
 
 
720
            case 'H':
 
721
                /* only keep if first & before vowel or btw. 2 vowels */
 
722
                if (((current == 0) || IsVowel(original, current - 1))
 
723
                    && IsVowel(original, current + 1))
 
724
                  {
 
725
                      MetaphAdd(primary, "H");
 
726
                      MetaphAdd(secondary, "H");
 
727
                      current += 2;
 
728
                  }
 
729
                else            /* also takes care of 'HH' */
 
730
                    current += 1;
 
731
                break;
 
732
 
 
733
            case 'J':
 
734
                /* obvious spanish, 'jose', 'san jacinto' */
 
735
                if (StringAt(original, current, 4, "JOSE", "")
 
736
                    || StringAt(original, 0, 4, "SAN ", ""))
 
737
                  {
 
738
                      if (((current == 0)
 
739
                           && (GetAt(original, current + 4) == ' '))
 
740
                          || StringAt(original, 0, 4, "SAN ", ""))
 
741
                        {
 
742
                            MetaphAdd(primary, "H");
 
743
                            MetaphAdd(secondary, "H");
 
744
                        }
 
745
                      else
 
746
                        {
 
747
                            MetaphAdd(primary, "J");
 
748
                            MetaphAdd(secondary, "H");
 
749
                        }
 
750
                      current += 1;
 
751
                      break;
 
752
                  }
 
753
 
 
754
                if ((current == 0)
 
755
                    && !StringAt(original, current, 4, "JOSE", ""))
 
756
                  {
 
757
                      MetaphAdd(primary, "J");  /* Yankelovich/Jankelowicz */
 
758
                      MetaphAdd(secondary, "A");
 
759
                  }
 
760
                else
 
761
                  {
 
762
                      /* spanish pron. of e.g. 'bajador' */
 
763
                      if (IsVowel(original, current - 1)
 
764
                          && !SlavoGermanic(original)
 
765
                          && ((GetAt(original, current + 1) == 'A')
 
766
                              || (GetAt(original, current + 1) == 'O')))
 
767
                        {
 
768
                            MetaphAdd(primary, "J");
 
769
                            MetaphAdd(secondary, "H");
 
770
                        }
 
771
                      else
 
772
                        {
 
773
                            if (current == last)
 
774
                              {
 
775
                                  MetaphAdd(primary, "J");
 
776
                                  MetaphAdd(secondary, "");
 
777
                              }
 
778
                            else
 
779
                              {
 
780
                                  if (!StringAt(original, (current + 1), 1, "L", "T",
 
781
                                                "K", "S", "N", "M", "B", "Z", "")
 
782
                                      && !StringAt(original, (current - 1), 1,
 
783
                                                   "S", "K", "L", "")) 
 
784
                                    {
 
785
                                      MetaphAdd(primary, "J");
 
786
                                      MetaphAdd(secondary, "J");
 
787
                                    }
 
788
                              }
 
789
                        }
 
790
                  }
 
791
 
 
792
                if (GetAt(original, current + 1) == 'J')        /* it could happen! */
 
793
                    current += 2;
 
794
                else
 
795
                    current += 1;
 
796
                break;
 
797
 
 
798
            case 'K':
 
799
                if (GetAt(original, current + 1) == 'K')
 
800
                    current += 2;
 
801
                else
 
802
                    current += 1;
 
803
                MetaphAdd(primary, "K");
 
804
                MetaphAdd(secondary, "K");
 
805
                break;
 
806
 
 
807
            case 'L':
 
808
                if (GetAt(original, current + 1) == 'L')
 
809
                  {
 
810
                      /* spanish e.g. 'cabrillo', 'gallegos' */
 
811
                      if (((current == (length - 3))
 
812
                           && StringAt(original, (current - 1), 4, "ILLO",
 
813
                                       "ILLA", "ALLE", ""))
 
814
                          || ((StringAt(original, (last - 1), 2, "AS", "OS", "")
 
815
                            || StringAt(original, last, 1, "A", "O", ""))
 
816
                           && StringAt(original, (current - 1), 4, "ALLE", "")))
 
817
                        {
 
818
                            MetaphAdd(primary, "L");
 
819
                            MetaphAdd(secondary, "");
 
820
                            current += 2;
 
821
                            break;
 
822
                        }
 
823
                      current += 2;
 
824
                  }
 
825
                else
 
826
                    current += 1;
 
827
                MetaphAdd(primary, "L");
 
828
                MetaphAdd(secondary, "L");
 
829
                break;
 
830
 
 
831
            case 'M':
 
832
                if ((StringAt(original, (current - 1), 3, "UMB", "")
 
833
                     && (((current + 1) == last)
 
834
                         || StringAt(original, (current + 2), 2, "ER", "")))
 
835
                    /* 'dumb','thumb' */
 
836
                    || (GetAt(original, current + 1) == 'M'))
 
837
                    current += 2;
 
838
                else
 
839
                    current += 1;
 
840
                MetaphAdd(primary, "M");
 
841
                MetaphAdd(secondary, "M");
 
842
                break;
 
843
 
 
844
            case 'N':
 
845
                if (GetAt(original, current + 1) == 'N')
 
846
                    current += 2;
 
847
                else
 
848
                    current += 1;
 
849
                MetaphAdd(primary, "N");
 
850
                MetaphAdd(secondary, "N");
 
851
                break;
 
852
 
 
853
            case '�':
 
854
                current += 1;
 
855
                MetaphAdd(primary, "N");
 
856
                MetaphAdd(secondary, "N");
 
857
                break;
 
858
 
 
859
            case 'P':
 
860
                if (GetAt(original, current + 1) == 'H')
 
861
                  {
 
862
                      MetaphAdd(primary, "F");
 
863
                      MetaphAdd(secondary, "F");
 
864
                      current += 2;
 
865
                      break;
 
866
                  }
 
867
 
 
868
                /* also account for "campbell", "raspberry" */
 
869
                if (StringAt(original, (current + 1), 1, "P", "B", ""))
 
870
                    current += 2;
 
871
                else
 
872
                    current += 1;
 
873
                MetaphAdd(primary, "P");
 
874
                MetaphAdd(secondary, "P");
 
875
                break;
 
876
 
 
877
            case 'Q':
 
878
                if (GetAt(original, current + 1) == 'Q')
 
879
                    current += 2;
 
880
                else
 
881
                    current += 1;
 
882
                MetaphAdd(primary, "K");
 
883
                MetaphAdd(secondary, "K");
 
884
                break;
 
885
 
 
886
            case 'R':
 
887
                /* french e.g. 'rogier', but exclude 'hochmeier' */
 
888
                if ((current == last)
 
889
                    && !SlavoGermanic(original)
 
890
                    && StringAt(original, (current - 2), 2, "IE", "")
 
891
                    && !StringAt(original, (current - 4), 2, "ME", "MA", ""))
 
892
                  {
 
893
                      MetaphAdd(primary, "");
 
894
                      MetaphAdd(secondary, "R");
 
895
                  }
 
896
                else
 
897
                  {
 
898
                      MetaphAdd(primary, "R");
 
899
                      MetaphAdd(secondary, "R");
 
900
                  }
 
901
 
 
902
                if (GetAt(original, current + 1) == 'R')
 
903
                    current += 2;
 
904
                else
 
905
                    current += 1;
 
906
                break;
 
907
 
 
908
            case 'S':
 
909
                /* special cases 'island', 'isle', 'carlisle', 'carlysle' */
 
910
                if (StringAt(original, (current - 1), 3, "ISL", "YSL", ""))
 
911
                  {
 
912
                      current += 1;
 
913
                      break;
 
914
                  }
 
915
 
 
916
                /* special case 'sugar-' */
 
917
                if ((current == 0)
 
918
                    && StringAt(original, current, 5, "SUGAR", ""))
 
919
                  {
 
920
                      MetaphAdd(primary, "X");
 
921
                      MetaphAdd(secondary, "S");
 
922
                      current += 1;
 
923
                      break;
 
924
                  }
 
925
 
 
926
                if (StringAt(original, current, 2, "SH", ""))
 
927
                  {
 
928
                      /* germanic */
 
929
                      if (StringAt
 
930
                          (original, (current + 1), 4, "HEIM", "HOEK", "HOLM",
 
931
                           "HOLZ", ""))
 
932
                        {
 
933
                            MetaphAdd(primary, "S");
 
934
                            MetaphAdd(secondary, "S");
 
935
                        }
 
936
                      else
 
937
                        {
 
938
                            MetaphAdd(primary, "X");
 
939
                            MetaphAdd(secondary, "X");
 
940
                        }
 
941
                      current += 2;
 
942
                      break;
 
943
                  }
 
944
 
 
945
                /* italian & armenian */
 
946
                if (StringAt(original, current, 3, "SIO", "SIA", "")
 
947
                    || StringAt(original, current, 4, "SIAN", ""))
 
948
                  {
 
949
                      if (!SlavoGermanic(original))
 
950
                        {
 
951
                            MetaphAdd(primary, "S");
 
952
                            MetaphAdd(secondary, "X");
 
953
                        }
 
954
                      else
 
955
                        {
 
956
                            MetaphAdd(primary, "S");
 
957
                            MetaphAdd(secondary, "S");
 
958
                        }
 
959
                      current += 3;
 
960
                      break;
 
961
                  }
 
962
 
 
963
                /* german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' 
 
964
                   also, -sz- in slavic language altho in hungarian it is pronounced 's' */
 
965
                if (((current == 0)
 
966
                     && StringAt(original, (current + 1), 1, "M", "N", "L", "W", ""))
 
967
                    || StringAt(original, (current + 1), 1, "Z", ""))
 
968
                  {
 
969
                      MetaphAdd(primary, "S");
 
970
                      MetaphAdd(secondary, "X");
 
971
                      if (StringAt(original, (current + 1), 1, "Z", ""))
 
972
                          current += 2;
 
973
                      else
 
974
                          current += 1;
 
975
                      break;
 
976
                  }
 
977
 
 
978
                if (StringAt(original, current, 2, "SC", ""))
 
979
                  {
 
980
                      /* Schlesinger's rule */
 
981
                      if (GetAt(original, current + 2) == 'H')
 
982
                      {
 
983
                          /* dutch origin, e.g. 'school', 'schooner' */
 
984
                          if (StringAt(original, (current + 3), 2, "OO", "ER", "EN",
 
985
                                       "UY", "ED", "EM", ""))
 
986
                            {
 
987
                                /* 'schermerhorn', 'schenker' */
 
988
                                if (StringAt(original, (current + 3), 2, "ER", "EN", ""))
 
989
                                  {
 
990
                                      MetaphAdd(primary, "X");
 
991
                                      MetaphAdd(secondary, "SK");
 
992
                                  }
 
993
                                else
 
994
                                  {
 
995
                                      MetaphAdd(primary, "SK");
 
996
                                      MetaphAdd(secondary, "SK");
 
997
                                  }
 
998
                                current += 3;
 
999
                                break;
 
1000
                            }
 
1001
                          else
 
1002
                            {
 
1003
                                if ((current == 0) && !IsVowel(original, 3)
 
1004
                                    && (GetAt(original, 3) != 'W'))
 
1005
                                  {
 
1006
                                      MetaphAdd(primary, "X");
 
1007
                                      MetaphAdd(secondary, "S");
 
1008
                                  }
 
1009
                                else
 
1010
                                  {
 
1011
                                      MetaphAdd(primary, "X");
 
1012
                                      MetaphAdd(secondary, "X");
 
1013
                                  }
 
1014
                                current += 3;
 
1015
                                break;
 
1016
                            }
 
1017
 
 
1018
                      if (StringAt(original, (current + 2), 1, "I", "E", "Y", ""))
 
1019
                        {
 
1020
                            MetaphAdd(primary, "S");
 
1021
                            MetaphAdd(secondary, "S");
 
1022
                            current += 3;
 
1023
                            break;
 
1024
                        }
 
1025
                      /* else */
 
1026
                      MetaphAdd(primary, "SK");
 
1027
                      MetaphAdd(secondary, "SK");
 
1028
                      current += 3;
 
1029
                      break;
 
1030
                    }
 
1031
                  }
 
1032
 
 
1033
                /* french e.g. 'resnais', 'artois' */
 
1034
                if ((current == last)
 
1035
                    && StringAt(original, (current - 2), 2, "AI", "OI", ""))
 
1036
                  {
 
1037
                      MetaphAdd(primary, "");
 
1038
                      MetaphAdd(secondary, "S");
 
1039
                  }
 
1040
                else
 
1041
                  {
 
1042
                      MetaphAdd(primary, "S");
 
1043
                      MetaphAdd(secondary, "S");
 
1044
                  }
 
1045
 
 
1046
                if (StringAt(original, (current + 1), 1, "S", "Z", ""))
 
1047
                    current += 2;
 
1048
                else
 
1049
                    current += 1;
 
1050
                break;
 
1051
 
 
1052
            case 'T':
 
1053
                if (StringAt(original, current, 4, "TION", ""))
 
1054
                  {
 
1055
                      MetaphAdd(primary, "X");
 
1056
                      MetaphAdd(secondary, "X");
 
1057
                      current += 3;
 
1058
                      break;
 
1059
                  }
 
1060
 
 
1061
                if (StringAt(original, current, 3, "TIA", "TCH", ""))
 
1062
                  {
 
1063
                      MetaphAdd(primary, "X");
 
1064
                      MetaphAdd(secondary, "X");
 
1065
                      current += 3;
 
1066
                      break;
 
1067
                  }
 
1068
 
 
1069
                if (StringAt(original, current, 2, "TH", "")
 
1070
                    || StringAt(original, current, 3, "TTH", ""))
 
1071
                  {
 
1072
                      /* special case 'thomas', 'thames' or germanic */
 
1073
                      if (StringAt(original, (current + 2), 2, "OM", "AM", "")
 
1074
                          || StringAt(original, 0, 4, "VAN ", "VON ", "")
 
1075
                          || StringAt(original, 0, 3, "SCH", ""))
 
1076
                        {
 
1077
                            MetaphAdd(primary, "T");
 
1078
                            MetaphAdd(secondary, "T");
 
1079
                        }
 
1080
                      else
 
1081
                        {
 
1082
                            MetaphAdd(primary, "0");
 
1083
                            MetaphAdd(secondary, "T");
 
1084
                        }
 
1085
                      current += 2;
 
1086
                      break;
 
1087
                  }
 
1088
 
 
1089
                if (StringAt(original, (current + 1), 1, "T", "D", ""))
 
1090
                    current += 2;
 
1091
                else
 
1092
                    current += 1;
 
1093
                MetaphAdd(primary, "T");
 
1094
                MetaphAdd(secondary, "T");
 
1095
                break;
 
1096
 
 
1097
            case 'V':
 
1098
                if (GetAt(original, current + 1) == 'V')
 
1099
                    current += 2;
 
1100
                else
 
1101
                    current += 1;
 
1102
                MetaphAdd(primary, "F");
 
1103
                MetaphAdd(secondary, "F");
 
1104
                break;
 
1105
 
 
1106
            case 'W':
 
1107
                /* can also be in middle of word */
 
1108
                if (StringAt(original, current, 2, "WR", ""))
 
1109
                  {
 
1110
                      MetaphAdd(primary, "R");
 
1111
                      MetaphAdd(secondary, "R");
 
1112
                      current += 2;
 
1113
                      break;
 
1114
                  }
 
1115
 
 
1116
                if ((current == 0)
 
1117
                    && (IsVowel(original, current + 1)
 
1118
                        || StringAt(original, current, 2, "WH", "")))
 
1119
                  {
 
1120
                      /* Wasserman should match Vasserman */
 
1121
                      if (IsVowel(original, current + 1))
 
1122
                        {
 
1123
                            MetaphAdd(primary, "A");
 
1124
                            MetaphAdd(secondary, "F");
 
1125
                        }
 
1126
                      else
 
1127
                        {
 
1128
                            /* need Uomo to match Womo */
 
1129
                            MetaphAdd(primary, "A");
 
1130
                            MetaphAdd(secondary, "A");
 
1131
                        }
 
1132
                  }
 
1133
 
 
1134
                /* Arnow should match Arnoff */
 
1135
                if (((current == last) && IsVowel(original, current - 1))
 
1136
                    || StringAt(original, (current - 1), 5, "EWSKI", "EWSKY",
 
1137
                                "OWSKI", "OWSKY", "")
 
1138
                    || StringAt(original, 0, 3, "SCH", ""))
 
1139
                  {
 
1140
                      MetaphAdd(primary, "");
 
1141
                      MetaphAdd(secondary, "F");
 
1142
                      current += 1;
 
1143
                      break;
 
1144
                  }
 
1145
 
 
1146
                /* polish e.g. 'filipowicz' */
 
1147
                if (StringAt(original, current, 4, "WICZ", "WITZ", ""))
 
1148
                  {
 
1149
                      MetaphAdd(primary, "TS");
 
1150
                      MetaphAdd(secondary, "FX");
 
1151
                      current += 4;
 
1152
                      break;
 
1153
                  }
 
1154
 
 
1155
                /* else skip it */
 
1156
                current += 1;
 
1157
                break;
 
1158
 
 
1159
            case 'X':
 
1160
                /* french e.g. breaux */
 
1161
                if (!((current == last)
 
1162
                      && (StringAt(original, (current - 3), 3, "IAU", "EAU", "")
 
1163
                       || StringAt(original, (current - 2), 2, "AU", "OU", ""))))
 
1164
                  {
 
1165
                      MetaphAdd(primary, "KS");
 
1166
                      MetaphAdd(secondary, "KS");
 
1167
                  }
 
1168
                  
 
1169
 
 
1170
                if (StringAt(original, (current + 1), 1, "C", "X", ""))
 
1171
                    current += 2;
 
1172
                else
 
1173
                    current += 1;
 
1174
                break;
 
1175
 
 
1176
            case 'Z':
 
1177
                /* chinese pinyin e.g. 'zhao' */
 
1178
                if (GetAt(original, current + 1) == 'H')
 
1179
                  {
 
1180
                      MetaphAdd(primary, "J");
 
1181
                      MetaphAdd(secondary, "J");
 
1182
                      current += 2;
 
1183
                      break;
 
1184
                  }
 
1185
                else if (StringAt(original, (current + 1), 2, "ZO", "ZI", "ZA", "")
 
1186
                        || (SlavoGermanic(original)
 
1187
                            && ((current > 0)
 
1188
                                && GetAt(original, current - 1) != 'T')))
 
1189
                  {
 
1190
                      MetaphAdd(primary, "S");
 
1191
                      MetaphAdd(secondary, "TS");
 
1192
                  }
 
1193
                else
 
1194
                  {
 
1195
                    MetaphAdd(primary, "S");
 
1196
                    MetaphAdd(secondary, "S");
 
1197
                  }
 
1198
 
 
1199
                if (GetAt(original, current + 1) == 'Z')
 
1200
                    current += 2;
 
1201
                else
 
1202
                    current += 1;
 
1203
                break;
 
1204
 
 
1205
            default:
 
1206
                current += 1;
 
1207
            }
 
1208
        /* printf("PRIMARY: %s\n", primary->str);
 
1209
        printf("SECONDARY: %s\n", secondary->str);  */
 
1210
      }
 
1211
 
 
1212
 
 
1213
    if (primary->length > 4)
 
1214
        SetAt(primary, 4, '\0');
 
1215
 
 
1216
    if (secondary->length > 4)
 
1217
        SetAt(secondary, 4, '\0');
 
1218
 
 
1219
    *codes = primary->str;
 
1220
    *++codes = secondary->str;
 
1221
 
 
1222
    DestroyMetaString(original);
 
1223
    DestroyMetaString(primary);
 
1224
    DestroyMetaString(secondary);
 
1225
}
 
1226