/* experimental CGI interface to ^z's FreeText indexer/browser "zftir" */

/* next few lines contain global variables and #defines which may need to
 * be customized or changed frequently
 */

float zftir_version = 0.015;
long zftir_date = 19970709;
#define DB_DIRECTORY    "/ftp.whatever.directory.goes.here/"

/* ZFTIR (c) 1987-1997 Mark Zimmermann --- free software under the GNU GPL!
 *
 * CONCEPT:  make a Web browser interface to classic FreeText IR files
 * to enable "real-time high-bandwidth large-scale free-text
 * information retrieval" for people anywhere on the WWW!
 *
 * see http://www.alumni.caltech.edu/~zimm/freetext.html
 *   for philosophical background; see also my chapter in
 *   THE DIGITAL WORD:  TEXT-BASED COMPUTING IN THE HUMANITIES
 *   (eds. Landow & Delany, MIT Press, 1993)
 *
 * preliminary experiments - do a few of the necessary functions
 * to permit (in)famous FreeText-style browsing --- specifically:
 *    INDEX - window into list of words and their frequencies
 *    CONTEXT - key-word-in-context instances of every word
 *    TEXT - full text from the database
 *    SEEK - jump INDEX display to a chosen word
 * 
 * Long-term goals/fantasies:  
 *  - extension language (Scheme, e.g. SIOD or GUILE?) for runtime customization
 *  - proximity search & fuzzy-neighborhood retrieval
 *  - multi-file databases
 *  - alternative alphabetizations & character mappings
 *  - relevance-ranked retrieval
 *  - thesaurus-like Index-word-mapping for synonyms/equivalents
 *  - grep-like wild-card search (like agrep, glimpse, etc.?)
 *  - up/down scan to target string in Text View
 *  - fractal space-filling graphical display of Index View
 *  - VRML data visualization - fly around in a dataspace?
 *  - correlation & similarity computations --- auto-identify characteristic words
 *  - locally-running versions (Java?!) for non-networked use
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* #include <ctype.h> */

/* defines and structures */

#define KEYLENGTH                28
#define STRLEN                  500
#define DEFAULT_TEXT_CHUNK      100

typedef struct {
    char kkey[KEYLENGTH];
    long ccount;
  } KEYREC;

/* global variables */
char db_name[STRLEN] = "";
char target_word[STRLEN] = "";
char proximity_search[STRLEN] = "";
char script_name[STRLEN] = "";
char query_string[STRLEN] = "";
char doc_filename[STRLEN], key_filename[STRLEN], ptr_filename[STRLEN];
FILE *doc_file, *key_file, *ptr_file;
long index_lines = 40;
long index_start = 0;
long context_lines = 40;
long context_start = 0;
long context_width = 72;

long context_offset = 30;
long context_jump = 0;
long text_length = 8000;
long text_start = 0;
long text_jump = 0;
int interpret_html = 0;


/* prototypes */

/* top level functions */
int main(int argc, char *argv[]);
void open_files(void);
int parse_query(int argc, char *argv[]);
void error_page(int argc, char *argv[]);
void header_html(void);
void db_form(void);
void index_html(void);
void context_html(void);
void text_html(void);
void param_form(void);
void zinfo_html(void);
void close_files(void);

/* lower level functions */
void getkeyrec(KEYREC *recp, long n, FILE *keyfile, long max);
long getptrrec(long ptrnum, FILE *ptrfile);
char *set_db_name(char *qsp);
char *set_target_word(char *qsp);
char *set_index_lines(char *qsp);
char *set_index_start(char *qsp);
char *set_context_lines(char *qsp);
char *set_context_start(char *qsp);
char *set_context_width(char *qsp);
char *set_context_offset(char *qsp);
char *set_text_length(char *qsp);
char *set_text_start(char *qsp);
char *set_interpret_html(char *qsp);
char *set_context_jump(char *qsp);
char *set_text_jump(char *qsp);
long seek_word(void);


/* ***** main program ***** */

int main(int argc, char *argv[]) {
  if (parse_query(argc, argv)) {
    open_files();
    header_html();
    db_form();
    index_html();
    context_html();
    text_html();
    param_form();
    zinfo_html();
    close_files();
  }
  else 
    error_page(argc, argv);
}


/* read and set the parameters for the global variables; return 1 if (apparently)
 * successful, 0 if error detected ... also set other parameters here as needed
 * for HTML generation */

