/* QUERY - "Rolodex" database accessing program
** Author: Ronald L. Rivest
** See QUERY.DOC for documentation
** 10/14/86
*/
 
#include <stdio.h>

/********************/
/* GLOBAL VARIABLES */
/********************/

char *SP[50];        /* Search patterns */
int  NSP;            /* Number of search patterns */

char *OP[50];        /* Output patterns */
int  NOP;            /* Number of output patterns */

FILE *DataFile;       /* file handle number for database */

/* Each paragraph may have up to 200 lines of 200 characters each */
char L[200][200];    /* buffer for lines */
int  NL;             /* number of lines actually read */
int  Mark[200];      /* Marks for printing */

int  LinesPerPage = 20;   /* Number of lines to output before "more" */
int  LineCounter = 0;     /* Lines output this page */

#define TRUE  1
#define FALSE 0

int EOFSeen = FALSE;

/* OPTION SWITCHES */
int SearchFieldNames;
int PrintFieldNames;
int MoreProcessing;

/**************/
/* PROCEDURES */
/**************/

char upper[256]; /* upper case version */

Initupper()
{ int i;
  for (i=0;i<256;i++) upper[i] = i;
  for (i='a'; i<='z'; i++) upper[i] = i - 'a' + 'A';
}

OpenDatabase(filename)
char *filename;
{ DataFile = fopen(filename, "r");
  if (DataFile == NULL)
    { printf("\nError: can't open file.\n");
      exit(0);
    }
}

IsWhiteSpace(c)
char c;
{ if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') 
    return(TRUE);
  else 
    return(FALSE);
}

ReadLine()
/* Read a line into L[NL] */
/* Returns  0 if a totally blank line read
**          1 if a nonblank line read (and NL is incremented)
**          2 if end-of-file condition
*/
{ char *c;
  if (EOFSeen) return(2);
  if (NULL==fgets(L[NL],200,DataFile))
     /* End of file */
    { EOFSeen = TRUE; return(2); }
  else
    { c = L[NL];
      while (*c)
	{ if (!IsWhiteSpace(*c))
	    { /* nonblank line read */
	      if (NL < 199) NL++;
	      return(1);
	    }
	  c++;
	}
      /* blank line read */
      return(0);
    }
}

ReadPara()
/* returns TRUE if para actually read, otherwise FALSE */
{ NL = 0;
  if (EOFSeen) return(FALSE);
  /* Skip any initial blank lines */
  while (ReadLine() == 0) ;
  /* Check for EOF */
  if (EOFSeen) return(FALSE);
  /* Collect remaining nonblank lines of paragraph */
  while (ReadLine() == 1) ;
  return(TRUE);
}

CloseDatabase()
/* closes the input file */
{ fclose(DataFile);
}

IsPrefix(x,y)
char *x, *y;
/* returns TRUE if x is a prefix of y , else FALSE */
{ while (upper[*x] == upper[*y] && *x && *y)  { x++; y++; }
  if (*x == 0) return(TRUE);
  return(FALSE);
}

IsSubstring(x,y)
char *x, *y;
/* returns TRUE if x is a substring of y, else FALSE */
{ int c;
  if (*x == 0) return(TRUE);
  c = upper[*x];
  while (*y) { if (c==upper[*y] && IsPrefix(x,y)) return(TRUE);
	       y++;
	     }
  return(FALSE);
}

MarkLinesForOutput()
/* Marks those lines satisfying the output request */
{  int i,j;
   for (i=0;i<NL;i++)
     { Mark[i] = FALSE;
       for (j=0;j<NOP;j++)
	 if (IsPrefix(OP[j],L[i])) Mark[i] = TRUE;
     }
}

DoMoreProcessing()
/* Ask for typein to see more output */
{ if (MoreProcessing && LineCounter == LinesPerPage)
    { printf("-- Press carriage return for more --");
      getchar();
      LineCounter = 0;
    }
}

PrintLines()
/* prints all marked lines, with or without fieldnames, 
*/
{ int i;
  char *x;
  for (i=0;i<NL;i++)
    if (Mark[i])
      { x = L[i];
        if (!PrintFieldNames) 
          { while (!IsWhiteSpace(*x) && *x) x++;
	    while ( IsWhiteSpace(*x) && *x) x++;
	  }
	DoMoreProcessing();
	printf("%s",x);
	LineCounter++;
      }
  DoMoreProcessing();
  printf("\n");
  LineCounter++;
}

