/*+++++++++++++++++
  bib2ris - converts bibtex files to RIS files
  markus@mhoenicka.de 2001-8-24
  $Id: bib2ris.c,v 1.13.2.2 2005/07/29 21:15:34 mhoenicka Exp $

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>

   ++++++++++++++++++++++++*/

/* temporary hack to include cgi features. This should be selectable via
   a configure switch */
#define REFDB_CGI 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <btparse.h>  /* header of the bibtex parsing library */
#include <syslog.h>  /* definitions of log levels */
#include <ctype.h>  /* for isxx() */
#include <limits.h>
#include <time.h> /* required by log_print() */
#include <sys/types.h> /* for getpid() */
#include <unistd.h>    /* for getpid() */

#include "refdb.h"
#include "getopt.h"
#include "linklist.h"
#include "pref.h"
#include "bib2ris.h"
#include "strfncs.h"
#include "cgi.h" /* cgi related stuff */

/*+ this array will hold the user preferences +*/
Prefs prefs[23] = {
  {"abbrevfirstname", ""},
  {"forcejabbrev", ""},
  {"listsep", ""},
  {"maparticle", ""},
  {"mapbook", ""},
  {"mapbooklet", ""},
  {"mapconference", ""},
  {"mapinbook", ""},
  {"mapincollection", ""},
  {"mapinproceedings", ""},
  {"mapmanual", ""},
  {"mapmastersthesis", ""},
  {"mapmisc", ""},
  {"mapphdthesis", ""},
  {"mapproceedings", ""},
  {"maptechreport", ""},
  {"mapunpublished", ""},
  {"logfile", ""},
  {"loglevel", ""},
  {"logdest", ""},
  {"refdblib", ""},
  {"autokill", ""},
  {"", ""}
};

/* these are the configurable variables with the compile-time defaults */
char abbrev_firstname[PREFS_BUF_LEN] = "f"; /*+ if t, firstnames are abbreviated +*/
char force_jabbrev[PREFS_BUF_LEN] = ""; /*+ if t, use JO instead of JF even if no periods are found +*/
char listsep[PREFS_BUF_LEN] = ";"; /*+ separator for lists in BibTeX fields +*/
char map_bibtypes[14][PREFS_BUF_LEN] = {"JOUR", "BOOK", "PAMP", "CHAP", "CHAP", "CHAP", "CHAP", "BOOK", "THES", "GEN", "THES", "CONF", "RPRT", "UNPB"}; /*+ map BibTeX fields to RIS +*/
char log_file[PREFS_BUF_LEN] = "/var/log/bib2ris.log"; /* filename of log file */
char log_dest[PREFS_BUF_LEN] = "1"; /*+ default log destination (0 = stderr, 1 = syslog, 2 = log_file +*/
char log_level[PREFS_BUF_LEN] = "6"; /*+ default level up to which messages are logged (0 through 7). -1 means no logging +*/
char autokill[PREFS_BUF_LEN] = "1800"; /*+ default time in seconds until the CGI app will kill itself +*/
char refdblib[PREFS_BUF_LEN] = ""; /* the location of shareable RefDB files */
char confdir[_POSIX_PATH_MAX+1] = ""; /* path to the config files */

int n_read_stdin = 0; /* if 1, data try to squeeze in at stdin */
int n_broken_pipe; /*+ 1 indicates that we attempted to write to a broken pipe +*/
int no_data = 1; /* will be set to 0 if at least one citation is found */
int n_log_level = 6; /* verbosity of message log */
int n_log_dest = 0; /* log destination, default is stderr */
int n_abbrev_first = 0; /* if 1, abbreviate firstnames */
int n_force_jabbrev = 0; /* if 1, always use JO; if 0, use JO only if periods are present */
unsigned int n_autokill = 1800; /* numeric version of autokill */
FILE* fp_log_file = NULL;
Lilibib sentinel; /* linked list for non-standard field mapping */

/* these variables determine the exit code */
int n_incomplete_entry = 0;
int n_unknown_field = 0;
int n_unknown_type = 0;
int n_invalid_mapping = 0;
int n_parse_error = 0;
int n_gen_error = 0;
int loopcounter = 0; /* just for debugging */
int n_cgi = 0; /* if 1, we run as a cgi app */