int parse_query(int argc, char *argv[]) {
  char *qsp;

  /* produce self-referential name of this CGI program for use in links */
  strcpy(script_name, "http://");
  if (getenv("SERVER_NAME") != NULL)
    strcat(script_name, getenv("SERVER_NAME"));
  else
    return(0);
  if (getenv("SCRIPT_NAME") != NULL)
    strcat(script_name, getenv("SCRIPT_NAME"));
  else
    return(0);

  /* load any parameters in QUERY_STRING into their places */
  if (getenv("QUERY_STRING") != NULL)
    strcpy(query_string, getenv("QUERY_STRING"));
  else
    return(0);
  qsp = &query_string[0];
  while (*qsp != '\0') {
    /* load in values; return NULL if problem detected */
    switch (*qsp) {
      case 'd':
      case 'D':
        qsp = set_db_name(qsp);
        break;
      case 'k':
      case 'K':
        qsp = set_target_word(qsp);
        break;
      case 'j':
      case 'J':
        qsp = set_index_lines(qsp);
        break;
      case 'i':
      case 'I':
        qsp = set_index_start(qsp);
        break;
      case 'l':
      case 'L':
        qsp = set_context_lines(qsp);
        break;
      case 'c':
      case 'C':
        qsp = set_context_start(qsp);
        break;
      case 'w':
      case 'W':
        qsp = set_context_width(qsp);
        break;
      case 'o':
      case 'O':
        qsp = set_context_offset(qsp);
        break;
      case 'u':
      case 'U':
        qsp = set_text_length(qsp);
        break;
      case 't':
      case 'T':
        qsp = set_text_start(qsp);
        break;
      case 'h':
      case 'H':
        qsp = set_interpret_html(qsp);
        break;
      case 'e':
      case 'E':
        qsp = set_context_jump(qsp);
        break;
      case 'v':
      case 'V':
        qsp = set_text_jump(qsp);
        break;
      default:
        return(0);
    }
    if (qsp == NULL)
      return(0);
  }

  if (target_word[0] != '\0')
    index_start = seek_word();
  context_start += context_jump;
  text_start += text_jump;

  return(1);
}


/* handle any problems detected by returning an error/diagnostic page & exiting
 */

void error_page(int argc, char *argv[]) {
  int i;

  printf("Content-type: text/html\n\n");
  printf("<HTML>\n");
  printf("<HEAD>\n");
  printf("<TITLE>zftir: Error!%s</TITLE>\n");
  printf("</HEAD>\n");
  printf("<BODY>\n");
  printf("<H1>Sorry!</H1>\n");
  printf("There has apparently been an error; please contact Mark Zimmermann\n");
  printf("and provide detailed information on how you got here, including the\n");
  printf("following diagnostics as well as background on other actions and symptoms\n");
  printf("which you observed, so that I can better understand and fix any problems.\n");
  printf("<P>\n");
  printf("Thank you! --- <I>^z</I>\n");
  printf("<HR>\n");

  printf("<PRE>\n");
  printf("zftir version #%f dated %ld\n\n", zftir_version, zftir_date);

  if (argc > 0) {
    printf("<B>Command-line arguments:</B>\n");
    printf("  #    value\n");
    printf(" ---  -------\n");
    for (i = 0; i < argc; ++i)
      printf("  %d    %s\n", i, argv[i]);
  }

  printf("\n\n");
  printf("<B>Environment variables:</B>\n");
  printf("getenv(\"SERVER_NAME\") = %s\n", getenv("SERVER_NAME"));
  printf("getenv(\"SERVER_PORT\") = %s\n", getenv("SERVER_PORT"));
  printf("getenv(\"SCRIPT_NAME\") = %s\n", getenv("SCRIPT_NAME"));
  printf("getenv(\"QUERY_STRING\") = %s\n",getenv("QUERY_STRING"));

  printf("\n\n");
  printf("<B>Internal variables:</B>\n");
  printf("db_name = %s\n", db_name);
  printf("target_word = %s\n", target_word);
  printf("proximity_search = %s\n", proximity_search);
  printf("script_name = %s\n", script_name);
  printf("index_lines = %ld\n", index_lines);
  printf("index_start = %ld\n", index_start);
  printf("context_lines = %ld\n", context_lines);
  printf("context_start = %ld\n", context_start);
  printf("context_width = %ld\n", context_width);
  printf("context_offset = %ld\n", context_offset);
  printf("context_jump = %ld\n", context_jump);
  printf("text_length = %ld\n", text_length);
  printf("text_start = %ld\n", text_start);
  printf("text_jump = %ld\n", text_jump);
  printf("interpret_html = %d\n", interpret_html);
 
  printf("</PRE>\n");
  printf("<HR>\n");

  zinfo_html();

  exit(1);
}


