Header And Logo

PostgreSQL
| The world's most advanced open source database.

Data Structures | Defines | Functions

dmetaphone.c File Reference

#include "postgres.h"
#include "utils/builtins.h"
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
Include dependency graph for dmetaphone.c:

Go to the source code of this file.

Data Structures

struct  metastring

Defines

#define NDEBUG
#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))))
#define META_FREE(x)

Functions

Datum dmetaphone (PG_FUNCTION_ARGS)
Datum dmetaphone_alt (PG_FUNCTION_ARGS)
static void DoubleMetaphone (char *, char **)
 PG_FUNCTION_INFO_V1 (dmetaphone)
 PG_FUNCTION_INFO_V1 (dmetaphone_alt)
static metastringNewMetaString (char *init_str)
static void DestroyMetaString (metastring *s)
static void IncreaseBuffer (metastring *s, int chars_needed)
static void MakeUpper (metastring *s)
static int IsVowel (metastring *s, int pos)
static int SlavoGermanic (metastring *s)
static char GetAt (metastring *s, int pos)
static void SetAt (metastring *s, int pos, char c)
static int StringAt (metastring *s, int start, int length,...)
static void MetaphAdd (metastring *s, char *new_str)

Define Documentation

#define META_FREE (   x  ) 

Definition at line 201 of file dmetaphone.c.

Referenced by DestroyMetaString().

#define META_MALLOC (   v,
  n,
  t 
)    (v = (t*)palloc(((n)*sizeof(t))))

Definition at line 188 of file dmetaphone.c.

Referenced by NewMetaString().

#define META_REALLOC (   v,
  n,
  t 
)    (v = (t*)repalloc((v),((n)*sizeof(t))))

Definition at line 191 of file dmetaphone.c.

Referenced by IncreaseBuffer().

#define NDEBUG

Definition at line 107 of file dmetaphone.c.


Function Documentation

static void DestroyMetaString ( metastring s  )  [static]

Definition at line 261 of file dmetaphone.c.

References metastring::free_string_on_destroy, META_FREE, NULL, and metastring::str.

Referenced by DoubleMetaphone().

{
    if (s == NULL)
        return;

    if (s->free_string_on_destroy && (s->str != NULL))
        META_FREE(s->str);

    META_FREE(s);
}

Datum dmetaphone ( PG_FUNCTION_ARGS   ) 

Definition at line 132 of file dmetaphone.c.

References arg, cstring_to_text(), DoubleMetaphone(), PG_ARGISNULL, PG_GETARG_TEXT_P, PG_RETURN_NULL, PG_RETURN_TEXT_P, and text_to_cstring().

{
    text       *arg;
    char       *aptr,
               *codes[2],
               *code;

#ifdef DMETAPHONE_NOSTRICT
    if (PG_ARGISNULL(0))
        PG_RETURN_NULL();
#endif
    arg = PG_GETARG_TEXT_P(0);
    aptr = text_to_cstring(arg);

    DoubleMetaphone(aptr, codes);
    code = codes[0];
    if (!code)
        code = "";

    PG_RETURN_TEXT_P(cstring_to_text(code));
}

Datum dmetaphone_alt ( PG_FUNCTION_ARGS   ) 

Definition at line 161 of file dmetaphone.c.

References arg, cstring_to_text(), DoubleMetaphone(), PG_ARGISNULL, PG_GETARG_TEXT_P, PG_RETURN_NULL, PG_RETURN_TEXT_P, and text_to_cstring().

{
    text       *arg;
    char       *aptr,
               *codes[2],
               *code;

#ifdef DMETAPHONE_NOSTRICT
    if (PG_ARGISNULL(0))
        PG_RETURN_NULL();
#endif
    arg = PG_GETARG_TEXT_P(0);
    aptr = text_to_cstring(arg);

    DoubleMetaphone(aptr, codes);
    code = codes[1];
    if (!code)
        code = "";

    PG_RETURN_TEXT_P(cstring_to_text(code));
}

static void DoubleMetaphone ( char *  str,
char **  codes 
) [static]

Definition at line 393 of file dmetaphone.c.

References DestroyMetaString(), metastring::free_string_on_destroy, GetAt(), IsVowel(), metastring::length, MakeUpper(), MetaphAdd(), NewMetaString(), SetAt(), SlavoGermanic(), metastring::str, and StringAt().

Referenced by dmetaphone(), and dmetaphone_alt().

{
    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);
}

static char GetAt ( metastring s,
int  pos 
) [static]

Definition at line 326 of file dmetaphone.c.

References metastring::length, and metastring::str.

Referenced by DoubleMetaphone().

{
    if ((pos < 0) || (pos >= s->length))
        return '\0';

    return ((char) *(s->str + pos));
}

static void IncreaseBuffer ( metastring s,
int  chars_needed 
) [static]

Definition at line 274 of file dmetaphone.c.

References assert, metastring::bufsize, META_REALLOC, NULL, and metastring::str.

Referenced by MetaphAdd().

{
    META_REALLOC(s->str, (s->bufsize + chars_needed + 10), char);
    assert(s->str != NULL);
    s->bufsize = s->bufsize + chars_needed + 10;
}

static int IsVowel ( metastring s,
int  pos 
) [static]

Definition at line 293 of file dmetaphone.c.

References metastring::length, and metastring::str.

Referenced by DoubleMetaphone().

{
    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 void MakeUpper ( metastring s  )  [static]

Definition at line 283 of file dmetaphone.c.

References i, and metastring::str.

Referenced by DoubleMetaphone().

{
    char       *i;

    for (i = s->str; *i; i++)
        *i = toupper((unsigned char) *i);
}

static void MetaphAdd ( metastring s,
char *  new_str 
) [static]

Definition at line 376 of file dmetaphone.c.

References metastring::bufsize, IncreaseBuffer(), metastring::length, NULL, and metastring::str.

Referenced by DoubleMetaphone().

{
    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 metastring* NewMetaString ( char *  init_str  )  [static]

Definition at line 236 of file dmetaphone.c.

References assert, metastring::bufsize, metastring::free_string_on_destroy, metastring::length, META_MALLOC, NULL, and metastring::str.

Referenced by DoubleMetaphone().

{
    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);

    strncpy(s->str, init_str, s->length + 1);
    s->free_string_on_destroy = 1;

    return s;
}

PG_FUNCTION_INFO_V1 ( dmetaphone   ) 
PG_FUNCTION_INFO_V1 ( dmetaphone_alt   ) 
static void SetAt ( metastring s,
int  pos,
char  c 
) [static]

Definition at line 336 of file dmetaphone.c.

References metastring::length, and metastring::str.

Referenced by DoubleMetaphone().

{
    if ((pos < 0) || (pos >= s->length))
        return;

    *(s->str + pos) = c;
}

static int SlavoGermanic ( metastring s  )  [static]

Definition at line 310 of file dmetaphone.c.

References metastring::str.

Referenced by DoubleMetaphone().

{
    if ((char *) strstr(s->str, "W"))
        return 1;
    else if ((char *) strstr(s->str, "K"))
        return 1;
    else if ((char *) strstr(s->str, "CZ"))
        return 1;
    else if ((char *) strstr(s->str, "WITZ"))
        return 1;
    else
        return 0;
}

static int StringAt ( metastring s,
int  start,
int  length,
  ... 
) [static]

Definition at line 349 of file dmetaphone.c.

References metastring::length, and metastring::str.

Referenced by DoubleMetaphone().

{
    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))
            return 1;
    }
    while (strcmp(test, "") != 0);

    va_end(ap);

    return 0;
}