/* * phone.c * * Search a phone book file for an entry * * Author: Martin Minow * MSDOS version: J. Anthony Movshon * 7 August 1984 * CP/M version: B.Eiben 27-August 1984 (AZTEC C) */ #include "stdio.h" /*#include "ctype.h"*/ #define isalpha(c) (((c>='a' && c<='z') || (c>='A' && c<='Z'))) #define isprint(c) (c >=0x20 && c<=0X7e) #define isgraph(c) (c >=0X20 && c<=0x7e && c != ' ') #define TRUE 1 #define FALSE 0 #define EOS 0 #define NFIELDS 100 #define LINESIZE 257 char *search[NFIELDS]; /* search text */ char *word[NFIELDS]; /* file text split up */ int issoundex[NFIELDS]; /* TRUE for soundex */ unsigned int hash[NFIELDS]; /* soundex hash value */ char line[LINESIZE]; /* text line as read */ char work[LINESIZE]; /* text line in words */ char argtext[LINESIZE]; /* prompted text */ FILE *fd; /* Phone book file */ FILE *ptr; /* * This search list is used to locate the phone book. */ static char *searchlist[] = { "phone.txt", "e:phone.txt", "a:phone.txt", NULL }; /* * Usage message */ static char *helpmessage[] = { "Search phone book for an argument string:", " '*' in a string matches any string of characters", " '?' in a string matches any non-null character", "Any left-most match succeeds and multiple search arguments are permitted.", "Names that sound similar will be reported if '*' and '?' are not used.", "Ex.:", "Search: Miller -> SOUNDEX match on MILLER*.", "Search: Mille? -> precise match on MILLE - plus one non-null character.", "Search: Miller Brown -> SOUNDEX match on MILLER or BROWN .", "PHONE -F FOO.TXT -> uses file FOO.TXT as phone book.", "PHONE MILLER -> does SOUNDEX match on MILLER and exits.", NULL, }; main(argc, argv) int argc; char *argv[]; { openphonebook(argc, argv); if (argc > 1) { findargs(argc, argv); setsoundexflag(); findmatch(); } else { /* * No arguments were given. Prompt and read * arguments from standard input. */ printf("Phonebook search program, for help\n"); while (!feof(stdin)) { printf("Search: "); fflush(stdin); if (gets(argtext,stdin) == NULL) break; if (argtext[1] == EOS) { usage(); } else { packtext(argtext, search); setsoundexflag(); findmatch(); fclose(fd); openphonebook(argc,argv); /* rewind(fd);*/ } } } } openphonebook(argc, argv) int argc; /* Arg count from operating system */ register char *argv[]; /* Arg vector from operating system */ /* * Open the phone book file -- exit to system if failure. */ { register char **namep; register char *cp; while (--argc > 0) { cp = *++argv; if (*cp++ == '-') { if ((*cp == 'f' || *cp == 'F') && cp[1] == EOS) { *argv++ = NULL; cp = *argv; if (--argc <= 0) { printf("PHONE: -f must be followed by a file name\n"); exit(1); } else if ((fd = fopen(cp, "r")) == NULL) { printf("PHONE: Can't open Your phonebook as %s\n",cp); exit(1); } else { *argv = NULL; return; } } } } /* * On VMS, RSTS, RSX, or whathaveyou, try the search list */ for (namep = searchlist; *namep != NULL; namep++) { if (openit(*namep, FALSE)) return; } printf("PHONE: Can't locate phonebook as PHONE.TXT\n"); exit(1); } static int openit(filename, fatal) char *filename; /* Actual filename to open */ int fatal; /* TRUE if failure is fatal */ /* * Open the indicated file. Return TRUE if success. If failure and * fatal is TRUE, exit to the operating system with an appropriate * error message, else, return FALSE. */ { if ((fd = fopen(filename, "r")) != NULL) return (TRUE); else if (!fatal) return (FALSE); else { printf("PHONE: Can't open phonebook %d\n",filename); exit(1); } } findargs(argc, argv) int argc; /* Arg count from operating system */ char *argv[]; /* Arg vector from operating system */ /* * Build the search arguments in field[]. */ { register char *cp; register int fieldindex; register char c; fieldindex = 0; while (--argc > 0) { cp = *++argv; if (cp == NULL) continue; search[fieldindex] = cp; while ((c = *cp) != EOS) { *cp++ = tolower(c); } fieldindex++; } search[fieldindex] = NULL; /* terminate the list */ setsoundexflag(); } setsoundexflag() /* * Examine the search arguments (in search[]), setting issoundex[] * appropriately. If this argument should be "soundex'ed", calculate * the hash value. */ { register char *cp; register int fieldindex; register char c; extern unsigned int soundex(); for (fieldindex = 0; (cp = search[fieldindex]) != NULL; fieldindex++) { issoundex[fieldindex] = TRUE; while ((c = *cp++) != EOS) { if (c == '*' || c == '?') { issoundex[fieldindex] = FALSE; break; } } if (issoundex[fieldindex]) hash[fieldindex] = soundex(search[fieldindex]); } } findmatch() /* * Read the file, print matching lines. */ { register int fieldindex; /* Field pointer */ register char **wp; /* Word pointer */ register int matchcount; /* Counts matches */ extern unsigned int soundex(); matchcount = 0; line[0] = EOS; for (;;) { if (line[0] == EOS && fgets(line, sizeof (line), fd) == NULL) break; if (isspace(line[0])) { line[0] = EOS; /* Force read next time */ continue; } strcpy(work, line); packtext(work, word); for (fieldindex = 0; search[fieldindex] != NULL; fieldindex++) { for (wp = word; *wp != NULL; wp++) { if (issoundex[fieldindex] != FALSE && soundex(*wp) == hash[fieldindex]) goto gotcha; if (matchtest(*wp, search[fieldindex])) goto gotcha; } } line[0] = EOS; /* Force read next time */ continue; /* Not found */ gotcha: matchcount++; do { printf(line); if (fgets(line, sizeof (line), fd) == NULL) goto done; } while (isspace(line[0])); printf("\n"); } done: if (matchcount == 0) printf("PHONE: No match found\n"); else { if (matchcount == 1) printf("%d\tmatch found\n",matchcount); else printf("%d\tmatches found\n",matchcount); } } packtext(text, wordlist) register char *text; /* This text line is packed */ register char **wordlist; /* Into this array of words */ /* * Build wordlist. Modifies text. */ { register char c; c = *text; while (c != EOS) { while ((c = *text) != EOS && !isgraph(c)) ++text; /* Skip over whitespace */ if (c == EOS) break; *wordlist++ = text; /* Start of new word */ while ((c = *text), isgraph(c)) *text++ = tolower(c); *text++ = EOS; /* Terminate the word */ } *wordlist = NULL; } int matchtest(name, pattern) register char *name; /* What to look for */ register char *pattern; /* May have wildcard */ /* * Recursive routine to match "name" against "pattern". * Returns TRUE if successful, FALSE if failure. * * pattern follows Dec filename wildcard conventions: '*' matches any * string (even null), '?' matches any single (non-null) byte. * */ { register char pattbyte; for (;;) { /* printf("(%s) (%s), namebyte = '%c', pattbyte = '%c'\n", name, pattern, *name, *pattern); */ /* * Assume that patterns are terminated -- implicitly -- * by '*', allowing all left-matches to succeed. */ if ((pattbyte = *pattern++) == EOS || (pattbyte == '*' && *pattern == EOS)) return (TRUE); /* * Not at end of the name string. */ switch (pattbyte) { case '*': /* Wild card means "advance" */ do { if (matchtest(name, pattern)) return (TRUE); } while (*name++ != EOS); return (FALSE); /* Did our best */ case '?': /* One byte JOKER */ break; /* succeeds with this byte */ default: /* Others must match exactly */ if (*name != pattbyte) return (FALSE); break; } name++; /* Advance name byte */ } } /* * soundex(string) * * Return the soundex hash value for the argument string. * S_V should be zero for efficiency. */ #define S_V 0 /* Vowels: a e i o u (maybe y, h, w) */ #define S_SK 1 /* "hard" consonants: s c z x k q */ #define S_TD 2 /* Dental stops: t d */ #define S_FV 3 /* f v */ #define S_GJ 4 /* g j */ #define S_B 5 /* b */ #define S_L 6 /* l */ #define S_M 7 /* m */ #define S_N 8 /* n */ #define S_P 9 /* p */ #define S_R 10 /* r */ #define S_MAX 11 /* "radix" of soundex values */ /* * The following are the hash values for each letter. */ static char sx_letters[] = { /* a b c d e f g h */ S_V, S_B, S_SK, S_TD, S_V, S_FV, S_GJ, S_V, /* i j k l m n o p */ S_V, S_GJ, S_SK, S_L, S_M, S_N, S_V, S_P, /* q r s t u v w x */ S_SK, S_R, S_SK, S_TD, S_V, S_FV, S_V, S_SK, /* y z */ S_V, S_SK, }; /* * The first letter of the following consonant pairs is silent. */ static char *sx_leading_silent = "tstzghknpnptpf"; unsigned int soundex(string) register char *string; /* * Compute the soundex hash for the argument string. * -- somewhat modified from the original algorithm. * "Margaret K. Odell and Robert C. Russell. U.S. * patents 1261167 (1918) and 1435663 (1922)." as * reprinted in Donald Knuth, Sorting and Searching. */ { register int c; /* Current character */ register char *sxp; /* -> leading silent string */ int last; /* Previous character for doubles */ int next; /* Next character in the string */ int value; /* Soundex value */ int temp; /* Hash for this character */ int count; /* Want only three digits */ while (c = *string++, !isalpha(c)) { if (c == EOS) return (0); } last = c = tolower(c); value = c - 'a' + 1; /* Initial hash == first letter value */ next = *string++; next = tolower(next); count = 3; /* Maximum times through this loop */ while (--count >= 0 && (c = next) != EOS) { next = *string++; next = tolower(next); if (next != EOS) { /* * Ignore the first letter in a silent-pair. */ for (sxp = sx_leading_silent; *sxp != EOS; sxp++) { if (*sxp++ == c && *sxp == next) { goto reject; /* reject silent first letter */ } } /* * Change 'ph' to 'f' */ if (c == 'p' && next == 'h') { c = 'f'; next = *string++; next = tolower(next); } } if (islower(c)) { if ((temp = sx_letters[c - 'a']) != S_V && c != last) { value *= S_MAX; value += temp; } last = c; } reject: ; /* Jump here on silent pairs */ } while (--count >= 0) /* Finish off the hash code */ value *= S_MAX; return (value); } usage() /* * Help message */ { register char **hp; for (hp = helpmessage; *hp != NULL; hp++) { printf("%s\n", *hp); } }