mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Remove (char *) casts around string functions where the arguments or result already have the right type and the cast is useless (or worse, potentially casts away a qualifier, but this doesn't appear to be the case here). Reviewed-by: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org> Discussion: https://www.postgresql.org/message-id/flat/fd1fcedb-3492-4fc8-9e3e-74b97f2db6c7%40eisentraut.org
		
			
				
	
	
		
			1439 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1439 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * This is a port of the Double Metaphone algorithm for use in PostgreSQL.
 | 
						|
 *
 | 
						|
 * contrib/fuzzystrmatch/dmetaphone.c
 | 
						|
 *
 | 
						|
 * Double Metaphone computes 2 "sounds like" strings - a primary and an
 | 
						|
 * alternate. In most cases they are the same, but for foreign names
 | 
						|
 * especially they can be a bit different, depending on pronunciation.
 | 
						|
 *
 | 
						|
 * Information on using Double Metaphone can be found at
 | 
						|
 *	 http://www.codeproject.com/string/dmetaphone1.asp
 | 
						|
 * and the original article describing it can be found at
 | 
						|
 *	 http://drdobbs.com/184401251
 | 
						|
 *
 | 
						|
 * For PostgreSQL we provide 2 functions - one for the primary and one for
 | 
						|
 * the alternate. That way the functions are pure text->text mappings that
 | 
						|
 * are useful in functional indexes. These are 'dmetaphone' for the
 | 
						|
 * primary and 'dmetaphone_alt' for the alternate.
 | 
						|
 *
 | 
						|
 * Assuming that dmetaphone.so is in $libdir, the SQL to set up the
 | 
						|
 * functions looks like this:
 | 
						|
 *
 | 
						|
 * CREATE FUNCTION dmetaphone (text) RETURNS text
 | 
						|
 *	  LANGUAGE C IMMUTABLE STRICT
 | 
						|
 *	  AS '$libdir/dmetaphone', 'dmetaphone';
 | 
						|
 *
 | 
						|
 * CREATE FUNCTION dmetaphone_alt (text) RETURNS text
 | 
						|
 *	  LANGUAGE C IMMUTABLE STRICT
 | 
						|
 *	  AS '$libdir/dmetaphone', 'dmetaphone_alt';
 | 
						|
 *
 | 
						|
 * Note that you have to declare the functions IMMUTABLE if you want to
 | 
						|
 * use them in functional indexes, and you have to declare them as STRICT
 | 
						|
 * as they do not check for NULL input, and will segfault if given NULL input.
 | 
						|
 * (See below for alternative ) Declaring them as STRICT means PostgreSQL
 | 
						|
 * will never call them with NULL, but instead assume the result is NULL,
 | 
						|
 * which is what we (I) want.
 | 
						|
 *
 | 
						|
 * Alternatively, compile with -DDMETAPHONE_NOSTRICT and the functions
 | 
						|
 * will detect NULL input and return NULL. The you don't have to declare them
 | 
						|
 * as STRICT.
 | 
						|
 *
 | 
						|
 * There is a small inefficiency here - each function call actually computes
 | 
						|
 * both the primary and the alternate and then throws away the one it doesn't
 | 
						|
 * need. That's the way the perl module was written, because perl can handle
 | 
						|
 * a list return more easily than we can in PostgreSQL. The result has been
 | 
						|
 * fast enough for my needs, but it could maybe be optimized a bit to remove
 | 
						|
 * that behaviour.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
/***************************** COPYRIGHT NOTICES ***********************
 | 
						|
 | 
						|
Most of this code is directly from the Text::DoubleMetaphone perl module
 | 
						|
version 0.05 available from https://www.cpan.org/.
 | 
						|
It bears this copyright notice:
 | 
						|
 | 
						|
 | 
						|
  Copyright 2000, Maurice Aubrey <maurice@hevanet.com>.
 | 
						|
  All rights reserved.
 | 
						|
 | 
						|
  This code is based heavily on the C++ implementation by
 | 
						|
  Lawrence Philips and incorporates several bug fixes courtesy
 | 
						|
  of Kevin Atkinson <kevina@users.sourceforge.net>.
 | 
						|
 | 
						|
  This module is free software; you may redistribute it and/or
 | 
						|
  modify it under the same terms as Perl itself.
 | 
						|
 | 
						|
The remaining code is authored by Andrew Dunstan <amdunstan@ncshp.org> and
 | 
						|
<andrew@dunslane.net> and is covered this copyright:
 | 
						|
 | 
						|
  Copyright 2003, North Carolina State Highway Patrol.
 | 
						|
  All rights reserved.
 | 
						|
 | 
						|
  Permission to use, copy, modify, and distribute this software and its
 | 
						|
  documentation for any purpose, without fee, and without a written agreement
 | 
						|
  is hereby granted, provided that the above copyright notice and this
 | 
						|
  paragraph and the following two paragraphs appear in all copies.
 | 
						|
 | 
						|
  IN NO EVENT SHALL THE NORTH CAROLINA STATE HIGHWAY PATROL BE LIABLE TO ANY
 | 
						|
  PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
 | 
						|
  INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 | 
						|
  DOCUMENTATION, EVEN IF THE NORTH CAROLINA STATE HIGHWAY PATROL HAS BEEN
 | 
						|
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 | 
						|
  THE NORTH CAROLINA STATE HIGHWAY PATROL SPECIFICALLY DISCLAIMS ANY
 | 
						|
  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
						|
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED
 | 
						|
  HEREUNDER IS ON AN "AS IS" BASIS, AND THE NORTH CAROLINA STATE HIGHWAY PATROL
 | 
						|
  HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
 | 
						|
  MODIFICATIONS.
 | 
						|
 | 
						|
***********************************************************************/
 | 
						|
 | 
						|
 | 
						|
/* include these first, according to the docs */
 | 
						|
#ifndef DMETAPHONE_MAIN
 | 
						|
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "utils/builtins.h"
 | 
						|
 | 
						|
/* turn off assertions for embedded function */
 | 
						|
#define NDEBUG
 | 
						|
 | 
						|
#else							/* DMETAPHONE_MAIN */
 | 
						|
 | 
						|
/* we need these if we didn't get them from postgres.h */
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdarg.h>
 | 
						|
 | 
						|
#endif							/* DMETAPHONE_MAIN */
 | 
						|
 | 
						|
#include <assert.h>
 | 
						|
#include <ctype.h>
 | 
						|
 | 
						|
/* prototype for the main function we got from the perl module */
 | 
						|
static void DoubleMetaphone(char *str, char **codes);
 | 
						|
 | 
						|
#ifndef DMETAPHONE_MAIN
 | 
						|
 | 
						|
/*
 | 
						|
 * The PostgreSQL visible dmetaphone function.
 | 
						|
 */
 | 
						|
 | 
						|
PG_FUNCTION_INFO_V1(dmetaphone);
 | 
						|
 | 
						|
Datum
 | 
						|
dmetaphone(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	text	   *arg;
 | 
						|
	char	   *aptr,
 | 
						|
			   *codes[2],
 | 
						|
			   *code;
 | 
						|
 | 
						|
#ifdef DMETAPHONE_NOSTRICT
 | 
						|
	if (PG_ARGISNULL(0))
 | 
						|
		PG_RETURN_NULL();
 | 
						|
#endif
 | 
						|
	arg = PG_GETARG_TEXT_PP(0);
 | 
						|
	aptr = text_to_cstring(arg);
 | 
						|
 | 
						|
	DoubleMetaphone(aptr, codes);
 | 
						|
	code = codes[0];
 | 
						|
	if (!code)
 | 
						|
		code = "";
 | 
						|
 | 
						|
	PG_RETURN_TEXT_P(cstring_to_text(code));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The PostgreSQL visible dmetaphone_alt function.
 | 
						|
 */
 | 
						|
 | 
						|
PG_FUNCTION_INFO_V1(dmetaphone_alt);
 | 
						|
 | 
						|
Datum
 | 
						|
dmetaphone_alt(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	text	   *arg;
 | 
						|
	char	   *aptr,
 | 
						|
			   *codes[2],
 | 
						|
			   *code;
 | 
						|
 | 
						|
#ifdef DMETAPHONE_NOSTRICT
 | 
						|
	if (PG_ARGISNULL(0))
 | 
						|
		PG_RETURN_NULL();
 | 
						|
#endif
 | 
						|
	arg = PG_GETARG_TEXT_PP(0);
 | 
						|
	aptr = text_to_cstring(arg);
 | 
						|
 | 
						|
	DoubleMetaphone(aptr, codes);
 | 
						|
	code = codes[1];
 | 
						|
	if (!code)
 | 
						|
		code = "";
 | 
						|
 | 
						|
	PG_RETURN_TEXT_P(cstring_to_text(code));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* here is where we start the code imported from the perl module */
 | 
						|
 | 
						|
/* all memory handling is done with these macros */
 | 
						|
 | 
						|
#define META_MALLOC(v,n,t) \
 | 
						|
		  (v = (t*)palloc(((n)*sizeof(t))))
 | 
						|
 | 
						|
#define META_REALLOC(v,n,t) \
 | 
						|
					  (v = (t*)repalloc((v),((n)*sizeof(t))))
 | 
						|
 | 
						|
/*
 | 
						|
 * Don't do pfree - it seems to cause a SIGSEGV sometimes - which might have just
 | 
						|
 * been caused by reloading the module in development.
 | 
						|
 * So we rely on context cleanup - Tom Lane says pfree shouldn't be necessary
 | 
						|
 * in a case like this.
 | 
						|
 */
 | 
						|
 | 
						|
#define META_FREE(x) ((void)true)	/* pfree((x)) */
 | 
						|
#else							/* not defined DMETAPHONE_MAIN */
 | 
						|
 | 
						|
/* use the standard malloc library when not running in PostgreSQL */
 | 
						|
 | 
						|
#define META_MALLOC(v,n,t) \
 | 
						|
		  (v = (t*)malloc(((n)*sizeof(t))))
 | 
						|
 | 
						|
#define META_REALLOC(v,n,t) \
 | 
						|
					  (v = (t*)realloc((v),((n)*sizeof(t))))
 | 
						|
 | 
						|
#define META_FREE(x) free((x))
 | 
						|
#endif							/* defined DMETAPHONE_MAIN */
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* this typedef was originally in the perl module's .h file */
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
	char	   *str;
 | 
						|
	int			length;
 | 
						|
	int			bufsize;
 | 
						|
	int			free_string_on_destroy;
 | 
						|
}
 | 
						|
 | 
						|
metastring;
 | 
						|
 | 
						|
/*
 | 
						|
 * remaining perl module funcs unchanged except for declaring them static
 | 
						|
 * and reformatting to PostgreSQL indentation and to fit in 80 cols.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
static metastring *
 | 
						|
NewMetaString(const char *init_str)
 | 
						|
{
 | 
						|
	metastring *s;
 | 
						|
	char		empty_string[] = "";
 | 
						|
 | 
						|
	META_MALLOC(s, 1, metastring);
 | 
						|
	assert(s != NULL);
 | 
						|
 | 
						|
	if (init_str == NULL)
 | 
						|
		init_str = empty_string;
 | 
						|
	s->length = strlen(init_str);
 | 
						|
	/* preallocate a bit more for potential growth */
 | 
						|
	s->bufsize = s->length + 7;
 | 
						|
 | 
						|
	META_MALLOC(s->str, s->bufsize, char);
 | 
						|
	assert(s->str != NULL);
 | 
						|
 | 
						|
	memcpy(s->str, init_str, s->length + 1);
 | 
						|
	s->free_string_on_destroy = 1;
 | 
						|
 | 
						|
	return s;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
DestroyMetaString(metastring *s)
 | 
						|
{
 | 
						|
	if (s == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (s->free_string_on_destroy && (s->str != NULL))
 | 
						|
		META_FREE(s->str);
 | 
						|
 | 
						|
	META_FREE(s);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
IncreaseBuffer(metastring *s, int chars_needed)
 | 
						|
{
 | 
						|
	META_REALLOC(s->str, (s->bufsize + chars_needed + 10), char);
 | 
						|
	assert(s->str != NULL);
 | 
						|
	s->bufsize = s->bufsize + chars_needed + 10;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
MakeUpper(metastring *s)
 | 
						|
{
 | 
						|
	char	   *i;
 | 
						|
 | 
						|
	for (i = s->str; *i; i++)
 | 
						|
		*i = toupper((unsigned char) *i);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
IsVowel(metastring *s, int pos)
 | 
						|
{
 | 
						|
	char		c;
 | 
						|
 | 
						|
	if ((pos < 0) || (pos >= s->length))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	c = *(s->str + pos);
 | 
						|
	if ((c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') ||
 | 
						|
		(c == 'U') || (c == 'Y'))
 | 
						|
		return 1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
SlavoGermanic(metastring *s)
 | 
						|
{
 | 
						|
	if (strstr(s->str, "W"))
 | 
						|
		return 1;
 | 
						|
	else if (strstr(s->str, "K"))
 | 
						|
		return 1;
 | 
						|
	else if (strstr(s->str, "CZ"))
 | 
						|
		return 1;
 | 
						|
	else if (strstr(s->str, "WITZ"))
 | 
						|
		return 1;
 | 
						|
	else
 | 
						|
		return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static char
 | 
						|
GetAt(metastring *s, int pos)
 | 
						|
{
 | 
						|
	if ((pos < 0) || (pos >= s->length))
 | 
						|
		return '\0';
 | 
						|
 | 
						|
	return ((char) *(s->str + pos));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
SetAt(metastring *s, int pos, char c)
 | 
						|
{
 | 
						|
	if ((pos < 0) || (pos >= s->length))
 | 
						|
		return;
 | 
						|
 | 
						|
	*(s->str + pos) = c;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
   Caveats: the START value is 0 based
 | 
						|
*/
 | 
						|
static int
 | 
						|
StringAt(metastring *s, int start, int length,...)
 | 
						|
{
 | 
						|
	char	   *test;
 | 
						|
	char	   *pos;
 | 
						|
	va_list		ap;
 | 
						|
 | 
						|
	if ((start < 0) || (start >= s->length))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	pos = (s->str + start);
 | 
						|
	va_start(ap, length);
 | 
						|
 | 
						|
	do
 | 
						|
	{
 | 
						|
		test = va_arg(ap, char *);
 | 
						|
		if (*test && (strncmp(pos, test, length) == 0))
 | 
						|
		{
 | 
						|
			va_end(ap);
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	while (strcmp(test, "") != 0);
 | 
						|
 | 
						|
	va_end(ap);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
MetaphAdd(metastring *s, const char *new_str)
 | 
						|
{
 | 
						|
	int			add_length;
 | 
						|
 | 
						|
	if (new_str == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	add_length = strlen(new_str);
 | 
						|
	if ((s->length + add_length) > (s->bufsize - 1))
 | 
						|
		IncreaseBuffer(s, add_length);
 | 
						|
 | 
						|
	strcat(s->str, new_str);
 | 
						|
	s->length += add_length;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
DoubleMetaphone(char *str, char **codes)
 | 
						|
{
 | 
						|
	int			length;
 | 
						|
	metastring *original;
 | 
						|
	metastring *primary;
 | 
						|
	metastring *secondary;
 | 
						|
	int			current;
 | 
						|
	int			last;
 | 
						|
 | 
						|
	current = 0;
 | 
						|
	/* we need the real length and last prior to padding */
 | 
						|
	length = strlen(str);
 | 
						|
	last = length - 1;
 | 
						|
	original = NewMetaString(str);
 | 
						|
	/* Pad original so we can index beyond end */
 | 
						|
	MetaphAdd(original, "     ");
 | 
						|
 | 
						|
	primary = NewMetaString("");
 | 
						|
	secondary = NewMetaString("");
 | 
						|
	primary->free_string_on_destroy = 0;
 | 
						|
	secondary->free_string_on_destroy = 0;
 | 
						|
 | 
						|
	MakeUpper(original);
 | 
						|
 | 
						|
	/* skip these when at start of word */
 | 
						|
	if (StringAt(original, 0, 2, "GN", "KN", "PN", "WR", "PS", ""))
 | 
						|
		current += 1;
 | 
						|
 | 
						|
	/* Initial 'X' is pronounced 'Z' e.g. 'Xavier' */
 | 
						|
	if (GetAt(original, 0) == 'X')
 | 
						|
	{
 | 
						|
		MetaphAdd(primary, "S");	/* 'Z' maps to 'S' */
 | 
						|
		MetaphAdd(secondary, "S");
 | 
						|
		current += 1;
 | 
						|
	}
 | 
						|
 | 
						|
	/* main loop */
 | 
						|
	while ((primary->length < 4) || (secondary->length < 4))
 | 
						|
	{
 | 
						|
		if (current >= length)
 | 
						|
			break;
 | 
						|
 | 
						|
		switch (GetAt(original, current))
 | 
						|
		{
 | 
						|
			case 'A':
 | 
						|
			case 'E':
 | 
						|
			case 'I':
 | 
						|
			case 'O':
 | 
						|
			case 'U':
 | 
						|
			case 'Y':
 | 
						|
				if (current == 0)
 | 
						|
				{
 | 
						|
					/* all init vowels now map to 'A' */
 | 
						|
					MetaphAdd(primary, "A");
 | 
						|
					MetaphAdd(secondary, "A");
 | 
						|
				}
 | 
						|
				current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'B':
 | 
						|
 | 
						|
				/* "-mb", e.g", "dumb", already skipped over... */
 | 
						|
				MetaphAdd(primary, "P");
 | 
						|
				MetaphAdd(secondary, "P");
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'B')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case '\xc7':		/* C with cedilla */
 | 
						|
				MetaphAdd(primary, "S");
 | 
						|
				MetaphAdd(secondary, "S");
 | 
						|
				current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'C':
 | 
						|
				/* various germanic */
 | 
						|
				if ((current > 1)
 | 
						|
					&& !IsVowel(original, current - 2)
 | 
						|
					&& StringAt(original, (current - 1), 3, "ACH", "")
 | 
						|
					&& ((GetAt(original, current + 2) != 'I')
 | 
						|
						&& ((GetAt(original, current + 2) != 'E')
 | 
						|
							|| StringAt(original, (current - 2), 6, "BACHER",
 | 
						|
										"MACHER", ""))))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "K");
 | 
						|
					MetaphAdd(secondary, "K");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* special case 'caesar' */
 | 
						|
				if ((current == 0)
 | 
						|
					&& StringAt(original, current, 6, "CAESAR", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "S");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* italian 'chianti' */
 | 
						|
				if (StringAt(original, current, 4, "CHIA", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "K");
 | 
						|
					MetaphAdd(secondary, "K");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "CH", ""))
 | 
						|
				{
 | 
						|
					/* find 'michael' */
 | 
						|
					if ((current > 0)
 | 
						|
						&& StringAt(original, current, 4, "CHAE", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "X");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
 | 
						|
					/* greek roots e.g. 'chemistry', 'chorus' */
 | 
						|
					if ((current == 0)
 | 
						|
						&& (StringAt(original, (current + 1), 5,
 | 
						|
									 "HARAC", "HARIS", "")
 | 
						|
							|| StringAt(original, (current + 1), 3, "HOR",
 | 
						|
										"HYM", "HIA", "HEM", ""))
 | 
						|
						&& !StringAt(original, 0, 5, "CHORE", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "K");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
 | 
						|
					/* germanic, greek, or otherwise 'ch' for 'kh' sound */
 | 
						|
					if ((StringAt(original, 0, 4, "VAN ", "VON ", "")
 | 
						|
						 || StringAt(original, 0, 3, "SCH", ""))
 | 
						|
					/* 'architect but not 'arch', 'orchestra', 'orchid' */
 | 
						|
						|| StringAt(original, (current - 2), 6, "ORCHES",
 | 
						|
									"ARCHIT", "ORCHID", "")
 | 
						|
						|| StringAt(original, (current + 2), 1, "T", "S",
 | 
						|
									"")
 | 
						|
						|| ((StringAt(original, (current - 1), 1,
 | 
						|
									  "A", "O", "U", "E", "")
 | 
						|
							 || (current == 0))
 | 
						|
 | 
						|
					/*
 | 
						|
					 * e.g., 'wachtler', 'wechsler', but not 'tichner'
 | 
						|
					 */
 | 
						|
							&& StringAt(original, (current + 2), 1, "L", "R",
 | 
						|
										"N", "M", "B", "H", "F", "V", "W",
 | 
						|
										" ", "")))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "K");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						if (current > 0)
 | 
						|
						{
 | 
						|
							if (StringAt(original, 0, 2, "MC", ""))
 | 
						|
							{
 | 
						|
								/* e.g., "McHugh" */
 | 
						|
								MetaphAdd(primary, "K");
 | 
						|
								MetaphAdd(secondary, "K");
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "X");
 | 
						|
								MetaphAdd(secondary, "K");
 | 
						|
							}
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "X");
 | 
						|
							MetaphAdd(secondary, "X");
 | 
						|
						}
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				/* e.g, 'czerny' */
 | 
						|
				if (StringAt(original, current, 2, "CZ", "")
 | 
						|
					&& !StringAt(original, (current - 2), 4, "WICZ", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "X");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* e.g., 'focaccia' */
 | 
						|
				if (StringAt(original, (current + 1), 3, "CIA", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "X");
 | 
						|
					MetaphAdd(secondary, "X");
 | 
						|
					current += 3;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* double 'C', but not if e.g. 'McClellan' */
 | 
						|
				if (StringAt(original, current, 2, "CC", "")
 | 
						|
					&& !((current == 1) && (GetAt(original, 0) == 'M')))
 | 
						|
				{
 | 
						|
					/* 'bellocchio' but not 'bacchus' */
 | 
						|
					if (StringAt(original, (current + 2), 1, "I", "E", "H", "")
 | 
						|
						&& !StringAt(original, (current + 2), 2, "HU", ""))
 | 
						|
					{
 | 
						|
						/* 'accident', 'accede' 'succeed' */
 | 
						|
						if (((current == 1)
 | 
						|
							 && (GetAt(original, current - 1) == 'A'))
 | 
						|
							|| StringAt(original, (current - 1), 5, "UCCEE",
 | 
						|
										"UCCES", ""))
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "KS");
 | 
						|
							MetaphAdd(secondary, "KS");
 | 
						|
							/* 'bacci', 'bertucci', other italian */
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "X");
 | 
						|
							MetaphAdd(secondary, "X");
 | 
						|
						}
 | 
						|
						current += 3;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{			/* Pierce's rule */
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "K");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "CK", "CG", "CQ", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "K");
 | 
						|
					MetaphAdd(secondary, "K");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "CI", "CE", "CY", ""))
 | 
						|
				{
 | 
						|
					/* italian vs. english */
 | 
						|
					if (StringAt
 | 
						|
						(original, current, 3, "CIO", "CIE", "CIA", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "X");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "S");
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* else */
 | 
						|
				MetaphAdd(primary, "K");
 | 
						|
				MetaphAdd(secondary, "K");
 | 
						|
 | 
						|
				/* name sent in 'mac caffrey', 'mac gregor */
 | 
						|
				if (StringAt(original, (current + 1), 2, " C", " Q", " G", ""))
 | 
						|
					current += 3;
 | 
						|
				else if (StringAt(original, (current + 1), 1, "C", "K", "Q", "")
 | 
						|
						 && !StringAt(original, (current + 1), 2,
 | 
						|
									  "CE", "CI", ""))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'D':
 | 
						|
				if (StringAt(original, current, 2, "DG", ""))
 | 
						|
				{
 | 
						|
					if (StringAt(original, (current + 2), 1,
 | 
						|
								 "I", "E", "Y", ""))
 | 
						|
					{
 | 
						|
						/* e.g. 'edge' */
 | 
						|
						MetaphAdd(primary, "J");
 | 
						|
						MetaphAdd(secondary, "J");
 | 
						|
						current += 3;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						/* e.g. 'edgar' */
 | 
						|
						MetaphAdd(primary, "TK");
 | 
						|
						MetaphAdd(secondary, "TK");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "DT", "DD", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "T");
 | 
						|
					MetaphAdd(secondary, "T");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* else */
 | 
						|
				MetaphAdd(primary, "T");
 | 
						|
				MetaphAdd(secondary, "T");
 | 
						|
				current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'F':
 | 
						|
				if (GetAt(original, current + 1) == 'F')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "F");
 | 
						|
				MetaphAdd(secondary, "F");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'G':
 | 
						|
				if (GetAt(original, current + 1) == 'H')
 | 
						|
				{
 | 
						|
					if ((current > 0) && !IsVowel(original, current - 1))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "K");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
 | 
						|
					if (current < 3)
 | 
						|
					{
 | 
						|
						/* 'ghislane', ghiradelli */
 | 
						|
						if (current == 0)
 | 
						|
						{
 | 
						|
							if (GetAt(original, current + 2) == 'I')
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "J");
 | 
						|
								MetaphAdd(secondary, "J");
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "K");
 | 
						|
								MetaphAdd(secondary, "K");
 | 
						|
							}
 | 
						|
							current += 2;
 | 
						|
							break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					/*
 | 
						|
					 * Parker's rule (with some further refinements) - e.g.,
 | 
						|
					 * 'hugh'
 | 
						|
					 */
 | 
						|
					if (((current > 1)
 | 
						|
						 && StringAt(original, (current - 2), 1,
 | 
						|
									 "B", "H", "D", ""))
 | 
						|
					/* e.g., 'bough' */
 | 
						|
						|| ((current > 2)
 | 
						|
							&& StringAt(original, (current - 3), 1,
 | 
						|
										"B", "H", "D", ""))
 | 
						|
					/* e.g., 'broughton' */
 | 
						|
						|| ((current > 3)
 | 
						|
							&& StringAt(original, (current - 4), 1,
 | 
						|
										"B", "H", "")))
 | 
						|
					{
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						/*
 | 
						|
						 * e.g., 'laugh', 'McLaughlin', 'cough', 'gough',
 | 
						|
						 * 'rough', 'tough'
 | 
						|
						 */
 | 
						|
						if ((current > 2)
 | 
						|
							&& (GetAt(original, current - 1) == 'U')
 | 
						|
							&& StringAt(original, (current - 3), 1, "C",
 | 
						|
										"G", "L", "R", "T", ""))
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "F");
 | 
						|
							MetaphAdd(secondary, "F");
 | 
						|
						}
 | 
						|
						else if ((current > 0)
 | 
						|
								 && GetAt(original, current - 1) != 'I')
 | 
						|
						{
 | 
						|
 | 
						|
 | 
						|
							MetaphAdd(primary, "K");
 | 
						|
							MetaphAdd(secondary, "K");
 | 
						|
						}
 | 
						|
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'N')
 | 
						|
				{
 | 
						|
					if ((current == 1) && IsVowel(original, 0)
 | 
						|
						&& !SlavoGermanic(original))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "KN");
 | 
						|
						MetaphAdd(secondary, "N");
 | 
						|
					}
 | 
						|
					else
 | 
						|
						/* not e.g. 'cagney' */
 | 
						|
						if (!StringAt(original, (current + 2), 2, "EY", "")
 | 
						|
							&& (GetAt(original, current + 1) != 'Y')
 | 
						|
							&& !SlavoGermanic(original))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "N");
 | 
						|
						MetaphAdd(secondary, "KN");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "KN");
 | 
						|
						MetaphAdd(secondary, "KN");
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* 'tagliaro' */
 | 
						|
				if (StringAt(original, (current + 1), 2, "LI", "")
 | 
						|
					&& !SlavoGermanic(original))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "KL");
 | 
						|
					MetaphAdd(secondary, "L");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* -ges-,-gep-,-gel-, -gie- at beginning */
 | 
						|
				if ((current == 0)
 | 
						|
					&& ((GetAt(original, current + 1) == 'Y')
 | 
						|
						|| StringAt(original, (current + 1), 2, "ES", "EP",
 | 
						|
									"EB", "EL", "EY", "IB", "IL", "IN", "IE",
 | 
						|
									"EI", "ER", "")))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "K");
 | 
						|
					MetaphAdd(secondary, "J");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* -ger-,  -gy- */
 | 
						|
				if ((StringAt(original, (current + 1), 2, "ER", "")
 | 
						|
					 || (GetAt(original, current + 1) == 'Y'))
 | 
						|
					&& !StringAt(original, 0, 6,
 | 
						|
								 "DANGER", "RANGER", "MANGER", "")
 | 
						|
					&& !StringAt(original, (current - 1), 1, "E", "I", "")
 | 
						|
					&& !StringAt(original, (current - 1), 3, "RGY", "OGY", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "K");
 | 
						|
					MetaphAdd(secondary, "J");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* italian e.g, 'biaggi' */
 | 
						|
				if (StringAt(original, (current + 1), 1, "E", "I", "Y", "")
 | 
						|
					|| StringAt(original, (current - 1), 4,
 | 
						|
								"AGGI", "OGGI", ""))
 | 
						|
				{
 | 
						|
					/* obvious germanic */
 | 
						|
					if ((StringAt(original, 0, 4, "VAN ", "VON ", "")
 | 
						|
						 || StringAt(original, 0, 3, "SCH", ""))
 | 
						|
						|| StringAt(original, (current + 1), 2, "ET", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "K");
 | 
						|
						MetaphAdd(secondary, "K");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						/* always soft if french ending */
 | 
						|
						if (StringAt
 | 
						|
							(original, (current + 1), 4, "IER ", ""))
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "J");
 | 
						|
							MetaphAdd(secondary, "J");
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "J");
 | 
						|
							MetaphAdd(secondary, "K");
 | 
						|
						}
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'G')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "K");
 | 
						|
				MetaphAdd(secondary, "K");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'H':
 | 
						|
				/* only keep if first & before vowel or btw. 2 vowels */
 | 
						|
				if (((current == 0) || IsVowel(original, current - 1))
 | 
						|
					&& IsVowel(original, current + 1))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "H");
 | 
						|
					MetaphAdd(secondary, "H");
 | 
						|
					current += 2;
 | 
						|
				}
 | 
						|
				else
 | 
						|
					/* also takes care of 'HH' */
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'J':
 | 
						|
				/* obvious spanish, 'jose', 'san jacinto' */
 | 
						|
				if (StringAt(original, current, 4, "JOSE", "")
 | 
						|
					|| StringAt(original, 0, 4, "SAN ", ""))
 | 
						|
				{
 | 
						|
					if (((current == 0)
 | 
						|
						 && (GetAt(original, current + 4) == ' '))
 | 
						|
						|| StringAt(original, 0, 4, "SAN ", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "H");
 | 
						|
						MetaphAdd(secondary, "H");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "J");
 | 
						|
						MetaphAdd(secondary, "H");
 | 
						|
					}
 | 
						|
					current += 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if ((current == 0)
 | 
						|
					&& !StringAt(original, current, 4, "JOSE", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "J");	/* Yankelovich/Jankelowicz */
 | 
						|
					MetaphAdd(secondary, "A");
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					/* spanish pron. of e.g. 'bajador' */
 | 
						|
					if (IsVowel(original, current - 1)
 | 
						|
						&& !SlavoGermanic(original)
 | 
						|
						&& ((GetAt(original, current + 1) == 'A')
 | 
						|
							|| (GetAt(original, current + 1) == 'O')))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "J");
 | 
						|
						MetaphAdd(secondary, "H");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						if (current == last)
 | 
						|
						{
 | 
						|
							MetaphAdd(primary, "J");
 | 
						|
							MetaphAdd(secondary, "");
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							if (!StringAt(original, (current + 1), 1, "L", "T",
 | 
						|
										  "K", "S", "N", "M", "B", "Z", "")
 | 
						|
								&& !StringAt(original, (current - 1), 1,
 | 
						|
											 "S", "K", "L", ""))
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "J");
 | 
						|
								MetaphAdd(secondary, "J");
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'J')	/* it could happen! */
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'K':
 | 
						|
				if (GetAt(original, current + 1) == 'K')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "K");
 | 
						|
				MetaphAdd(secondary, "K");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'L':
 | 
						|
				if (GetAt(original, current + 1) == 'L')
 | 
						|
				{
 | 
						|
					/* spanish e.g. 'cabrillo', 'gallegos' */
 | 
						|
					if (((current == (length - 3))
 | 
						|
						 && StringAt(original, (current - 1), 4, "ILLO",
 | 
						|
									 "ILLA", "ALLE", ""))
 | 
						|
						|| ((StringAt(original, (last - 1), 2, "AS", "OS", "")
 | 
						|
							 || StringAt(original, last, 1, "A", "O", ""))
 | 
						|
							&& StringAt(original, (current - 1), 4,
 | 
						|
										"ALLE", "")))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "L");
 | 
						|
						MetaphAdd(secondary, "");
 | 
						|
						current += 2;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
				}
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "L");
 | 
						|
				MetaphAdd(secondary, "L");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'M':
 | 
						|
				if ((StringAt(original, (current - 1), 3, "UMB", "")
 | 
						|
					 && (((current + 1) == last)
 | 
						|
						 || StringAt(original, (current + 2), 2, "ER", "")))
 | 
						|
				/* 'dumb','thumb' */
 | 
						|
					|| (GetAt(original, current + 1) == 'M'))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "M");
 | 
						|
				MetaphAdd(secondary, "M");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'N':
 | 
						|
				if (GetAt(original, current + 1) == 'N')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "N");
 | 
						|
				MetaphAdd(secondary, "N");
 | 
						|
				break;
 | 
						|
 | 
						|
			case '\xd1':		/* N with tilde */
 | 
						|
				current += 1;
 | 
						|
				MetaphAdd(primary, "N");
 | 
						|
				MetaphAdd(secondary, "N");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'P':
 | 
						|
				if (GetAt(original, current + 1) == 'H')
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "F");
 | 
						|
					MetaphAdd(secondary, "F");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* also account for "campbell", "raspberry" */
 | 
						|
				if (StringAt(original, (current + 1), 1, "P", "B", ""))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "P");
 | 
						|
				MetaphAdd(secondary, "P");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'Q':
 | 
						|
				if (GetAt(original, current + 1) == 'Q')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "K");
 | 
						|
				MetaphAdd(secondary, "K");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'R':
 | 
						|
				/* french e.g. 'rogier', but exclude 'hochmeier' */
 | 
						|
				if ((current == last)
 | 
						|
					&& !SlavoGermanic(original)
 | 
						|
					&& StringAt(original, (current - 2), 2, "IE", "")
 | 
						|
					&& !StringAt(original, (current - 4), 2, "ME", "MA", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "");
 | 
						|
					MetaphAdd(secondary, "R");
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "R");
 | 
						|
					MetaphAdd(secondary, "R");
 | 
						|
				}
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'R')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'S':
 | 
						|
				/* special cases 'island', 'isle', 'carlisle', 'carlysle' */
 | 
						|
				if (StringAt(original, (current - 1), 3, "ISL", "YSL", ""))
 | 
						|
				{
 | 
						|
					current += 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* special case 'sugar-' */
 | 
						|
				if ((current == 0)
 | 
						|
					&& StringAt(original, current, 5, "SUGAR", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "X");
 | 
						|
					MetaphAdd(secondary, "S");
 | 
						|
					current += 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "SH", ""))
 | 
						|
				{
 | 
						|
					/* germanic */
 | 
						|
					if (StringAt
 | 
						|
						(original, (current + 1), 4, "HEIM", "HOEK", "HOLM",
 | 
						|
						 "HOLZ", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "S");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "X");
 | 
						|
						MetaphAdd(secondary, "X");
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* italian & armenian */
 | 
						|
				if (StringAt(original, current, 3, "SIO", "SIA", "")
 | 
						|
					|| StringAt(original, current, 4, "SIAN", ""))
 | 
						|
				{
 | 
						|
					if (!SlavoGermanic(original))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "X");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "S");
 | 
						|
					}
 | 
						|
					current += 3;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/*
 | 
						|
				 * german & anglicisations, e.g. 'smith' match 'schmidt',
 | 
						|
				 * 'snider' match 'schneider' also, -sz- in slavic language
 | 
						|
				 * although in hungarian it is pronounced 's'
 | 
						|
				 */
 | 
						|
				if (((current == 0)
 | 
						|
					 && StringAt(original, (current + 1), 1,
 | 
						|
								 "M", "N", "L", "W", ""))
 | 
						|
					|| StringAt(original, (current + 1), 1, "Z", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "X");
 | 
						|
					if (StringAt(original, (current + 1), 1, "Z", ""))
 | 
						|
						current += 2;
 | 
						|
					else
 | 
						|
						current += 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "SC", ""))
 | 
						|
				{
 | 
						|
					/* Schlesinger's rule */
 | 
						|
					if (GetAt(original, current + 2) == 'H')
 | 
						|
					{
 | 
						|
						/* dutch origin, e.g. 'school', 'schooner' */
 | 
						|
						if (StringAt(original, (current + 3), 2,
 | 
						|
									 "OO", "ER", "EN",
 | 
						|
									 "UY", "ED", "EM", ""))
 | 
						|
						{
 | 
						|
							/* 'schermerhorn', 'schenker' */
 | 
						|
							if (StringAt(original, (current + 3), 2,
 | 
						|
										 "ER", "EN", ""))
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "X");
 | 
						|
								MetaphAdd(secondary, "SK");
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "SK");
 | 
						|
								MetaphAdd(secondary, "SK");
 | 
						|
							}
 | 
						|
							current += 3;
 | 
						|
							break;
 | 
						|
						}
 | 
						|
						else
 | 
						|
						{
 | 
						|
							if ((current == 0) && !IsVowel(original, 3)
 | 
						|
								&& (GetAt(original, 3) != 'W'))
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "X");
 | 
						|
								MetaphAdd(secondary, "S");
 | 
						|
							}
 | 
						|
							else
 | 
						|
							{
 | 
						|
								MetaphAdd(primary, "X");
 | 
						|
								MetaphAdd(secondary, "X");
 | 
						|
							}
 | 
						|
							current += 3;
 | 
						|
							break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					if (StringAt(original, (current + 2), 1,
 | 
						|
								 "I", "E", "Y", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "S");
 | 
						|
						MetaphAdd(secondary, "S");
 | 
						|
						current += 3;
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					/* else */
 | 
						|
					MetaphAdd(primary, "SK");
 | 
						|
					MetaphAdd(secondary, "SK");
 | 
						|
					current += 3;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* french e.g. 'resnais', 'artois' */
 | 
						|
				if ((current == last)
 | 
						|
					&& StringAt(original, (current - 2), 2, "AI", "OI", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "");
 | 
						|
					MetaphAdd(secondary, "S");
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "S");
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, (current + 1), 1, "S", "Z", ""))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'T':
 | 
						|
				if (StringAt(original, current, 4, "TION", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "X");
 | 
						|
					MetaphAdd(secondary, "X");
 | 
						|
					current += 3;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 3, "TIA", "TCH", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "X");
 | 
						|
					MetaphAdd(secondary, "X");
 | 
						|
					current += 3;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, current, 2, "TH", "")
 | 
						|
					|| StringAt(original, current, 3, "TTH", ""))
 | 
						|
				{
 | 
						|
					/* special case 'thomas', 'thames' or germanic */
 | 
						|
					if (StringAt(original, (current + 2), 2, "OM", "AM", "")
 | 
						|
						|| StringAt(original, 0, 4, "VAN ", "VON ", "")
 | 
						|
						|| StringAt(original, 0, 3, "SCH", ""))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "T");
 | 
						|
						MetaphAdd(secondary, "T");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "0");
 | 
						|
						MetaphAdd(secondary, "T");
 | 
						|
					}
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if (StringAt(original, (current + 1), 1, "T", "D", ""))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "T");
 | 
						|
				MetaphAdd(secondary, "T");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'V':
 | 
						|
				if (GetAt(original, current + 1) == 'V')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				MetaphAdd(primary, "F");
 | 
						|
				MetaphAdd(secondary, "F");
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'W':
 | 
						|
				/* can also be in middle of word */
 | 
						|
				if (StringAt(original, current, 2, "WR", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "R");
 | 
						|
					MetaphAdd(secondary, "R");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				if ((current == 0)
 | 
						|
					&& (IsVowel(original, current + 1)
 | 
						|
						|| StringAt(original, current, 2, "WH", "")))
 | 
						|
				{
 | 
						|
					/* Wasserman should match Vasserman */
 | 
						|
					if (IsVowel(original, current + 1))
 | 
						|
					{
 | 
						|
						MetaphAdd(primary, "A");
 | 
						|
						MetaphAdd(secondary, "F");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						/* need Uomo to match Womo */
 | 
						|
						MetaphAdd(primary, "A");
 | 
						|
						MetaphAdd(secondary, "A");
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				/* Arnow should match Arnoff */
 | 
						|
				if (((current == last) && IsVowel(original, current - 1))
 | 
						|
					|| StringAt(original, (current - 1), 5, "EWSKI", "EWSKY",
 | 
						|
								"OWSKI", "OWSKY", "")
 | 
						|
					|| StringAt(original, 0, 3, "SCH", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "");
 | 
						|
					MetaphAdd(secondary, "F");
 | 
						|
					current += 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* polish e.g. 'filipowicz' */
 | 
						|
				if (StringAt(original, current, 4, "WICZ", "WITZ", ""))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "TS");
 | 
						|
					MetaphAdd(secondary, "FX");
 | 
						|
					current += 4;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				/* else skip it */
 | 
						|
				current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'X':
 | 
						|
				/* french e.g. breaux */
 | 
						|
				if (!((current == last)
 | 
						|
					  && (StringAt(original, (current - 3), 3,
 | 
						|
								   "IAU", "EAU", "")
 | 
						|
						  || StringAt(original, (current - 2), 2,
 | 
						|
									  "AU", "OU", ""))))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "KS");
 | 
						|
					MetaphAdd(secondary, "KS");
 | 
						|
				}
 | 
						|
 | 
						|
 | 
						|
				if (StringAt(original, (current + 1), 1, "C", "X", ""))
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			case 'Z':
 | 
						|
				/* chinese pinyin e.g. 'zhao' */
 | 
						|
				if (GetAt(original, current + 1) == 'H')
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "J");
 | 
						|
					MetaphAdd(secondary, "J");
 | 
						|
					current += 2;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				else if (StringAt(original, (current + 1), 2,
 | 
						|
								  "ZO", "ZI", "ZA", "")
 | 
						|
						 || (SlavoGermanic(original)
 | 
						|
							 && ((current > 0)
 | 
						|
								 && GetAt(original, current - 1) != 'T')))
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "TS");
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					MetaphAdd(primary, "S");
 | 
						|
					MetaphAdd(secondary, "S");
 | 
						|
				}
 | 
						|
 | 
						|
				if (GetAt(original, current + 1) == 'Z')
 | 
						|
					current += 2;
 | 
						|
				else
 | 
						|
					current += 1;
 | 
						|
				break;
 | 
						|
 | 
						|
			default:
 | 
						|
				current += 1;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * printf("PRIMARY: %s\n", primary->str); printf("SECONDARY: %s\n",
 | 
						|
		 * secondary->str);
 | 
						|
		 */
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	if (primary->length > 4)
 | 
						|
		SetAt(primary, 4, '\0');
 | 
						|
 | 
						|
	if (secondary->length > 4)
 | 
						|
		SetAt(secondary, 4, '\0');
 | 
						|
 | 
						|
	*codes = primary->str;
 | 
						|
	*++codes = secondary->str;
 | 
						|
 | 
						|
	DestroyMetaString(original);
 | 
						|
	DestroyMetaString(primary);
 | 
						|
	DestroyMetaString(secondary);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DMETAPHONE_MAIN
 | 
						|
 | 
						|
/* just for testing - not part of the perl code */
 | 
						|
 | 
						|
main(int argc, char **argv)
 | 
						|
{
 | 
						|
	char	   *codes[2];
 | 
						|
 | 
						|
	if (argc > 1)
 | 
						|
	{
 | 
						|
		DoubleMetaphone(argv[1], codes);
 | 
						|
		printf("%s|%s\n", codes[0], codes[1]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#endif
 |