/* open the database, key, and pointer files */

void open_files(void) {

  if (db_name[0] == '\0')
    return;

  strcpy(doc_filename, DB_DIRECTORY);
  strcat(doc_filename, db_name);
  strcpy(key_filename, doc_filename);
  strcat(key_filename, ".k");
  strcpy(ptr_filename, doc_filename);
  strcat(ptr_filename, ".p");

  if ((doc_file = fopen(doc_filename, "rb")) == NULL) {
    printf("ERROR! --- can't open doc_file %s, doc_filename\n", doc_filename);
    exit(1);
  }
  if ((key_file = fopen(key_filename, "rb")) == NULL) {
    printf("ERROR! --- can't open key_file %s, doc_filename\n", key_filename);
    exit(1);
  }
  if ((ptr_file = fopen(ptr_filename, "rb")) == NULL) {
    printf("ERROR! --- can't open ptr_file %s, doc_filename\n", ptr_filename);
    exit(1);
  }
}

/* produce header and title to begin a normal ZFTIR page */

void header_html(void) {

  printf("Content-type: text/html\n\n");
  printf("<HTML>\n");
  printf("<HEAD>\n");
  printf("<TITLE>zftir: %s</TITLE>\n", db_name);
  printf("</HEAD>\n");
  printf("<BODY>\n");

  printf("<I>Zftir</I> free-text information retrieval experiment\n");
  printf("version #%f dated %ld\n\n", zftir_version, zftir_date);
  printf("(c) 1987-1997\n");
  printf("<A HREF=\"http://www.alumni.caltech.edu/~zimm\">\n");
  printf("Mark Zimmermann </A>\n");

  if (db_name[0] == '\0') {
    printf("<P>\n");
    printf("Zftir was opened without a database selection --- please choose\n");
    printf("a database on the form below and try again --- thank you!\n");
  }

  printf("<HR>\n");

}


/* create the top of the form where the database name is chosen */

void db_form(void) {

  printf("<FORM ACTION=\"%s\" METHOD=GET\n", script_name);
  printf("<SELECT NAME=\"d\">\n");
  printf("<OPTION SELECTED>Gibbon\n");
  printf("<OPTION>Shakespeare\n");
  printf("</SELECT>\n");

  printf("<HR>\n");
}


/* build the Index View */

void index_html(void) {
  long i, key_num, max_key_num, key_lines;
  KEYREC this_key, prev_key;

  if (db_name[0] == '\0')
    return;

  /* determine length of key_file and get max_key_num */

  if (fseek (key_file, 0L, 2) != 0) {
    printf ("ERROR in fseek() getting key_file length!\n");
    exit (1);
  }
  max_key_num = ftell(key_file) / sizeof(KEYREC) - 1;

  key_num = index_start;
  if (max_key_num - index_start < index_lines)
    key_lines = max_key_num - index_start;
  else
    key_lines = index_lines;
  if (key_num < 0)
    key_num = 0;
  if (key_num > max_key_num)
    key_num = max_key_num;
  if (key_lines < 1)
    key_lines = 1;

  printf("<H4>Index</H4>\n");

  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&", index_start - index_lines);
  printf("l=%ld&c=%ld&w=%ld&", context_lines, context_start, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, text_start);
  printf("h=%d\"> Page Up </A>\n", interpret_html);

  printf("<PRE>\n");

  /* generate the guts of the index display here --- lines of counts followed by
* the associated indexed words, each its own link to its first instance
* as the "context_start" value  */

  getkeyrec(&prev_key, key_num - 1, key_file, max_key_num);
  for (i = key_num; i < key_num + key_lines; ++i) {
    getkeyrec(&this_key, i, key_file, max_key_num);
    printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
    printf("i=%ld&l=%ld&", index_start, context_lines);
    printf("c=%ld&w=%ld&", prev_key.ccount, context_width);
    printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, text_start);
    printf("h=%d\">", interpret_html);
    printf("%10ld %.28s", this_key.ccount - prev_key.ccount, this_key.kkey);
    printf("</A>\n");
  }

  printf("</PRE>\n");

  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&", index_start + index_lines);
  printf("l=%ld&c=%ld&w=%ld&", context_lines, context_start, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, text_start);
  printf("h=%d\"> Page Down </A>\n", interpret_html);


  printf("Jump to word: <INPUT NAME=\"k\" SIZE=40>\n");
  printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Find\">\n");

  printf("<HR>\n");
}