PrintPara()
/* prints out requested lines of this paragraph */
{ MarkLinesForOutput();               
  PrintLines();
}

MatchLinePattern(line,x)
char *line, *x;
{ if (!SearchFieldNames) while (*line && !IsWhiteSpace(*line)) line++;
  if (IsSubstring(x,line)) return(TRUE);
  else                     return(FALSE);
}

MatchParaFieldPattern(field,x)
char *field, *x;
/* returns TRUE if this para contains pattern x in field given */
{ int i;
  for (i=0;i<NL;i++)
    if (IsPrefix(field,L[i]) && MatchLinePattern(L[i],x))
      return(TRUE);
  return(FALSE);
}

MatchPara()
/* returns TRUE if this para matches search spec, else FALSE */
{ int i, j;
  char field[200], *x, *y;
  /* initialize field name to null fieldname */
  field[0] = 0; field[1] = 0; 
  /* now check that para meets all the specs */
  for (i=0;i<NSP;i++) 
    { if (SP[i][0] == '-') /* change field spec */
	{ x = SP[i]+1; y = field;
	  while (*x && !IsWhiteSpace(*x)) *y++ = *x++;
	  *y = 0;
	}
      else /* check that para matches this spec */
	{ if (!MatchParaFieldPattern(field,SP[i])) return(FALSE);
	}
    }
  return(TRUE);
}

ParseCommand(argc,argv)
int argc;
char *argv[];
{ char *x;
  int i;
  NOP = 0; 
  NSP = 0;
  SearchFieldNames = TRUE;
  PrintFieldNames = TRUE;
  MoreProcessing = FALSE;
  OpenDatabase(argv[1]);
  /* Now process options one at a time */
  for (i=2;i<argc;i++)
    { /* check for output specification */
      if (argv[i][0] == '+') 
	OP[NOP++] = argv[i]+1;
      /* check for option choice */
      else if (argv[i][0] == '.')
	{ x = argv[i]++;    
	  while (*x)
	    { if (*x == 'S' || *x == 's') PrintFieldNames = FALSE;
	      if (*x == 'F' || *x == 'f') SearchFieldNames = FALSE;
	      if (*x == 'M' || *x == 'm') MoreProcessing = TRUE;
	      x++;
	    }
	}
      /* it is input specification */
      else SP[NSP++] = argv[i];
    }
  if (NOP == 0) OP[NOP++] = "";
} 

char *mss[] = {
"usage:  QUERY FILENAME PATTERNS",
"FILENAME is the name of a database of PARAGRAPHS separated by blank lines.",
"A word at the beginning of a nonblank line is a FIELDNAME.",
"A paragraph matches pattern string X if its current scope contains string X.",
"Initially the scope is the entire paragraph; the switch -FF redefines the",
"scope to be all lines whose fieldnames begin with the string FF.",
"(Upper and lower case letters always match.)  If more than one pattern is",
"given, the paragraph must satisfy them all in order to be printed.",
" ",
"A switch of the form +GG will output, for each matching paragraph, the lines",
"whose fieldnames begin with GG.  The default is + (all lines output). ",
"The .S switch suprresses the printing of the fieldnames.",
"The .F switch prevents patterns from matching against fieldnames.",
"The .M switchinvokes `more?' processing for output control.",
" ",
"Example:  QUERY AI.BIB -AUTHOR JOHN SM -TITLE MARKOV +TI +JO +YE .S",
"might find all entries in a SCRIBE database with author containing the ",
"strings JOHN and SM, title containing the word MARKOV, and would print",
"the TITLE, JOURNAL, and YEAR lines out for those entries.",
"See QUERY.DOC for more information and examples.",
""
};
PrintUsage()
{ int i;
  for (i=0;;i++) if(mss[i][0]) printf("%s\n",mss[i]); else return;
}

main(argc,argv)
int argc;
char *argv[];
{ Initupper();
  /* check for presence of file name and open file */
  if (argc == 1) { PrintUsage(); exit(0); }
  ParseCommand(argc,argv);
  while (ReadPara()) if (MatchPara()) PrintPara();
  CloseDatabase();
}