/* forward declarations of static funtions */
void alrmhandler(int sig);

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  The one and only main function
  
  int main returns 0 if successful, >0 if error. The error code is
                     the sum of the following error conditions:
		     1 = n_gen_error
		     2 = n_incomplete_entry
		     4 = n_unknown_field
		     8 = n_unknown_type
		     16 = n_invalid_mapping
		     32 = n_parse_error;
  
  int argc number of arguments

  char** argv ptr to array of strings with the command line arguments
  
  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int main (int argc, char** argv) {
  struct sigaction act, alrm, oldalrm;
  char msgbuf[512] = "";
  int errcode;
  int i;
  int n_opt;
  int n_readinit = 1;
  size_t n_content_length;
  char mp_bibtypes[14][PREFS_BUF_LEN] = {"", "", "", "", "", "", "", "", "", "", "", "", "", ""};
  char delayed_cgi_errmsg[64] = "";
  char* cgi_data;
  char* request_method;
  char* content_length;
  FILE* errstream;
#ifdef REFDB_CGI
  struct liliform sentinelili;
  struct liliform* ptr_current;
#endif

  sentinel.ptr_next = NULL;
  sentinel.name = NULL;
  sentinel.value = NULL;

  /* test how we were invoked */
#ifdef REFDB_CGI
  n_cgi = is_cgi(&request_method, &content_length, delayed_cgi_errmsg); /* 1 if we are running as a cgi app */
#endif

  /* redirect error messages correctly */
  if (n_cgi) {
    errstream = stdout;
  }
  else {
    errstream = stderr;
  }

  if (!isatty(fileno(stdin))) { /* if we receive data on stdin via a redirection or a pipe */
    n_read_stdin = 1;
  }
    
  /* initialize signal handler */
  n_broken_pipe = 0;

  act.sa_handler = pipehandler;
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;

#ifdef REFDB_CGI
  if (n_cgi) {
    alrm.sa_handler = alrmhandler;
    sigemptyset(&alrm.sa_mask);
    alrm.sa_flags = 0;

    if (sigaction(SIGALRM, &alrm, &oldalrm) != 0) {
      write_err("initializing signal handler failed\n");
      LOG_PRINT(LOG_ERR, "could not initialize signal handler");
      exit(1);
    }
  }
#endif

  /* read preferences */
  prefs[0].varvalue = abbrev_firstname;
  prefs[1].varvalue = force_jabbrev;
  prefs[2].varvalue = listsep;
  prefs[3].varvalue = mp_bibtypes[BT_ARTICLE];
  prefs[4].varvalue = mp_bibtypes[BT_BOOK];
  prefs[5].varvalue = mp_bibtypes[BT_BOOKLET];
  prefs[6].varvalue = mp_bibtypes[BT_CONFERENCE];
  prefs[7].varvalue = mp_bibtypes[BT_INBOOK];
  prefs[8].varvalue = mp_bibtypes[BT_INCOLLECTION];
  prefs[9].varvalue = mp_bibtypes[BT_INPROCEEDINGS];
  prefs[10].varvalue = mp_bibtypes[BT_MANUAL];
  prefs[11].varvalue = mp_bibtypes[BT_MASTERSTHESIS];
  prefs[12].varvalue = mp_bibtypes[BT_MISC];
  prefs[13].varvalue = mp_bibtypes[BT_PHDTHESIS];
  prefs[14].varvalue = mp_bibtypes[BT_PROCEEDINGS];
  prefs[15].varvalue = mp_bibtypes[BT_TECHREPORT];
  prefs[16].varvalue = mp_bibtypes[BT_UNPUBLISHED];
  prefs[17].varvalue = log_file;
  prefs[18].varvalue = log_level;
  prefs[19].varvalue = log_dest;
  prefs[20].varvalue = refdblib;

  /* look for custom config directory */
  for (i = 0; i < argc; i++) {
    if (argv[i][0] == '-' && argv[i][1] == 'y') {
      strncpy(confdir, argv[i+1], _POSIX_PATH_MAX);
      confdir[_POSIX_PATH_MAX] = '\0';
      break;
    }
  }

  if (!n_cgi) {
    /* a slimy hack to detect the -q option before we run getopt */
    for (i = 0; i < argc; i++) {
      if (argv[i][0] == '-' && argv[i][1] == 'q') {
	n_readinit = 0;
	break;
      }
    }

    if (n_readinit) {
      /* read config file settings */
      read_prefs((void*)prefs, "bib2risrc", 0);
    }

    /* read command line settings. These may override the config file settings */
    while ((n_opt = getopt(argc, argv, "e:hl:L:qs:vy:")) != -1) {
      switch (n_opt) {
      case 'e':
	strncpy(log_dest, optarg, PREFS_BUF_LEN);
	log_dest[PREFS_BUF_LEN-1] = '\0';
	break;
      case 'h':
	fprintf(stderr, "bib2ris converts references from BibTeX to RIS format as understood by the refdb package.\n");
	fprintf(stderr, "Usage: bib2ris [-e destination] [-h] [-l log-level] [-L logfile] [-q] [-s separator] [-v] [-y confdir]\nOptions: -e set log destination (0 stderr;1 syslog;2 logfile)\n         -h print this help and exit\n         -l set the log level\n         -L set the log file\n         -q ignore configuration file\n         -s set keyword list separator\n         -v print version info and exit\n         -y look for configuration files in confdir\n");
	exit (0);
	break;
      case 'l':
	strncpy(log_level, optarg, PREFS_BUF_LEN);
	log_level[PREFS_BUF_LEN-1] = '\0';
	break;
      case 'L':
	strncpy(log_file, optarg, PREFS_BUF_LEN);
	log_file[PREFS_BUF_LEN-1] = '\0';
	break;
      case 'q':
	/* do nothing; prevents message about unknown -q option */
	break;
      case 's':
	strncpy(listsep, optarg, PREFS_BUF_LEN);
	listsep[PREFS_BUF_LEN-1] = '\0';
	break;
      case 'v':
	printf("bib2ris %s markus@mhoenicka.de\nYou may redistribute and modify this software under the terms of the GNU General Public License.\n", VERSION);
	exit (0);
	break;
      case 'y':
	/* do nothing, this option is used before getopt runs */
	break;
      case ':':
	fprintf(stderr, "Usage: bib2ris [-e destination] [-h] [-l log-level] [-L logfile] [-q] [-s separator] [-v] [-y confdir]\nOptions: -e set log destination (0 stderr;1 syslog;2 logfile)\n         -h print this help and exit\n         -l set the log level\n         -L set the log file\n         -q ignore configuration file\n         -s set keyword list separator\n         -v print version info and exit\n         -y look for configuration files in confdir\n");
	exit (1);
	break;
      case '?':
	printf("unknown option %c: use bib2ris -h to display usage\n", optopt);
	exit (1);
	break;
      }
    }
  }
  else { /* if we run as cgi */
    read_prefs((void*)prefs, "bib2riscgirc", 0);
  }


#ifdef REFDB_CGI
  if (n_cgi) { /* no killer alarm in interactive mode */
    n_autokill = (unsigned int)atoi(autokill);
  
    /* this alarm will go off after n_autokill seconds and cause the CGI app
       to exit as it is apparently hanging */
    if (n_autokill) {
      alarm(n_autokill);
    }
  }
#endif

  /* convert some configuration variables to numeric versions */
  n_abbrev_first = (abbrev_firstname[0] == 't') ? 1:0;
  n_force_jabbrev = (force_jabbrev[0] == 't') ? 1:0;
  n_log_level = num_loglevel(log_level);
  n_log_dest = num_logdest(log_dest);

  /* set up logging */
  if (n_log_dest == 2) { /* use custom log file */
    if ((fp_log_file = fopen(log_file, "ab")) == NULL) {
      n_log_dest = 1; /* fall back to syslog */
      openlog("bib2ris", LOG_PID|LOG_ODELAY, LOG_USER);
      LOG_PRINT(LOG_WARNING, "could not open custom log file");
    }
  }
  else if (n_log_dest == 1) { /* use syslog */
    openlog("bib2ris", LOG_PID|LOG_ODELAY, LOG_USER);
  }

  LOG_PRINT(LOG_INFO, "bib2ris started");

  /* now report incorrect cgi input, if any */
  if (delayed_cgi_errmsg[0]) {
    LOG_PRINT(LOG_ERR, delayed_cgi_errmsg);
    exit(1);
  }

  if (n_readinit) {
    /* read non-standard field mapping in config file */
    if (!n_cgi) {
      read_prefs((void*)&sentinel, "bib2risrc", 1);
    }
    else {
      read_prefs((void*)&sentinel, "bib2riscgirc", 1);
    }
  }

/*    ptr_lb = &sentinel; */
/*    while ((ptr_lb = get_next_lilibib(ptr_lb)) != NULL) { */
/*      fprintf(stderr, "%s %s\n", ptr_lb->name, ptr_lb->value); */
/*    } */
/*    exit(0); */

  /* check (and fix) map_xx settings: must be valid RIS tags, otherwise use
     defaults */
  fix_bibtypes(mp_bibtypes, 14);

  if (n_cgi) { /* we're running as a cgi app */
#ifdef REFDB_CGI
    /* initialize linked list */
    sentinelili.name[0] = '\0';
    sentinelili.value = NULL;
    sentinelili.ptr_next = NULL;

    /* read string from stdin and chop it */
    n_content_length = atoi(content_length);
  
    cgi_data = malloc(n_content_length + 1);
    if (!cgi_data) {
      cgi_header(CGI_PLAIN);
      fprintf(errstream, "out of memory\n");
      LOG_PRINT(LOG_WARNING, "out of memory");
      exit(1);
    }

    if (fread(cgi_data, 1, n_content_length, stdin) != n_content_length) {
      cgi_header(CGI_PLAIN);
      fprintf(errstream, "could not read cgi data\n");
      free(cgi_data);
      LOG_PRINT(LOG_ERR, "could not read cgi data");
      exit(1);
    }

    cgi_data[n_content_length] = '\0'; /* convert to a C-style string */
    LOG_PRINT(LOG_DEBUG, cgi_data);

    if (decode_cgi(cgi_data, &sentinelili)) {
      delete_all_liliform(&sentinelili);
      free(cgi_data);
      cgi_header(CGI_PLAIN);
      fprintf(errstream, "could not decode cgi data\n");
      LOG_PRINT(LOG_ERR, "could not decode cgi data");
      exit(1);
    }

    free(cgi_data);

    /* deduce command */
    if ((ptr_current = get_liliform(&sentinelili, "addref")) != NULL) {
      cgi_header(CGI_HTML);
      if (process_bib(ptr_current->value, 0)) {
	LOG_PRINT(LOG_ERR, "errors encountered while processing CGI data");
	/*        n_gen_error = 1; */
      }
    }
    else {
      delete_all_liliform(&sentinelili);
      cgi_header(CGI_PLAIN);
      fprintf(errstream, "unknown command\n");
      LOG_PRINT(LOG_ERR, "unknown command");
      exit(1);
    }

    /* clean up */
    delete_all_liliform(&sentinelili);
#endif
  }
  else if (n_read_stdin) { /* read data from stdin */
    if (process_bib("-", 1)) {
      LOG_PRINT(LOG_ERR, "errors encountered while processing stdin");
/*        n_gen_error = 1; */
    }
  }
  else { /* read data from all files on the command line */
    for (i = optind; i < argc; i++) { /* loop over all filenames */
      if (process_bib(argv[i], 1)) {
	sprintf(msgbuf, "errors encountered while processing %s", argv[i]);
	LOG_PRINT(LOG_ERR, msgbuf);
/*  	n_gen_error = 1; */
      }
    }
  }

  /* compute exit code */
  errcode = n_gen_error + 2*n_incomplete_entry + 4*n_unknown_field + 8*n_unknown_type + 16*n_invalid_mapping + 32*n_parse_error;
  sprintf(msgbuf, "bib2ris finished with error code %d", errcode);
  LOG_PRINT(LOG_INFO, msgbuf);

  delete_all_lilibib(&sentinel);

  exit(errcode);
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  process_bib(): scans the stream for citation information

  int process_bib returns 0 if ok, 1 if some error occurred

  char* infile ptr to a buffer specifying the data source. If file==1:
               - means stdin
	       everything else means a file name
	       if file==0
	       ptr points to a string with BibTeX data

  int file if 1, input comes from stdin or a file, if 0, input data
               is in a string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int process_bib(char* infile, int file) {
  char* value_text;
  char* field_text;
  char* ris_type; /* the current RIS publication type */
  char* new_notes;
  char* address;
  char* new_address;
  char* abstract;
  char* new_abstract;
  char* html_string;
  char fieldname[64];
  char bib_type[16]; /* the current BibTeX publication type */
  char errmsg[ERRMSG_LEN]; /* ToDo: size should be dependent on largest path string */
  char bibvalid[13];
  int n_error = 0;
  int n_total_incomplete = 0;
  int bv_index;
  size_t notes_len;
  size_t address_len;
  AST* ptr_ast;
  AST* ptr_entry;
  AST* ptr_field;
  AST* ptr_value;
  bt_metatype metatype;
  boolean status;
  struct RISPY rispy;
  struct TITLES titles;
  struct PUBINFO pubinfo;
  struct NOTES notes;
  Lilibib* ptr_lb;

  bt_initialize();

  /*    bt_set_stringopts(); */ /* use defaults, hope they work */

  snprintf(errmsg, ERRMSG_LEN, "begin processing %s", infile);
  LOG_PRINT(LOG_INFO, errmsg);

  /* read input file or stdin */
  if (file) {
    ptr_ast = bt_parse_file(infile, 0, &status);
  }
  else {
    ptr_ast = bt_parse_entry_s(infile, "cgidata", 0, 0, &status);
  }
  
  if (ptr_ast == NULL) {
    bt_cleanup();
    snprintf(errmsg, 511, "parsing error while processing %s", infile);
    LOG_PRINT(LOG_CRIT, errmsg);
    return 1;
  }

  ptr_entry = NULL;

  if (n_cgi) {
    if ((html_string = load_html("refdbadd_head", refdblib)) == NULL) {
      LOG_PRINT(LOG_CRIT, "failed to load template");
      printf("failed to load template\n");
      return 1;
    }
    else {
      printf("%s", html_string);
      free(html_string);
    }
  }

  /* loop over all entries */
  while ((ptr_entry = bt_next_entry(ptr_ast, ptr_entry)) != NULL) {
    metatype = bt_entry_metatype(ptr_entry);
    if (metatype != BTE_REGULAR) {
      continue;
    }
    no_data = 0;
    ptr_field = NULL;

    /* start RIS entry with a newline */
    printf("\n");

    /* next comes the type tag */
    print_type(bib_type, &ris_type, ptr_entry);

    /* now the key */
    printf("ID  - %s\n", bt_entry_key(ptr_entry));

    /* initialize stuff */
    (rispy.year)[0] = '\0';
    (rispy.month)[0] = '\0';
    (rispy.day)[0] = '\0';
    (rispy.edition)[0] = '\0';
    (rispy.howpublished)[0] = '\0';

    titles.title = NULL;
    titles.booktitle = NULL;
    titles.seriestitle = NULL;

    (pubinfo.institution)[0] = '\0';
    (pubinfo.organization)[0] = '\0';
    (pubinfo.publisher)[0] = '\0';
    (pubinfo.school)[0] = '\0';

    notes.annote = NULL;
    notes.note = NULL;

    address = NULL;
    abstract = NULL;

    memset((void*)bibvalid, (int)'f', sizeof(bibvalid));
    bibvalid[12] = '\0';

    /* loop over all fields */
    while ((ptr_field = bt_next_field(ptr_entry, ptr_field, &value_text)) != NULL) {
      ptr_value = NULL;
      bv_index = -1;

      /* at this point we can retrieve the following values: */
      /* bt_entry_type(ptr_entry): article, book etc */
      /* bt_entry_key(ptr_entry): IDxx */
      /* value_text: field name like author, title, year */

      field_text = bt_get_text(ptr_field);
      if (field_text == NULL) {
	continue;
      }

      /* convert the field name to lowercase */
      strncpy(fieldname, value_text, 63); /* arbitrary, custom fields could
					     be longer */
      fieldname[63] = '\0';
      strdn(fieldname);

      /* generate RIS tags based on the field name in value_text
	 some fields can be directly translated into RIS lines;
	 other RIS lines need to be assembled from possibly more
	 than one BibTeX field. in that case we pool the values
	 in structures and evaluate the structures after reading
	 the complete entry */
      if (!strcmp(value_text, "address")) {
	printf("CY  - %s\n", field_text);
      }
      else if (!strcmp(value_text, "annote")) {
	notes.annote = malloc(strlen(field_text)+1);
	if (notes.annote == NULL) {
	  n_error = 1;
	  break;
	}
	strcpy(notes.annote, field_text);
      }
      else if (!strcmp(value_text, "author")) {
	bv_index = BV_AUTHOR;
	print_authors(field_text, "AU", infile);
      }
      else if (!strcmp(value_text, "booktitle")) {
	bv_index = BV_BOOKTITLE;
	titles.booktitle = malloc(strlen(field_text)+1);
	if (titles.booktitle == NULL) {
	  n_error = 1;
	  break;
	}
	strcpy(titles.booktitle, field_text);
      }
      else if (!strcmp(value_text, "chapter")) {
	bv_index = BV_CHAPTER;
	printf("CP  - %s\n", field_text); /* is this correct? */
      }
      else if (!strcmp(value_text, "edition")) {
	strncpy(rispy.edition, field_text, MAX_FIXFIELD_LEN-1);
	(rispy.edition)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "editor")) {
	bv_index = BV_EDITOR;
	print_authors(field_text, "A2", infile);
      }
      else if (!strcmp(value_text, "howpublished")) {
	strncpy(rispy.howpublished, field_text, MAX_FIXFIELD_LEN-1);
	(rispy.howpublished)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "institution")) { 
	bv_index = BV_INSTITUTION;
	strncpy(pubinfo.institution, field_text, MAX_FIXFIELD_LEN-1);
	(pubinfo.institution)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "journal")) {
	bv_index = BV_JOURNAL;
	print_journal(field_text);
      }
      else if (!strcmp(value_text, "month")) {
	strncpy(rispy.month, field_text, MAX_FIXFIELD_LEN-1);
	(rispy.month)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "note")) {
	bv_index = BV_NOTE;
	if (notes.note == NULL) {
	  notes.note = malloc(strlen(field_text)+1);
	  if (notes.note == NULL) {
	    n_error = 1;
	    break;
	  }
	  strcpy(notes.note, field_text);
	  notes_len = strlen(field_text)+1;
	}
	else {
	  if ((new_notes = mstrcat(notes.note, ";", &notes_len, 0)) == NULL) {
	    n_error = 1;
	    break;
	  }
	  else {
	    notes.note = new_notes;
	  }
	  if ((new_notes = mstrcat(notes.note, field_text, &notes_len, 0)) == NULL) {
	    n_error = 1;
	    break;
	  }
	  else {
	    notes.note = new_notes;
	  }
	}
      }
      else if (!strcmp(value_text, "number")) {
	printf("IS  - %s\n", field_text);
      }
      else if (!strcmp(value_text, "organization")) {
	strncpy(pubinfo.organization, field_text, MAX_FIXFIELD_LEN-1);
	(pubinfo.organization)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "pages")) {
	bv_index = BV_PAGES;
	print_pageinfo(field_text);
      }
      else if (!strcmp(value_text, "publisher")) {
	bv_index = BV_PUBLISHER;
	strncpy(pubinfo.publisher, field_text, MAX_FIXFIELD_LEN-1);
	(pubinfo.publisher)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "school")) {
	bv_index = BV_SCHOOL;
	strncpy(pubinfo.school, field_text, MAX_FIXFIELD_LEN-1);
	(pubinfo.school)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if (!strcmp(value_text, "series")) {
	titles.seriestitle = malloc(strlen(field_text)+1);
	if (titles.seriestitle == NULL) {
	  n_error = 1;
	  break;
	}
	strcpy(titles.seriestitle, field_text);
      }
      else if (!strcmp(value_text, "title")) {
	bv_index = BV_TITLE;
	titles.title = malloc(strlen(field_text)+1);
	if (titles.title == NULL) {
	  n_error = 1;
	  break;
	}
	strcpy(titles.title, field_text);
      }
      else if (!strcmp(value_text, "url")) {
	printf("UR  - %s\n", field_text);
      }
      else if (!strcmp(value_text, "volume")) {
	printf("VL  - %s\n", field_text);
      }
      else if (!strcmp(value_text, "year")) {
	bv_index = BV_YEAR;
	strncpy(rispy.year, field_text, MAX_FIXFIELD_LEN-1);
	(rispy.year)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
      }
      else if ((ptr_lb = get_lilibib(&sentinel, value_text)) != NULL) {
	if (!strcmp(ptr_lb->value, "N1")) { /* append to notes */
	  if (notes.note == NULL) {
	    notes.note = malloc(strlen(field_text)+1);
	    if (notes.note == NULL) {
	      n_error = 1;
	      break;
	    }
	    strcpy(notes.note, field_text);
	    notes_len = strlen(field_text)+1;
	  }
	  else {
	    if ((new_notes = mstrcat(notes.note, ";", &notes_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      notes.note = new_notes;
	    }
	    if ((new_notes = mstrcat(notes.note, field_text, &notes_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      notes.note = new_notes;
	    }
	  }
	}
	else if (!strcmp(ptr_lb->value, "N2")) { /* append to abstract */
	  if (abstract == NULL) {
	    abstract = malloc(strlen(field_text)+1);
	    if (abstract == NULL) {
	      n_error = 1;
	      break;
	    }
	    strcpy(abstract, field_text);
	    notes_len = strlen(field_text)+1;
	  }
	  else {
	    if ((new_abstract = mstrcat(abstract, ";", &notes_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      abstract = new_abstract;
	    }
	    if ((abstract = mstrcat(abstract, field_text, &notes_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      abstract = new_abstract;
	    }
	  }
	}
	else if (!strcmp(ptr_lb->value, "PY.day")) {
	  strncpy(rispy.day, field_text, MAX_FIXFIELD_LEN-1);
	  (rispy.day)[MAX_FIXFIELD_LEN-1] = '\0'; /* terminate */
	}
	else if (!strcmp(ptr_lb->value, "KW")) {
	  /* split into single keywords */
	  print_keywords(field_text);
	}
	else if (!strcmp(ptr_lb->value, "AD")) {
	  if (address == NULL) {
	    address = malloc(strlen(field_text)+1);
	    if (address == NULL) {
	      n_error = 1;
	      break;
	    }
	    strcpy(address, field_text);
	    address_len = strlen(field_text)+1;
	  }
	  else {
	    if ((new_address = mstrcat(address, ";", &address_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      address = new_address;
	    }
	    if ((new_address = mstrcat(address, field_text, &address_len, 0)) == NULL) {
	      n_error = 1;
	      break;
	    }
	    else {
	      address = new_address;
	    }
	  }
	}
	else {
	  printf("%s  - %s\n", ptr_lb->value, field_text);
	}
      }

      /* ToDo: this is what computer freaks use as additional fields
	 taken from ftp://ftp.math.utah.edu/pub/tex/bib/sgml.bib
acknowledgement, bibdate, isbn, keywords, lccn, issn, coden, affiliation, classification, thesaurus, abstract, pubcountry, conflocation, price, confdate, day, sponsor, alttitle, confsponsor, treatment, classcodes, language, corpsource, bookpages, type, conftitle, subject, xxnote, sponsororg, affiliationaddress, issue, journalabr, ericno, majordesc, minordesc, identifiers, countrypub, conference, lccnalt, meetingaddress, meetingdate, meetingdate2, dimensions, summary, key, paperback, pricecode, govtdocnumber, xxauthor, xxtitle, availability, stdtype, format, online, review, status, abstract2, keyword, searchkey, in, location, oldlabel, optprice, standardno, xmldata, alttitle-1, alttitle-2, annote-1, annote-2, author-1-address, author-2-address, contents, generalterms, issuedby, meetingabr, numericalindex, oclcno, oldtitle, optcoden, publishersnote, source, xxisbn, xxpages, xxprice, xxxnote      */



      else {
	snprintf(errmsg, ERRMSG_LEN, "unknown field: %s, value: %s\n", value_text, field_text);
	LOG_PRINT(LOG_WARNING, errmsg);
	n_unknown_field = 1;
      }

      if (bv_index != -1) {
	bibvalid[bv_index] = 't';
      }
    } /* end while ptr_field */

    if (n_error) {
      LOG_PRINT(LOG_ERR, "out of memory");
      n_gen_error = 1;
    }

    /* dump all buffered tags now */
    print_py(&rispy);

    print_titles(ris_type, &titles); /* deallocates all referenced strings */

    print_pubinfo(bib_type, &pubinfo);

    print_notes(&notes); /* deallocates all referenced strings */

    print_poolstring(address, "AD"); 

    print_poolstring(abstract, "N2"); 

    n_total_incomplete += validate_bib(bib_type, bibvalid, ptr_entry);

    /* finish entry with the end tag */
    printf("ER  - \n");
    loopcounter++; /* only for debugging */
  } /* end while ptr_entry */

  if (n_cgi) {
    if ((html_string = load_html("refdbadd_foot", refdblib)) == NULL) {
      LOG_PRINT(LOG_CRIT, "failed to load template");
      printf("failed to load template\n");
      return 1;
    }
    else {
      printf("%s",html_string);
      free(html_string);
    }
  }

  /* cleanup */
  if (!file) {
    ptr_ast = bt_parse_entry_s(NULL, "cgidata", 0, 0, &status);
  }

  bt_cleanup();

  if (n_total_incomplete) {
    n_incomplete_entry = 1;
    snprintf(errmsg, ERRMSG_LEN, "%d incomplete datasets while processing %s", n_total_incomplete, infile);
    LOG_PRINT(LOG_INFO, errmsg);
    return 1;
  }

  snprintf(errmsg, ERRMSG_LEN, "done processing %s", infile);
  LOG_PRINT(LOG_INFO, errmsg);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  validate_bib(): validates input dataset. This fn checks whether all
                  required fields for a given publication type are
		  present.

  int validate_bib: returns 0 if dataset is ok. returns 1 if something
                  is missing

  char* bib_type ptr to a buffer containing the current BibTeX
                  publication type

  char* bibvalid ptr to a char map indicating which fields are present

  AST* ptr_entry ptr to a structure with the current dataset

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int validate_bib(char* bib_type, char* bibvalid, AST* ptr_entry) {
  size_t i;
  int nincomplete = 0;
  char errmsg[ERRMSG_LEN] = "";
  char essential[13];
  char* id;

  memset((void*)essential, (int)'f', sizeof(essential));
  essential[12] = '\0';

  /* first we set up a char map indicating the fields which are essential
     for the current publication type. The corresponding chars are set to
     't' unless either of two fields are essential. In that case both
     fields are set to an identical char other than 't' and other than 'f' */
  if (!strcmp(bib_type, "article")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_JOURNAL] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "book")) {
    essential[BV_AUTHOR] = 'x';
    essential[BV_EDITOR] = 'x';
    essential[BV_TITLE] = 't';
    essential[BV_PUBLISHER] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "booklet") || !strcmp(bib_type, "manual")) {
    essential[BV_TITLE] = 't';
  }
  else if (!strcmp(bib_type, "conference") || !strcmp(bib_type, "inproceedings")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_BOOKTITLE] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "inbook")) {
    essential[BV_AUTHOR] = 'x';
    essential[BV_EDITOR] = 'x';
    essential[BV_TITLE] = 't';
    essential[BV_CHAPTER] = 'y';
    essential[BV_PAGES] = 'y';
    essential[BV_PUBLISHER] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "incollection")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_BOOKTITLE] = 't';
    essential[BV_PUBLISHER] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "mastersthesis") || !strcmp(bib_type, "phdthesis")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_SCHOOL] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "proceedings")) {
    essential[BV_TITLE] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "techreport")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_INSTITUTION] = 't';
    essential[BV_YEAR] = 't';
  }
  else if (!strcmp(bib_type, "unpublished")) {
    essential[BV_AUTHOR] = 't';
    essential[BV_TITLE] = 't';
    essential[BV_NOTE] = 't';
  }

  /* loop over all possible fields. If a field (and its alternative
     if applicable) is found missing, append the name of the field to
     the error string. The resulting error message won't exactly be
     Queen's English but it'll be sufficiently clear to indicate the
     problem */
  for (i = 0; i < sizeof(essential); i++) {
    /* if a field is required but missing, generate error */
    if (essential[i] == 't' && bibvalid[i] == 'f') {
      nincomplete = 1;
      strncat(errmsg, bv_names[i], ERRMSG_LEN-strlen(errmsg));
    }
    else if (essential[i] != 'f' && bibvalid[i] == 'f' && i < sizeof(essential)-1) {
      /* if a field or an alternative is required but missing, check
	 the alternative. If that is missing too, generate error */
      id = strchr(&essential[i+1], (int)(essential[i])); 
      if (id != NULL && bibvalid[id-essential] == 'f') {
	nincomplete = 1;
	strncat(errmsg, bv_names[i], ERRMSG_LEN-strlen(errmsg));
	strncat(errmsg, "or ", ERRMSG_LEN-strlen(errmsg));
	strncat(errmsg, bv_names[id-essential], ERRMSG_LEN-strlen(errmsg));
      }
    }
  }
  /* else: other publication types do not have any required fields */

  if (nincomplete) {
    id = bt_entry_key(ptr_entry);
    strncat(errmsg, "missing from ", ERRMSG_LEN-strlen(errmsg));
    strncat(errmsg, id, ERRMSG_LEN-strlen(errmsg));

    LOG_PRINT(LOG_WARNING, errmsg);
    return 1;
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  fix_bibtypes(): checks the RIS types read from the configuration
                  file. If ok, the new value will be used in the
		  global mapping array. If invalid, the default
		  will be used

  int fix_bibtypes returns 0 if ok, 1 if an error occurred

  char mp_bibtypes[][] array with the BIB->RIS mapping

  int size size of first dimension of mp_bibtypes (number of entries)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int fix_bibtypes(char mp_bibtypes[14][PREFS_BUF_LEN], int size) {
  int i;
  int n_error = 0;
  char ris_type[16];
  char errmsg[ERRMSG_LEN];

  /* loop over all map entries */
  for (i = 0; i < size; i++) {
    if (mp_bibtypes[i][0]) {
      strncpy(ris_type, mp_bibtypes[i], 15);
      ris_type[15] = '\0';
      strup(ris_type);
      if (strcmp(ris_type, "ABST") && strcmp(ris_type, "ADVS") &&
	  strcmp(ris_type, "ART") && strcmp(ris_type, "BILL") &&
	  strcmp(ris_type, "BOOK") && strcmp(ris_type, "CASE") &&
	  strcmp(ris_type, "CHAP") && strcmp(ris_type, "COMP") &&
	  strcmp(ris_type, "CONF") && strcmp(ris_type, "CTLG") &&
	  strcmp(ris_type, "DATA") && strcmp(ris_type, "ELEC") &&
	  strcmp(ris_type, "GEN") && strcmp(ris_type, "HEAR") &&
	  strcmp(ris_type, "ICOMM") && strcmp(ris_type, "INPR") &&
	  strcmp(ris_type, "JFULL") && strcmp(ris_type, "JOUR") &&
	  strcmp(ris_type, "MAP") && strcmp(ris_type, "MGZN") &&
	  strcmp(ris_type, "MPCT") && strcmp(ris_type, "MUSIC") &&
	  strcmp(ris_type, "NEWS") && strcmp(ris_type, "PAMP") &&
	  strcmp(ris_type, "PAT") && strcmp(ris_type, "PCOMM") &&
	  strcmp(ris_type, "RPRT") && strcmp(ris_type, "SER") &&
	  strcmp(ris_type, "SLIDE") && strcmp(ris_type, "SOUND") &&
	  strcmp(ris_type, "STAT") && strcmp(ris_type, "THES") &&
	  strcmp(ris_type, "UNBILL") && strcmp(ris_type, "UNPB") &&
	  strcmp(ris_type, "VIDEO")) {
	snprintf(errmsg, ERRMSG_LEN, "unknown RIS type %s, use default instead", ris_type);
	LOG_PRINT(LOG_WARNING, errmsg);
	n_invalid_mapping = 1;
	n_error++;
	/* still use default value */
      }
      else {
	strcpy(map_bibtypes[i], ris_type); /* use new value */
      }
    }
    /* else: if no entry in config file, use default value */
  } /* end for loop */
  return (n_error) ? 1:0;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_type(): prints the TY tag based on the bibtex entry type

  char* print_type returns ptr to result buffer

  char* bib_type ptr to a buffer that receives the BibTeX type string
                must hold at least 16 chars

  char** ris_type ptr to ptr that will be set to point to the RIS
                type

  AST* ptr_entry ptr to a node retrieved with bt_next_entry()

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_type(char* bib_type, char** ris_type, AST* ptr_entry) {
  char* type;
  char errmsg[ERRMSG_LEN];

  if (ptr_entry == NULL) {
    n_parse_error = 1;
    return NULL;
  }

  type = bt_entry_type(ptr_entry);
  if (type == NULL) {
    n_parse_error = 1;
    return NULL;
  }

  strncpy(bib_type, type, 15);
  bib_type[15] = '\0';
  strdn(bib_type);

  /* poor man's associative array... */
  if (!strcmp(bib_type, "article")) {
    *ris_type = map_bibtypes[BT_ARTICLE];
  }
  else if (!strcmp(bib_type, "book")) {
    *ris_type =  map_bibtypes[BT_BOOK];
  }
  else if (!strcmp(bib_type, "booklet")) {
    *ris_type = map_bibtypes[BT_BOOKLET]; 
  }
  else if (!strcmp(bib_type, "conference")) {
    *ris_type = map_bibtypes[BT_CONFERENCE];
  }
  else if (!strcmp(bib_type, "inbook")) {
    *ris_type = map_bibtypes[BT_INBOOK];
  }
  else if (!strcmp(bib_type, "incollection")) {
    *ris_type = map_bibtypes[BT_INCOLLECTION];
  }
  else if (!strcmp(bib_type, "inproceedings")) {
    *ris_type = map_bibtypes[BT_INPROCEEDINGS];
  }
  else if (!strcmp(bib_type, "manual")) {
    *ris_type = map_bibtypes[BT_MANUAL];
  }
  else if (!strcmp(bib_type, "mastersthesis")) {
    *ris_type = map_bibtypes[BT_MASTERSTHESIS];
  }
  else if (!strcmp(bib_type, "proceedings")) {
    *ris_type = map_bibtypes[BT_PROCEEDINGS];
  }
  else if (!strcmp(bib_type, "techreport")) {
    *ris_type = map_bibtypes[BT_TECHREPORT];
  }
  else if (!strcmp(bib_type, "phdthesis")) {
    *ris_type = map_bibtypes[BT_PHDTHESIS];
  }
  else if (!strcmp(bib_type, "unpublished")) {
    *ris_type = map_bibtypes[BT_UNPUBLISHED];
  }
  else { /* misc */
    *ris_type = map_bibtypes[BT_MISC];
    if (strcmp(bib_type, "misc")) {
      snprintf(errmsg, ERRMSG_LEN, "unknown publication type %s", bib_type);
      n_unknown_type = 1;
      LOG_PRINT(LOG_WARNING, errmsg);
    }
  }

  printf("TY  - %s\n", *ris_type);
  
  return *ris_type;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_journal(): creates the JO/JF tags

  int print_journal returns 0 if ok, 1 if error

  char* journal ptr to a buffer with the journal name

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_journal(char* journal) {
  int i;
  int len;
  char* my_journal;
  char* final_journal;
  char* start;
  char* period;
  char* next_start;
  my_journal = malloc(strlen(journal)+1);
  if (my_journal == NULL) {
    LOG_PRINT(LOG_ERR, "out of memory");
    n_gen_error = 1;
    return 1;
  }
  my_journal[0] = '\0';

  /* eliminate spaces after dots and collapse consecutive spaces to
     a single space */
  start = journal;
  i = 0;
  while (journal[i]) {
    if ((journal[i] == '.' || journal[i] == ' ') && journal[i+1] == ' ') {
      period = &journal[i+1];
      i++;
      while (journal[i] == ' ') {
	i++;
      }
      next_start = &journal[i];
      len = strlen(my_journal);
      strncpy(&my_journal[len], start, period-start);
      my_journal[len+(period-start)] = '\0';
      start = next_start;
    }
    else {
      i++;
    }
  }

  strcat(my_journal, start);

  /* remove leading or trailing whitespace */
  final_journal = stripwhite(my_journal, 0, 0);

  /* assume abbreviated name if there is at least one period.
     this fails to catch the cases where the words are abbreviated
     without a trailing period */
  
  if (strchr(final_journal, (int)'.') != NULL || n_force_jabbrev) {
    printf("JO  - %s\n", final_journal); 
  }
  else {
    printf("JF  - %s\n", final_journal); 
  }

  free(my_journal);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_py(): creates the publication year tag

  int print_py returns 0 (no error checking yet)

  struct RISPY* rispy ptr to a structure with all relevant strings

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_py(struct RISPY* rispy) {
  char pystring[267]; /* yyyy/mm/dd/otherinfo with otherinfo <= 255 chars */
  char bufstring[MAX_FIXFIELD_LEN];

  strcpy(pystring, get_year(bufstring, rispy->year));
  strcat(pystring, get_month(bufstring, rispy->month));
  strcat(pystring, get_day(bufstring, rispy->day));
  
  /* lacking a better idea we just append edition and howpublished here
     we make sure that we don't get a buffer overflow so the second
     string may get truncated */
  strcat(pystring, rispy->edition);
  
  if ((rispy->howpublished)[0] && strlen(rispy->edition) < MAX_FIXFIELD_LEN-1) {
    strcat(pystring, ";");
    strncat(pystring, rispy->howpublished, MAX_FIXFIELD_LEN-strlen(rispy->edition)-2);
  }

  printf("PY  - %s\n", pystring);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_authors(): creates the author/editor tags

  int print_authors returns 0 (no error checking yet)

  char* authorstring the string to scan for authors

  char* tagname ptr to two-letter tag name, e.g. "AU" for primary
                authors, "A2" for secondary etc

  char* infile name of input file (for error messages)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_authors(char* authorstring, char* tagname, char* infile) {
  int i, j;
  int nfirst = 1;
  int navail; /* maximum length of a RIS author field */
  int nitem_len;
  char namestring[MAX_FIXFIELD_LEN];
  char firstabbrev[3] = "x.";
  bt_stringlist* ptr_authorlist;
  bt_name* ptr_partlist;

  ptr_authorlist = bt_split_list(authorstring, "and", infile, -1, "name");

  /* walk over all substrings (individual author names) */
  for (i = 0; i < ptr_authorlist->num_items; i++) {
    /* split the name into its components */
    ptr_partlist = bt_split_name((ptr_authorlist->items)[i], infile, -1, i);


    /* make sure we stop in time */
    navail = MAX_FIXFIELD_LEN - 1;

    /* last name goes first, optionally with a "von" part */
    namestring[0] = '\0';
    for (j = 0; j < ptr_partlist->part_len[BTN_VON]; j++) {
      if ((nitem_len = strlen(ptr_partlist->parts[BTN_VON][j])) <= navail) {
	strcat(namestring, ptr_partlist->parts[BTN_VON][j]);
	navail -= nitem_len;
      }
      else {
	goto haveall;
      }
    }
    if (j > 0) { /* add a space only if there was some "von" output */
      if (navail >= 1) {
	strcat(namestring, " ");
	navail--;
      }
      else {
	goto haveall;
      }
    }

    /* the "real" last name parts */
    for (j = 0; j < ptr_partlist->part_len[BTN_LAST]; j++) {
      if ((nitem_len = strlen(ptr_partlist->parts[BTN_LAST][j])) <= navail) {
	strcat(namestring, ptr_partlist->parts[BTN_LAST][j]);
	navail -= nitem_len;
      }
      else {
	goto haveall;
      }
    }

    /* now the first names */
    for (j = 0; j < ptr_partlist->part_len[BTN_FIRST]; j++) {
      if (nfirst) { /* add comma after last name only once */
	nfirst = 0;
	if (navail >= 1) {
	  strcat(namestring, ",");
	  navail--;
	}
	else {
	  goto haveall;
	}
      }

      /* add space, except when the previous char is a period or a comma */
      if(j < ptr_partlist->part_len[BTN_FIRST]){
	if (navail >= 1) {
	  if (namestring[strlen(namestring)-1] != '.'
	    && namestring[strlen(namestring)-1] != ',') {
	    strcat(namestring, " ");
	    navail--;
	  }
	}
	else {
	  goto haveall;
	}
      }

      if (n_abbrev_first) { /* abbreviate firstnames */
	if (navail >= 2) {
	  firstabbrev[0] = (char)toupper(ptr_partlist->parts[BTN_FIRST][j][0]);
	  strcat(namestring, firstabbrev);
	  navail -= 2;
	}
	else {
	  goto haveall;
	}
      }
      else { /* use firstname parts as they come */
	if ((nitem_len = strlen(ptr_partlist->parts[BTN_FIRST][j])) <= navail) {
	  strcat(namestring, ptr_partlist->parts[BTN_FIRST][j]);
	  navail -= nitem_len;
	}
	else {
	  goto haveall;
	}
      }
    }

    /* now the pedigree suffix */
    nfirst = 1;
    for (j = 0; j < ptr_partlist->part_len[BTN_JR]; j++) {
      if (nfirst) { /* need the comma once before the first part */
	nfirst = 0;
	if (navail >= 1) {
	  strcat(namestring, ",");
	  navail--;
	}
	else {
	  goto haveall;
	}
      }
      if ((nitem_len = strlen(ptr_partlist->parts[BTN_JR][j])) <= navail) {
	strcat(namestring, ptr_partlist->parts[BTN_JR][j]);
	navail -= nitem_len;
      }
      else {
	goto haveall;
      }
    }
    haveall:
    printf("%s  - %s\n", tagname, namestring); 

    bt_free_name(ptr_partlist);
  }

  bt_free_list(ptr_authorlist);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_titles(): creates the title tags

  int print_titles returns 0 (no error checking yet)

  char* ris_type ptr to a buffer with the current RIS publication
                   type

  struct TITLES* titles ptr to a struct with the available titles

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_titles(char* ris_type, struct TITLES* titles) {
  if ((!strcmp(ris_type, "BOOK") || 
       !strcmp(ris_type, "PAMP") || 
       !strcmp(ris_type, "CONF")) && titles->title) {
    printf("BT  - %s\n", titles->title);
  }
  else if (!strcmp(ris_type, "CHAP") || !strcmp(ris_type, "PAMP")) {
    if (titles->title) {
      printf("TI  - %s\n", titles->title);
    }
    if (titles->booktitle) {
      printf("BT  - %s\n", titles->booktitle);
    }
    if (titles->seriestitle) {
      printf("T3  - %s\n", titles->seriestitle);
    }
  }
  else if (titles->title) {
    printf("TI  - %s\n", titles->title);
  }

  /* free memory */
  if (titles->title) {
    free(titles->title);
  }
  if (titles->booktitle) {
    free(titles->booktitle);
  }
  if (titles->seriestitle) {
    free(titles->seriestitle);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_pubinfo(): creates the PB tag

  int print_pubinfo returns 0 (no error checking yet)

  char* ris_type ptr to a buffer containing the current RIS publication
                 type

  struct PUBINFO* pubinfo ptr to a struct with the notes

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_pubinfo(char* bib_type, struct PUBINFO* pubinfo) {
  char* the_pb;

  /* in lieu of a good idea we just set up arbitrary priorities and
     then choose the non-zero length string with the highest priority */

  if (pubinfo->publisher[0]) {
    the_pb = pubinfo->publisher;
  }
  else if (pubinfo->organization[0]) {
    the_pb = pubinfo->organization;
  }
  else if (pubinfo->institution[0]) {
    the_pb = pubinfo->institution;
  }
  else if (pubinfo->school[0]) {
    the_pb = pubinfo->school;
  }
  else {
    the_pb = NULL;
  }
  if (the_pb != NULL) {
    printf("PB  - %s\n", the_pb);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_notes(): creates the N1 tag

  int print_notes returns 0 (no error checking yet)

  struct NOTES* notes ptr to a struct with the notes

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_notes(struct NOTES* notes) {
  if (notes->note) {
    printf("N1  - %s", notes->note);
    if (notes->annote) {
      printf(";%s", notes->annote);
    }
    printf("\n");
  }
  else if (notes->annote) {
    printf("N1  - %s\n", notes->annote);
  }

  /* free memory */
  if (notes->note) {
    free(notes->note);
  }
  if (notes->annote) {
    free(notes->annote);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_poolstring(): creates a given tag with a pooled string and
                      frees the memory allocated for the string

  int print_poolstring returns 0 (no error checking yet)

  char* the_string ptr to buffer with the address info

  char* tag ptr to buffer with the two-letter tag to print

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_poolstring(char* the_string, char* tag) {
  if (the_string != NULL) {
    printf("%s  - %s\n", tag, the_string);
    free(the_string);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_keywords(): creates KW tags

  int print_keywords returns 0 (no error checking yet)

  char* kwlist ptr to buffer with the address info

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_keywords(char* kwlist) {
  char* token;
  char my_listsep[PREFS_BUF_LEN] = " \t";

  if (strcmp(listsep, "spc")) {
    strcpy(my_listsep, listsep);
  }

  token = strtok(kwlist, my_listsep);
  while (token) {
    printf("KW  - %s\n", stripwhite(token, 0, 0));    
    token = strtok(NULL, my_listsep);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_pageinfo(): creates the start/end page tags

  int print_pageinfo returns 0 (no error checking yet)

  char* pagestring the string to scan for pages

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int print_pageinfo(char* pagestring) {
  char* token;
  char* token1;
  char* mystring;
  char fullstring[MAX_FIXFIELD_LEN*2] = "";
  char startpage[MAX_FIXFIELD_LEN] = "";
  char endpage[MAX_FIXFIELD_LEN] = "";

  /* possible formats are:
     1. single page
     2. page range with single hypen
     3. page range with double hyphen
     4. xx+ indicating page xx ssq. 
     5. comma-separated list of single pages
     6. comma-separated list of a combination of 1, 2, 3, or 4

     this function currently handles only 1-5 correctly. 6 will
     be handled more or less correctly if the tokens are arranged
     in ascending order. E.g. "5,7,14-16,19" will be converted to:
     SP  - 5
     EP  - 19
     which includes everything but is not strictly correct (and
     if the string starts or ends with a range the result is
     truly ugly).
     we can't bet that the page info is numeric as some journals
     (e.g. Am.J.Physiol.) use letters or strings to "enhance" the
     page information */

  /* ToDo: in order to handle 6 correctly: break pagestring into
     comma-separated tokens, apply magic to each single token */
  strncpy(fullstring, pagestring, (MAX_FIXFIELD_LEN*2)-1);
  fullstring[(MAX_FIXFIELD_LEN*2)-1] = '\0';
  mystring = stripwhite(fullstring, 0, 0);

  /* try page range */
  if ((token = strchr(mystring, (int)'-')) != NULL) {
    *token = '\0';
    strncpy(startpage, mystring, MAX_FIXFIELD_LEN-1);
    startpage[MAX_FIXFIELD_LEN-1] = '\0';
    token++;
    if (*token == '-') {
      token++;
    }
    if (*token) {
      strncpy(endpage, token, MAX_FIXFIELD_LEN-1);
      endpage[MAX_FIXFIELD_LEN-1] = '\0';
    }
  }
  /* try comma-separated list and convert into a range */
  else if ((token = strchr(mystring, (int)',')) != NULL) {
    /* first retrieve the EP string as we modify the string
       to copy the SP string */
    token1 = strrchr(mystring, (int)','); /* will at least find the same comma
					    as strchr() */
    strncpy(endpage, token1+1, MAX_FIXFIELD_LEN-1);
    endpage[MAX_FIXFIELD_LEN-1] = '\0';
    *token = '\0';
    strncpy(startpage, mystring, MAX_FIXFIELD_LEN-1);
    startpage[MAX_FIXFIELD_LEN-1] = '\0';
  }
  /* try strange + syntax */
  else if ((token = strchr(mystring, (int)'+')) != NULL) {
    *token = '\0';
    strncpy(startpage, mystring, MAX_FIXFIELD_LEN-1);
    startpage[MAX_FIXFIELD_LEN-1] = '\0';
  }
  /* take it as it is */
  else {
    strncpy(startpage, mystring, MAX_FIXFIELD_LEN-1);
    startpage[MAX_FIXFIELD_LEN-1] = '\0';
  }

  if (*startpage) {
    printf("SP  - %s\n", startpage);
  }
  if (*endpage) {
    printf("EP  - %s\n", endpage);
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  get_year(): extracts a max. 4-digit year string from an input string

  char* get_year returns a ptr to the result string

  char* bufstring ptr to a buffer with at least 5 chars which will
                  receive the result string

  char* yearstring input string containing the year information

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* get_year(char* bufstring, char* yearstring) {
  char* result;

  /* just make sure we got valid ptrs */
  if (bufstring == NULL || yearstring == NULL) {
    return NULL;
  }

  if ((result = find_num(bufstring, yearstring, 4)) == NULL) {
    strcpy(bufstring, "/");
  }
  else {
    sprintf(bufstring, "%s/", result);
  }

  return bufstring;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  get_month(): extracts a max. 2-digit month string from an input string

  char* get_month returns a ptr to the result string

  char* bufstring ptr to a buffer with at least 5 chars which will
                  receive the result string

  char* monthstring input string containing the month information

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* get_month(char* bufstring, char* monthstring) {
  char* result;
  char month[16];
  char monthbuf[16];

  /* just make sure we got valid ptrs */
  if (bufstring == NULL || monthstring == NULL) {
    return NULL;
  }

  /* truncate and lowercase input string */
  bufstring[0] = '\0';
  strncpy(month, monthstring, 15); /* maybe too short in other languages? */
  month[15] = '\0';
  strdn(month);

  /* the month information may be available in several formats:
     1. numerical one or two-digit
     2. full month name in any language
     3. abbreviated month name in any language
     currently we handle only numerical and full/abbrev in English */

  /* first try full and abbreviated month names */
  if (!strcmp(month, "january") || !strcmp(month, "jan")) {
    strcpy(bufstring, "1/");
  }
  else if (!strcmp(month, "february") || !strcmp(month, "feb")) {
    strcpy(bufstring, "2/");
  }
  else if (!strcmp(month, "march") || !strcmp(month, "mar")) {
    strcpy(bufstring, "3/");
  }
  else if (!strcmp(month, "april") || !strcmp(month, "apr")) {
    strcpy(bufstring, "4/");
  }
  else if (!strcmp(month, "may")) {
    strcpy(bufstring, "5/");
  }
  else if (!strcmp(month, "june") || !strcmp(month, "jun")) {
    strcpy(bufstring, "6/");
  }
  else if (!strcmp(month, "july") || !strcmp(month, "jul")) {
    strcpy(bufstring, "7/");
  }
  else if (!strcmp(month, "august") || !strcmp(month, "aug")) {
    strcpy(bufstring, "8/");
  }
  else if (!strcmp(month, "september") || !strcmp(month, "sep")) {
    strcpy(bufstring, "9/");
  }
  else if (!strcmp(month, "october") || !strcmp(month, "oct")) {
    strcpy(bufstring, "10/");
  }
  else if (!strcmp(month, "november") || !strcmp(month, "nov")) {
    strcpy(bufstring, "11/");
  }
  else if (!strcmp(month, "december") || !strcmp(month, "dec")) {
    strcpy(bufstring, "12/");
  }

  if (*bufstring) {
    return bufstring;
  }

  /* now try numeric month */
  if ((result = find_num(monthbuf, monthstring, 2)) == NULL) {
    strcpy(bufstring, "/");
  }
  else {
    sprintf(bufstring, "%s/", result);
  }

  return bufstring;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  get_day(): extracts a max. 2-digit day string from an input string

  char* get_day returns a ptr to the result string

  char* bufstring ptr to a buffer with at least 5 chars which will
                  receive the result string

  char* daystring input string containing the day information

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* get_day(char* bufstring, char* daystring) {
  char* result;
  char daybuf[16];

  /* just make sure we got valid ptrs */
  if (bufstring == NULL || daystring == NULL) {
    return NULL;
  }

  /* this fn currently handles only numeric day formats. Others
   are rare but possible (think "Ides of March" or something) */

  if ((result = find_num(daybuf, daystring, 2)) == NULL) {
    strcpy(bufstring, "/");
  }
  else {
    sprintf(bufstring, "%s/", result);
  }

  return bufstring;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_num(): finds a positive integer string of a given length in an
              input string

  char* find_num returns a ptr to the result buffer

  char* bufstring ptr to a buffer that will receive the result. Must hold
                  at least len+1 chars

  char* numstring ptr to the buffer containing the number to find

  size_t len the length of the number string you're looking for. E.g. len
                  is 4 if you try to find "1999"

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* find_num(char* bufstring, char* numstring, size_t len) {
  char* cur;
  char* end;
  char* new_num;
  int num;

  /* just make sure we got valid ptrs */
  if (bufstring == NULL || numstring == NULL) {
    return NULL;
  }

  /* take the short way home if the request does not make any sense */
  if (len <= 0) {
    bufstring[0] = '\0';
    return bufstring;
  }

  cur = numstring;
  end = cur+strlen(numstring);

  /* find the first digit (skip any leading zeros) */
  while (*cur && (!isdigit(*cur) || *cur == '0')) {
    cur++;
  }

  if (cur == end) { /* no digits found */
    bufstring[0] = '\0';
    return bufstring;
  }

  /* get a buffer to hold the remaining string. We don't want to
     modify the original string */
  new_num = malloc(len+1);
  if (new_num == NULL) {
    LOG_PRINT(LOG_ERR, "out of memory");
    n_gen_error = 1;
    return NULL;
  }

  strcpy(new_num, cur);

  /* chop off the next len characters and convert it to an integer.
     atoi() will end the input string at the first non-digit if any */
  if (strlen(new_num) > len) {
    new_num[len] = '\0';
  }

  num = atoi(new_num);
  free(new_num);

  sprintf(bufstring, "%d", num);

  return bufstring;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pipehandler(): handler for the SIGPIPE signal. Sets the global
                 variable n_broken_pipe to non-zero. This condition
                 can be used by a function writing to a pipe to abort
                 further write attempts. Any function that uses
                 n_broken_pipe should reset it to zero when it is done
                 dealing with the broken pipe.

  void pipehandler

  int sig the received signal

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void pipehandler(int sig) {
  n_broken_pipe = 1;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  log_print(): writes a log message

  void log_print

  int priority the priority level of the log message as in syslog.h

  char* string a string containing the message to be logged

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void log_print(int priority, char* string) {
  /* we must have this fn in the file with main() because FILE* 
     cannot be declared extern (??) */
  time_t the_time;
  char timestring[256];

  if (n_log_dest == 0) { /* output on stderr */
    fprintf(stderr, "%s\n", string);
  }
  else if (n_log_dest == 1) { /* output via syslog */
    syslog(priority, "%s", string);
  }
  else { /* output in user-defined logfile */
    time(&the_time);
    strftime(timestring, 256, "%a %b %d %H:%M:%S %Y", gmtime(&the_time));
    fprintf(fp_log_file, "%d:pid=%d:%s:%s\n", priority, getpid(), timestring, string);
  }
}

#ifdef REFDB_CGI
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  alrmhandler(): handler for the SIGALRM signal. The app will exit.
                 Purpose of this behaviour is to kill hanging CGI
		 programs that could otherwise accumulate until the
		 server dies of memory exhaustion

  void alrmhandler

  int sig the received signal

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void alrmhandler(int sig) {
    LOG_PRINT(LOG_ERR, "received suicide alarm and exited");
    exit(2);
}
#endif
