]> git.sur5r.net Git - openldap/blob - servers/slapd/phonetic.c
4c7f3537bf57717da9ae0967652562dad582f8f0
[openldap] / servers / slapd / phonetic.c
1 /* phonetic.c - routines to do phonetic matching */
2
3 #include "portable.h"
4
5 #include <stdio.h>
6
7 #include <ac/ctype.h>
8 #include <ac/string.h>
9 #include <ac/socket.h>
10 #include <ac/time.h>
11
12 #include "slap.h"
13
14 #if !defined(METAPHONE) && !defined(SLAPD_PHONETIC)
15 #define METAPHONE
16 #endif
17
18 #define iswordbreak(x)  (!isascii(x) || isspace((unsigned char) (x)) || \
19                          ispunct((unsigned char) (x)) || \
20                          isdigit((unsigned char) (x)) || x == '\0')
21
22 char *
23 first_word( char *s )
24 {
25         if ( s == NULL ) {
26                 return( NULL );
27         }
28
29         while ( iswordbreak( *s ) ) {
30                 if ( *s == '\0' ) {
31                         return( NULL );
32                 } else {
33                         s++;
34                 }
35         }
36
37         return( s );
38 }
39
40 char *
41 next_word( char *s )
42 {
43         if ( s == NULL ) {
44                 return( NULL );
45         }
46
47         while ( ! iswordbreak( *s ) ) {
48                 s++;
49         }
50
51         while ( iswordbreak( *s ) ) {
52                 if ( *s == '\0' ) {
53                         return( NULL );
54                 } else {
55                         s++;
56                 }
57         }
58
59         return( s );
60 }
61
62 char *
63 word_dup( char *w )
64 {
65         char    *s, *ret;
66         char    save;
67
68         for ( s = w; !iswordbreak( *s ); s++ )
69                 ;       /* NULL */
70         save = *s;
71         *s = '\0';
72         ret = ch_strdup( w );
73         *s = save;
74
75         return( ret );
76 }
77
78 #ifndef MAXPHONEMELEN
79 #define MAXPHONEMELEN   4
80 #endif
81
82 #if defined(SLAPD_PHONETIC)
83
84 /* lifted from isode-8.0 */
85 char *
86 phonetic( char *s )
87 {
88         char    code, adjacent, ch;
89         char    *p;
90         char    **c;
91         int     i, cmax;
92         char    phoneme[MAXPHONEMELEN + 1];
93
94         p = s;
95         if (  p == NULL || *p == '\0' ) {
96                 return( NULL );
97         }
98
99         adjacent = '0';
100         phoneme[0] = TOUPPER(*p);
101
102         phoneme[1]  = '\0';
103         for ( i = 0; i < 99 && (! iswordbreak(*p)); p++ ) {
104                 ch = TOUPPER (*p);
105
106                 code = '0';
107
108                 switch (ch) {
109                 case 'B':
110                 case 'F':
111                 case 'P':
112                 case 'V':
113                         code = (adjacent != '1') ? '1' : '0';
114                         break;
115                 case 'S':
116                 case 'C':
117                 case 'G':
118                 case 'J':
119                 case 'K':
120                 case 'Q':
121                 case 'X':
122                 case 'Z':
123                         code = (adjacent != '2') ? '2' : '0';
124                         break;
125                 case 'D':
126                 case 'T':
127                         code = (adjacent != '3') ? '3' : '0';
128                         break;
129                 case 'L':
130                         code = (adjacent != '4') ? '4' : '0';
131                         break;
132                 case 'M':
133                 case 'N':
134                         code = (adjacent != '5') ? '5' : '0';
135                         break;
136                 case 'R':
137                         code = (adjacent != '6') ? '6' : '0';
138                         break;
139                 default:
140                         adjacent = '0';
141                 }
142
143                 if ( i == 0 ) {
144                         adjacent = code;
145                         i++;
146                 } else if ( code != '0' ) {
147                         if ( i == MAXPHONEMELEN )
148                                 break;
149                         adjacent = phoneme[i] = code;
150                         i++;
151                 }
152         }
153
154         if ( i > 0 )
155                 phoneme[i] = '\0';
156
157         return( ch_strdup( phoneme ) );
158 }
159
160 #else
161 #if defined(METAPHONE)
162
163 /*
164  * Metaphone copied from C Gazette, June/July 1991, pp 56-57,
165  * author Gary A. Parker, with changes by Bernard Tiffany of the
166  * University of Michigan, and more changes by Tim Howes of the
167  * University of Michigan.
168  */
169
170 /* Character coding array */
171 static char     vsvfn[26] = {
172            1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2,
173         /* A   B  C   D  E  F  G   H  I  J  K  L  M  */
174            2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0};
175         /* N  O  P  Q  R  S  T  U  V  W  X  Y  Z  */
176
177 /* Macros to access character coding array */
178 #define vowel(x)        ((x) != '\0' && vsvfn[(x) - 'A'] & 1)   /* AEIOU */
179 #define same(x)         ((x) != '\0' && vsvfn[(x) - 'A'] & 2)   /* FJLMNR */
180 #define varson(x)       ((x) != '\0' && vsvfn[(x) - 'A'] & 4)   /* CGPST */
181 #define frontv(x)       ((x) != '\0' && vsvfn[(x) - 'A'] & 8)   /* EIY */
182 #define noghf(x)        ((x) != '\0' && vsvfn[(x) - 'A'] & 16)  /* BDH */
183
184 char *
185 phonetic( char *Word )
186 {
187         char           *n, *n_start, *n_end;    /* pointers to string */
188         char           *metaph, *metaph_end;    /* pointers to metaph */
189         char            ntrans[40];     /* word with uppercase letters */
190         char            newm[8];/* new metaph for comparison */
191         int             KSflag; /* state flag for X -> KS */
192         char            buf[MAXPHONEMELEN + 2];
193         char            *Metaph;
194
195         /*
196          * Copy Word to internal buffer, dropping non-alphabetic characters
197          * and converting to upper case
198          */
199
200         for (n = ntrans + 4, n_end = ntrans + 35; !iswordbreak( *Word ) &&
201             n < n_end; Word++) {
202                 if (isalpha(*Word))
203                         *n++ = TOUPPER(*Word);
204         }
205         Metaph = buf;
206         *Metaph = '\0';
207         if (n == ntrans + 4) {
208                 return( ch_strdup( buf ) );             /* Return if null */
209         }
210         n_end = n;              /* Set n_end to end of string */
211
212         /* ntrans[0] will always be == 0 */
213         ntrans[0] = '\0';
214         ntrans[1] = '\0';
215         ntrans[2] = '\0';
216         ntrans[3] = '\0';
217         *n++ = 0;
218         *n++ = 0;
219         *n++ = 0;
220         *n = 0;                 /* Pad with nulls */
221         n = ntrans + 4;         /* Assign pointer to start */
222
223         /* Check for PN, KN, GN, AE, WR, WH, and X at start */
224         switch (*n) {
225         case 'P':
226         case 'K':
227         case 'G':
228                 /* 'PN', 'KN', 'GN' becomes 'N' */
229                 if (*(n + 1) == 'N')
230                         *n++ = 0;
231                 break;
232         case 'A':
233                 /* 'AE' becomes 'E' */
234                 if (*(n + 1) == 'E')
235                         *n++ = 0;
236                 break;
237         case 'W':
238                 /* 'WR' becomes 'R', and 'WH' to 'H' */
239                 if (*(n + 1) == 'R')
240                         *n++ = 0;
241                 else if (*(n + 1) == 'H') {
242                         *(n + 1) = *n;
243                         *n++ = 0;
244                 }
245                 break;
246         case 'X':
247                 /* 'X' becomes 'S' */
248                 *n = 'S';
249                 break;
250         }
251
252         /*
253          * Now, loop step through string, stopping at end of string or when
254          * the computed 'metaph' is MAXPHONEMELEN characters long
255          */
256
257         KSflag = 0;             /* state flag for KS translation */
258         for (metaph_end = Metaph + MAXPHONEMELEN, n_start = n;
259              n <= n_end && Metaph < metaph_end; n++) {
260                 if (KSflag) {
261                         KSflag = 0;
262                         *Metaph++ = 'S';
263                 } else {
264                         /* Drop duplicates except for CC */
265                         if (*(n - 1) == *n && *n != 'C')
266                                 continue;
267                         /* Check for F J L M N R or first letter vowel */
268                         if (same(*n) || (n == n_start && vowel(*n)))
269                                 *Metaph++ = *n;
270                         else
271                                 switch (*n) {
272                                 case 'B':
273
274                                         /*
275                                          * B unless in -MB
276                                          */
277                                         if (n == (n_end - 1) && *(n - 1) != 'M')
278                                                 *Metaph++ = *n;
279                                         break;
280                                 case 'C':
281
282                                         /*
283                                          * X if in -CIA-, -CH- else S if in
284                                          * -CI-, -CE-, -CY- else dropped if
285                                          * in -SCI-, -SCE-, -SCY- else K
286                                          */
287                                         if (*(n - 1) != 'S' || !frontv(*(n + 1))) {
288                                                 if (*(n + 1) == 'I' && *(n + 2) == 'A')
289                                                         *Metaph++ = 'X';
290                                                 else if (frontv(*(n + 1)))
291                                                         *Metaph++ = 'S';
292                                                 else if (*(n + 1) == 'H')
293                                                         *Metaph++ = ((n == n_start && !vowel(*(n + 2)))
294                                                          || *(n - 1) == 'S')
295                                                             ? (char) 'K' : (char) 'X';
296                                                 else
297                                                         *Metaph++ = 'K';
298                                         }
299                                         break;
300                                 case 'D':
301
302                                         /*
303                                          * J if in DGE or DGI or DGY else T
304                                          */
305                                         *Metaph++ = (*(n + 1) == 'G' && frontv(*(n + 2)))
306                                             ? (char) 'J' : (char) 'T';
307                                         break;
308                                 case 'G':
309
310                                         /*
311                                          * F if in -GH and not B--GH, D--GH,
312                                          * -H--GH, -H---GH else dropped if
313                                          * -GNED, -GN, -DGE-, -DGI-, -DGY-
314                                          * else J if in -GE-, -GI-, -GY- and
315                                          * not GG else K
316                                          */
317                                         if ((*(n + 1) != 'J' || vowel(*(n + 2))) &&
318                                             (*(n + 1) != 'N' || ((n + 1) < n_end &&
319                                                                  (*(n + 2) != 'E' || *(n + 3) != 'D'))) &&
320                                             (*(n - 1) != 'D' || !frontv(*(n + 1))))
321                                                 *Metaph++ = (frontv(*(n + 1)) &&
322                                                              *(n + 2) != 'G') ? (char) 'G' : (char) 'K';
323                                         else if (*(n + 1) == 'H' && !noghf(*(n - 3)) &&
324                                                  *(n - 4) != 'H')
325                                                 *Metaph++ = 'F';
326                                         break;
327                                 case 'H':
328
329                                         /*
330                                          * H if before a vowel and not after
331                                          * C, G, P, S, T else dropped
332                                          */
333                                         if (!varson(*(n - 1)) && (!vowel(*(n - 1)) ||
334                                                            vowel(*(n + 1))))
335                                                 *Metaph++ = 'H';
336                                         break;
337                                 case 'K':
338
339                                         /*
340                                          * dropped if after C else K
341                                          */
342                                         if (*(n - 1) != 'C')
343                                                 *Metaph++ = 'K';
344                                         break;
345                                 case 'P':
346
347                                         /*
348                                          * F if before H, else P
349                                          */
350                                         *Metaph++ = *(n + 1) == 'H' ?
351                                             (char) 'F' : (char) 'P';
352                                         break;
353                                 case 'Q':
354
355                                         /*
356                                          * K
357                                          */
358                                         *Metaph++ = 'K';
359                                         break;
360                                 case 'S':
361
362                                         /*
363                                          * X in -SH-, -SIO- or -SIA- else S
364                                          */
365                                         *Metaph++ = (*(n + 1) == 'H' ||
366                                                      (*(n + 1) == 'I' && (*(n + 2) == 'O' ||
367                                                           *(n + 2) == 'A')))
368                                             ? (char) 'X' : (char) 'S';
369                                         break;
370                                 case 'T':
371
372                                         /*
373                                          * X in -TIA- or -TIO- else 0 (zero)
374                                          * before H else dropped if in -TCH-
375                                          * else T
376                                          */
377                                         if (*(n + 1) == 'I' && (*(n + 2) == 'O' ||
378                                                            *(n + 2) == 'A'))
379                                                 *Metaph++ = 'X';
380                                         else if (*(n + 1) == 'H')
381                                                 *Metaph++ = '0';
382                                         else if (*(n + 1) != 'C' || *(n + 2) != 'H')
383                                                 *Metaph++ = 'T';
384                                         break;
385                                 case 'V':
386
387                                         /*
388                                          * F
389                                          */
390                                         *Metaph++ = 'F';
391                                         break;
392                                 case 'W':
393
394                                         /*
395                                          * W after a vowel, else dropped
396                                          */
397                                 case 'Y':
398
399                                         /*
400                                          * Y unless followed by a vowel
401                                          */
402                                         if (vowel(*(n + 1)))
403                                                 *Metaph++ = *n;
404                                         break;
405                                 case 'X':
406
407                                         /*
408                                          * KS
409                                          */
410                                         if (n == n_start)
411                                                 *Metaph++ = 'S';
412                                         else {
413                                                 *Metaph++ = 'K';        /* Insert K, then S */
414                                                 KSflag = 1;
415                                         }
416                                         break;
417                                 case 'Z':
418
419                                         /*
420                                          * S
421                                          */
422                                         *Metaph++ = 'S';
423                                         break;
424                                 }
425                 }
426         }
427
428         *Metaph = 0;            /* Null terminate */
429         return( ch_strdup( buf ) );
430 }
431
432 #endif /* metaphone */
433 #endif /* SLAPD_PHONETIC */