/* build the Context View */


void context_html(void) {
  long i, n, p, m, ptr, doc_ptr, max_doc_ptr, ptr_num, max_ptr_num, ptr_lines, 
    ptr_width, ptr_offset;
  char buf[STRLEN], escbuf[5*STRLEN], c;

  if (db_name[0] == '\0')
    return;

/* determine file lengths and get max values */

  if (fseek(doc_file, 0L, 2) != 0) {
    printf("Error in fseek() getting doc_file length!");
    exit(1);
  }
  max_doc_ptr = ftell(doc_file);
  if (fseek(ptr_file, 0L, 2) != 0) {
    printf ("Error in fseek() getting ptr_file length!");
    exit (1);
  }
  max_ptr_num = ftell(ptr_file) / sizeof(long) - 1;

  ptr_num = context_start;
  ptr_lines = context_lines;
  ptr_width = context_width;
  ptr_offset = context_offset;

/* force values into legal ranges */
  if (ptr_num < 0)
    ptr_num = 0;
  if (ptr_num > max_ptr_num)
    ptr_num = max_ptr_num;
  if (ptr_lines < 1)
    ptr_lines = 1;
  if (ptr_lines > max_ptr_num - ptr_num + 1)
    ptr_lines = max_ptr_num - ptr_num + 1;
  if (ptr_width < 10)
    ptr_width = 10;
  if (ptr_width > STRLEN / 2)
    ptr_width = STRLEN /2 ;
  if (ptr_offset < 0)
    ptr_offset = 0;
  if (ptr_offset > ptr_width)
    ptr_offset = ptr_width;


  printf("<H4>Context</H4>\n");

  i = ptr_num - context_lines;
  if (i < 0)
    i = 0;
  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&l=%ld&", index_start, context_lines);
  printf("c=%ld&w=%ld&", i, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, text_start);
  printf("h=%d\"> Page Up </A>\n", interpret_html);

  printf("<PRE>\n");

  /* do the guts of the Context display here */

  for (i = ptr_num; i < ptr_num + ptr_lines; ++i) {
    ptr = getptrrec(i, ptr_file);
    /* make line begin ptr_offset before the keyword */
    p = ptr - ptr_offset;
    n = 0;
    /* handle beginning of file by filling in with blanks */
    if (p < 0) {
      for ( ; n < -p; ++n)
	buf[n] = ' ';
      p = 0;
    }
    /* move to the right location and get the CONTEXT line */
    if (fseek(doc_file, p, 0) != 0) {
      printf("Error in fseek() getting a context line!\n");
      exit (1);
    }
    if ((m = fread(buf+n, sizeof(char), ptr_width - n, doc_file)) == 0) {
      printf ("Error in fread() getting a context line!\n");
      exit (1);
    }
    n += m;
    /* fill remainder of buffer with blanks, if at end of file */
    for ( ; n < ptr_width; ++n)
      buf[n] = ' ';
    /* filter out non-printing characters */
    for (n = 0; n < ptr_width; ++n)
      if (! isprint(buf[n] & 0xFF))
	buf[n] = ' ';
    buf[ptr_width] = '\0';
    /* now copy buf to escbuf fixing '<' and '>' and '&' characters */
    for (n = 0, m = 0;  ; ++n, ++m) {
      c = buf[n];
      if (c == '<') {
	escbuf[m++] = '&';
	escbuf[m++] = 'l';
	escbuf[m++] = 't';
	escbuf[m] = ';';
      }
      else if (c == '>') {
	escbuf[m++] = '&';
	escbuf[m++] = 'g';
	escbuf[m++] = 't';
	escbuf[m] = ';';
      }
      else if (c == '&') {
	escbuf[m++] = '&';
	escbuf[m++] = 'a';
	escbuf[m++] = 'm';
	escbuf[m++] = 'p';
	escbuf[m] = ';';
      }
      else
	escbuf[m] = c;
      if (c == '\0')
	break;
    }

    /* now make each line's proper HTML for CONTEXT command; for the moment,
     * back up DEFAULT_TEXT_CHUNK from the instance clicked on in setting up
     * to display the retrieved text --- not a good solution --- fix later?! */


    printf("<A HREF=\"%s? d=%s&j=%ld&", script_name, db_name, index_lines);
    printf("i=%ld&l=%ld&", index_start, context_lines);
    printf("c=%ld&w=%ld&", context_start, context_width);
    printf("o=%ld&u=%ld&", context_offset, text_length);
    printf("t=%ld&", ptr - DEFAULT_TEXT_CHUNK);
    printf("h=%d\">", interpret_html);
    printf("%s", escbuf);
    printf("</A>\n");
  }

  printf("</PRE>\n");

  i = ptr_num + context_lines;
  if (i > max_ptr_num)
    i = max_ptr_num;

  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&l=%ld&", index_start, context_lines);
  printf("c=%ld&w=%ld&", i, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, text_start);
  printf("h=%d\"> Page Down </A>\n", interpret_html);


  printf("Move (+/-) this many lines: <INPUT NAME=\"e\" SIZE=10>\n");
  printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Jump\">\n");

  printf("<HR>\n");
}


