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
|