/* build the Text View */

void text_html(void) {
  long i, c, doc_file_length, begin_text, count_text;

  if (db_name[0] == '\0')
    return;

 /* get docfile length and force all parameters to be legal */
  if (fseek(doc_file, 0L, 2) != 0) {
    printf ("Error in fseek() getting doc_file length!\n");
    exit (1);
  }
  doc_file_length = ftell(doc_file);
  begin_text = text_start;
  if (begin_text < 0)
    begin_text = 0;
  if (begin_text > doc_file_length - 1)
    begin_text = doc_file_length - 1;
  count_text = text_length;
  if (count_text < 1)
    count_text = 1;
  if (count_text > doc_file_length - begin_text)
    count_text = doc_file_length - begin_text;

  printf("<H4>Text</H4>\n");

  i = text_start - text_length;
  if (i < 0)
    i = 0;
  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&l=%ld&", index_start, context_lines);
  printf("c=%ld&w=%ld&", context_start, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, i);
  printf("h=%d\"> Jump Up </A>\n", interpret_html);

 /* display text_length characters here, filtering out HTML control codes
  * for "<", ">", and "&" if interpret_html is false
  */
  if (fseek(doc_file, begin_text, 0) != 0) {
    printf("Error in fseek() positioning in doc_file!\n");
    exit(1);
  }
  for (i = 0; i < count_text; ++i) {
    c = getc(doc_file);
    if (interpret_html)
      putc(c, stdout);
    else {
      if (c == '<') {
        putc('&', stdout);
        putc('l', stdout);
        putc('t', stdout);
        putc(';', stdout);
      }
      else if (c == '>') {
        putc('&', stdout);
        putc('g', stdout);
        putc('t', stdout);
        putc(';', stdout);
      }
      else if (c == '&') {
        putc('&', stdout);
        putc('a', stdout);
        putc('m', stdout);
        putc('p', stdout);
        putc(';', stdout);
      }
      else
        putc(c, stdout);
    }
  }

/* finish off Text View */

  printf("\n<P>\n");
  i = text_start + text_length;
  if (i > doc_file_length - DEFAULT_TEXT_CHUNK)
    i = doc_file_length - DEFAULT_TEXT_CHUNK;
  printf("<A HREF=\"%s?d=%s&j=%ld&", script_name, db_name, index_lines);
  printf("i=%ld&l=%ld&", index_start, context_lines);
  printf("c=%ld&w=%ld&", context_start, context_width);
  printf("o=%ld&u=%ld&t=%ld&", context_offset, text_length, i);
  printf("h=%d\"> Jump Down </A>\n", interpret_html);

  printf("Jump (+/-) this many bytes: <INPUT NAME=\"v\" SIZE=10>\n");
  printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Jump\">\n");

  printf("<HR>\n");
}


/* build the end of the form where user-customizable parameters are set */

void param_form(void) {

  printf("<H4>Zftir Parameters</H4>\n");
  printf("Interpret HTML in Text window?\n");
  printf("<INPUT NAME=\"h\" TYPE=\"RADIO\" VALUE=1> Yes\n");
  printf("<INPUT NAME=\"h\" TYPE=\"RADIO\" VALUE=0 CHECKED> No\n");

  printf("<INPUT TYPE=\"HIDDEN\" NAME=\"i\" VALUE=0> <!-- Index start -->\n");
  printf("<INPUT TYPE=\"HIDDEN\" NAME=\"c\" VALUE=0> <!-- Context start -->\n");
  printf("<INPUT TYPE=\"HIDDEN\" NAME=\"t\" VALUE=0> <!-- Text start -->\n");
  printf("<BR>\n");
  printf("<INPUT NAME=\"j\" VALUE=40 SIZE=5> = lines of Index<BR>\n");
  printf("<INPUT NAME=\"l\" VALUE=40 SIZE=5> = lines of Context<BR>\n");
  printf("<INPUT NAME=\"w\" VALUE=72 SIZE=5> = width of Context<BR>\n");
  printf("<INPUT NAME=\"o\" VALUE=30 SIZE=5> = offset of keyword in Context<BR>\n");
  printf("<INPUT NAME=\"u\" VALUE=8000 SIZE=10> = bytes of Text<BR>\n");

  printf("<INPUT TYPE=\"SUBMIT\" VALUE=\"Set Parameters\">\n");
  printf("<INPUT TYPE=\"RESET\" VALUE=\"Defaults\">\n");
  printf("</FORM>\n");

}


/* provide contact info, finish off the html page, and we're done */

void zinfo_html(void) {
  printf("<B>zftir<B> = ^z's free-text information retrieval experiment<BR>\n");
  printf("version #%f dated %ld\n\n", zftir_version, zftir_date);
  printf("(c) 1987-1997 by Mark Zimmermann<BR>\n");
  printf("Free software under the GNU GPL.<BR>\n");
  printf("Thank you! --- <I>^z</I>\n");
  
  printf("</BODY>\n");
  printf("</HTML>\n");
}


void close_files(void) {
  fclose(doc_file);
  fclose(key_file);
  fclose(ptr_file);
}

/* ***** lower-level functions to fetch and format data follow ***** */  


/* get the nth KEYREC */

void getkeyrec(KEYREC *recp, long n, FILE *keyfile, long max) {
    
  if (n < 0 || n > max) {
    strncpy((char *)recp->kkey, 
	     "                                    ",
	     KEYLENGTH);
    recp->ccount = 0;
    return;
  }

  if (fseek(keyfile, sizeof(KEYREC) * n, 0) != 0) {
    printf("ERROR in fseek() getting key record #%ld\n", n);
    exit(1);
  }

  if (fread((char *)recp, sizeof(KEYREC), 1, keyfile) == 0) {
    printf("ERROR in fread() getting key record #%ld\n", n);
    exit(1);
  }
}

/* fetch ptr record # ptrnum from ptrfile */

long getptrrec(long ptrnum, FILE *ptrfile) {
  long p;

  if (fseek(ptrfile, sizeof(long) *ptrnum, 0) != 0) {
    printf("<B>ERROR!</B> in fseek() getting ptr record # %ld!\n",
	    ptrnum);
    exit(1);
  }
  if (fread((char *)&p, sizeof(long), 1, ptrfile) == 0) {
    printf("<B>ERROR!</B> in fread() getting ptr record #%ld!\n",
	    ptrnum);
    exit(1);
  }

  return(p);
}


/* following routines read in the variables and return qsp pointing to the
 * next thing to read, or qsp = NULL if trouble is detected in the input stream
 */

char *set_db_name(char *qsp) {
  char *dbp = &db_name[0];

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp != '&' && *qsp != '\0')
    *dbp++ = *qsp++;
  *dbp = '\0';
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_target_word(char *qsp) {
  char *twp = &target_word[0];

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp != '&' && *qsp != '\0')
    *twp++ = *qsp++;
  *twp = '\0';
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}


char *set_index_lines(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  index_lines = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}


char *set_index_start(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  index_start = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_context_lines(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_lines = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_context_start(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_start = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_context_width(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_width = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_context_offset(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_offset = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_text_length(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_offset = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_text_start(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  text_start = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_interpret_html(char *qsp) {
  long i = 0;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  interpret_html = i;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}

char *set_context_jump(char *qsp) {
  long i = 0;
  int sign = 1;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  if (*qsp == '-') {
    sign = -1;
    ++qsp;
  }
  if (*qsp == '+')
    ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  context_jump = i * sign;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}


char *set_text_jump(char *qsp) {
  long i = 0;
  int sign = 1;

  if (*++qsp != '=')
    return(NULL);
  ++qsp;
  if (*qsp == '-') {
    sign = -1;
    ++qsp;
  }
  if (*qsp == '+')
    ++qsp;
  while (*qsp >= '0' && *qsp <= '9')
    i = 10 * i + *qsp++ - '0';
  text_jump = i * sign;
  if (*qsp == '&')
    ++qsp;
  return(qsp);
}


/* look up a word in the index and return its number --- SOME DAY!!! ...
 * will have to turn the word to all caps, make sure the file is open, etc.
 */

long seek_word(void) {

  return (0);
}

