/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  backend-bibtex.c defines the bibtex backend of refdbd
  markus@mhoenicka.de 8-8-00
  $Id: backend-bibtex.c,v 1.16.2.12 2006/04/10 21:03:02 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/>

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

#include <string.h>
#include <syslog.h> /* for definitions of log message priorities */
#include <stdio.h>
#include <dbi/dbi.h> /* database abstraction layer */

#include "refdb.h"
#include "connect.h"
#include "linklist.h"
#include "backend.h"
#include "strfncs.h"
#include "tokenize.h"
#include "refdbd.h" /* for struct ADDRESULT */
#include "xmlhelper.h"
#include "backend-bibtex.h"
#include "backend-dbib.h"
#include "dbfncs.h"

/* some globals */
extern int n_log_level; /* numeric version of log_level */
extern char main_db[];

/* the style information if load_style() was successful */
extern dbi_result dbi_style_res;

extern char cs_term[];

/* forward declarations of local functions */
static char* bibtexify_author(char** ptr_bibauthor, char* author);
static char* print_field_bibtex(const char* item, struct renderinfo* ptr_rendinfo, const char* start_string);
static int add_partdata_bibtex(char** ptr_buffer, size_t* ptr_buffer_len, struct renderinfo* ptr_rendinfo);
static int add_setdata_bibtex(char** ptr_buffer, size_t* ptr_buffer_len, struct renderinfo* ptr_rendinfo);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  prepare_render_bibtex(): writes a header for the bibtex output of a
                         query

  int prepare_render_bibtex returns 0 if successful, >0 if not

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int prepare_render_bibtex(struct renderinfo* ptr_rendinfo) {
  /* we just make sure that we start with a clean string */
  (*(ptr_rendinfo->ptr_ref))[0] = '\0';
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  finish_render_bibtex(): writes a footer for the bibtex output of a query

  int finish_render_bibtex returns 0 if successful, >0 if failed

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int finish_render_bibtex(struct renderinfo* ptr_rendinfo) {
  char* new_ref;

  if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "\n", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 801;
  }
  
  *(ptr_rendinfo->ptr_ref) = new_ref;

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  render_bibtex() renders a RIS dataset for bibtex output

  int render_bibtex returns 0 if successful, >0 if failed

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int render_bibtex(struct renderinfo* ptr_rendinfo) {
  int nperiodical_type;
  int sql_command_len;
  int i; /* what the hell is this? */
  int errcode; /* receives error code when retrieving periodical */
  int n_multiple_db = 0; /* if 1, driver supports multiple databases per conn */
  int retval = 0;
  unsigned int n_id;
  size_t buffer_len = 4096;
  char have_author = 0;
  char *new_ref;
  char *bibauthor;
  char *sql_command; /* buffer for assembling SQL queries */
  char *quoted_journal;
  const char *item;
  const char *drivername;
  const char *type;
  const char *thesistype = NULL;
  char date_buffer[256];
  char role[2][7] = {"author", "editor"};
  char* buffer;
  dbi_result dbires;
  dbi_result dbirescit;
  struct BIBCONNS bibconns;
  dbi_driver driver;

  bibconns.conn = NULL;
  bibconns.conn_refdb = NULL;
  bibconns.conn_source = NULL;

  if ((buffer = malloc(buffer_len)) == NULL) {
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    retval = 801;
    goto cleanup;
  }
  *buffer = '\0';

  if ((type = get_refdb_type(ptr_rendinfo->dbires)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    retval = 234;
    goto cleanup;
  }

  if (!strcmp(type, "THES")) {
    thesistype = get_refdb_degree(ptr_rendinfo->dbires);
  }

  bibconns.conn = dbi_result_get_conn(ptr_rendinfo->dbires);
  bibconns.conn_source = bibconns.conn;

  driver = dbi_conn_get_driver(bibconns.conn);
  drivername = dbi_driver_get_name(driver);

  if (ptr_rendinfo->nuse_citestyle) {
    sql_command_len = 512;
    sql_command = malloc(sql_command_len);
    if (!sql_command) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }

    /* retrieve basic information about the bibliography style from CITSTYLE into ptr_result */
    quoted_journal = mstrdup((ptr_rendinfo->ptr_biblio_info)->format_string);
    if (!dbi_conn_quote_string(bibconns.conn, &quoted_journal)) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      free(sql_command);
      return 801;
    }

    /* db capability: multiple databases per connection */
    if (!strcmp(my_dbi_conn_get_cap(bibconns.conn, "multiple_db"), "t")) {
      bibconns.conn_refdb = bibconns.conn;
      n_multiple_db = 1;
      sprintf(sql_command, "SELECT ID FROM %s.CITSTYLE WHERE JOURNAL=%s", main_db, quoted_journal);
    }
    else {
      sprintf(sql_command, "SELECT ID FROM CITSTYLE WHERE JOURNAL=%s", quoted_journal);
      /* need another connection to the database refdb */
      if ((bibconns.conn_refdb = connect_to_db(ptr_rendinfo->ptr_clrequest, main_db, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(202));
	free(sql_command);
	return 202;
      }
    }

    free(quoted_journal);
    dbirescit = dbi_conn_query(bibconns.conn_refdb, sql_command);

    LOG_PRINT(LOG_DEBUG, sql_command);
    free(sql_command);

    if (!dbirescit) {
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      return 234;
    }
  
    if (dbi_result_next_row(dbirescit)) {
      n_id = my_dbi_result_get_int_idval(dbirescit, "ID");
      if (!load_style(type, n_id, bibconns.conn_refdb)) {
	LOG_PRINT(LOG_WARNING, get_status_msg(842));
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 842;
      }
    }
    else {
      LOG_PRINT(LOG_WARNING, get_status_msg(269));
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      return 269;
    }

    LOG_PRINT(LOG_DEBUG, "style information loaded");
  }

  /* the first entries should always fit, don't hassle with mstrcat() */
  /*----------------------------------------------------------------*/
  /* type */
  /* the bibtex backend uses the following mapping between RIS types and
     bibtex types */
  /* ABST  @misc
     ADVS  @misc
     ART   @misc
     BILL  @misc
     BOOK  @book
     CASE  @misc
     CHAP  @incollection
     COMP  @misc
     CONF  @inproceedings
     CTLG  @misc
     DATA  @misc
     ELEC  @misc
     GEN   @misc
     HEAR  @proceedings
     ICOMM @misc
     INPR  @misc
     JFULL @article
     JOUR  @article
     MAP   @misc
     MGZN  @article
     MPCT  @misc
     MUSIC @misc
     NEWS  @misc
     PAMP  @misc
     PAT   @misc
     PCOMM @misc
     RPRT  @techreport
     SER   @book
     SLIDE @misc
     SOUND @misc
     STAT  @misc
     THES  @phdthesis (could be mastersthesis as well, ambiguous!)
     UNBILL@misc
     UNPB  @unpublished
     VIDEO @misc */
     
  if (strcmp(type, "JOUR") == 0 || strcmp(type, "JFULL") == 0 || strcmp(type, "MGZN") == 0) {
/*     LOG_PRINT(LOG_DEBUG, "start JOUR"); */
    strcat(*(ptr_rendinfo->ptr_ref), "@ARTICLE{ ");
/*     LOG_PRINT(LOG_DEBUG, "done JOUR"); */
  }
  else if (strcmp(type, "BOOK") == 0 || strcmp(type, "SER") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@BOOK{ ");
  }
  else if (strcmp(type, "CHAP") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@INCOLLECTION{ ");
  }
  else if (strcmp(type, "THES") == 0) {
    if (thesistype && !strcmp(thesistype, "masters")) {
      strcat(*(ptr_rendinfo->ptr_ref), "@MASTERSTHESIS{ ");
    }
    else {
      strcat(*(ptr_rendinfo->ptr_ref), "@PHDTHESIS{ ");
    }
  }
  else if (strcmp(type, "CONF") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@INPROCEEDINGS{ ");
  }
  else if (strcmp(type, "HEAR") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@PROCEEDINGS{ ");
  }
  else if (strcmp(type, "RPRT") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@TECHREPORT{ ");
  }
  else if (strcmp(type, "UNPB") == 0) {
    strcat(*(ptr_rendinfo->ptr_ref), "@UNPUBLISHED{ ");
  }
  else {
    strcat(*(ptr_rendinfo->ptr_ref), "@MISC{ ");
  }

  /*----------------------------------------------------------------*/
  /* ID */
/*   if (ptr_rendinfo->dbname) { */
/*     strcat(*(ptr_rendinfo->ptr_ref), ptr_rendinfo->dbname); */
/*     strcat(*(ptr_rendinfo->ptr_ref), "-ID"); */
/*   } */
/*   else { */
/*     strcat(*(ptr_rendinfo->ptr_ref), "ID"); */
/*   } */
/*   get_refdb_id(ptr_rendinfo->dbires, id); */
/*   if (*id) { */
/*     strcat(*(ptr_rendinfo->ptr_ref), id); */ /* ID */
/*     strcat(*(ptr_rendinfo->ptr_ref), ",\n"); */
  item = get_refdb_citekey(ptr_rendinfo->dbires);
  if (item && *item) {
    /* the citation format is: [dbname:]citekey */
    if (ptr_rendinfo->dbname) {
      strcat(*(ptr_rendinfo->ptr_ref), ptr_rendinfo->dbname);
      strcat(*(ptr_rendinfo->ptr_ref), ":");
    }
    strcat(*(ptr_rendinfo->ptr_ref), item); /* citekey */
    strcat(*(ptr_rendinfo->ptr_ref), ",\n");
  }
  else {
    unload_style(); /* will do no harm if load_style() was not called so we
		       don't have to wrap it with a conditional each time */
    if (!n_multiple_db) {
      dbi_conn_close(bibconns.conn_refdb);
    }
    return 234;
  }

  /*----------------------------------------------------------------*/
  /* pubyear */
  item = get_refdb_pubyear(ptr_rendinfo->dbires, date_buffer);
  if (item != NULL) {
    strcat(*(ptr_rendinfo->ptr_ref), "  YEAR = {");
    strcat(*(ptr_rendinfo->ptr_ref), date_buffer); /* year */
    strcat(*(ptr_rendinfo->ptr_ref), "},\n");
  }

  /*----------------------------------------------------------------*/
  /* the part apparatus (analytic) */


  if (has_part_data(type)) {
    if ((retval = add_partdata_bibtex(&buffer, &buffer_len, ptr_rendinfo)) != 0) {
      goto cleanup;
    }
    else {
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), buffer, ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	retval = 801;
	goto cleanup;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
    }
    *buffer = '\0';
  }

  /*----------------------------------------------------------------*/
  /* the publication apparatus (monographic) */

  /*----------------------------------------------------------------*/
  /* authors */
  /* we loop here to first create authors and then editors if needed */

  for (i = 0; i < 2; i++) {
    have_author = 0;

    dbires = request_authors(bibconns.conn, 2 /* publication */, role[i], NULL, 0, my_dbi_result_get_idval(ptr_rendinfo->dbires, "refdb_id"));

    if (!dbires) {
      unload_style();
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      return 234;
    }

    /* we're done if there are no authors or editors to list */
    if (dbi_result_get_numrows(dbires) == 0) {
      clean_request(dbires);
      continue;
    }

    if (i == 0) { /* authors */
      new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "  AUTHOR = {", ptr_rendinfo->ptr_ref_len, 0);
    }
    else { /* editors */
      new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "  EDITOR = {", ptr_rendinfo->ptr_ref_len, 0);
    }
    if (new_ref == NULL) {
      clean_request(dbires);
      unload_style();
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }

    /* fetch all authors */
    while ((item = get_author(dbires)) != NULL) {
      have_author = 1; /* at least one author is present */


      if (bibtexify_author(&bibauthor, (char*)item) == NULL) {
	clean_request(dbires);
	unload_style();
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }

      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), bibauthor, ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	clean_request(dbires);
	free(bibauthor);
	unload_style();
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	free(bibauthor);
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), " and ", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	clean_request(dbires);
	unload_style();
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
    }
    clean_request(dbires);

    if (have_author) { /* eliminate the trailing and after the last author */
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "},\n", ptr_rendinfo->ptr_ref_len, 5)) == NULL) {
	unload_style();
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
    }
    else {
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), ",\n", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	unload_style();
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
    }
  }

  if (has_periodical_data(type)) {
  /*----------------------------------------------------------------*/
  /* periodical */

    if (ptr_rendinfo->nuse_citestyle) { /* do some formatting */
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "  JOURNAL = {", ptr_rendinfo->ptr_ref_len, 0)) == NULL) { 
	unload_style();
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }

      if ((new_ref = format_journalname(ptr_rendinfo->ptr_ref, ptr_rendinfo->ptr_ref_len, &bibconns, ptr_rendinfo->dbires, dbi_style_res, ptr_rendinfo->database, 3/* bibtex */, NULL, NULL, REFBIBTEX, 0 /* not title as author */)) == NULL) {
	LOG_PRINT(LOG_WARNING, "JOURNALNAME formatting failed");
	unload_style();
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }
    }
    else { /* plain output */
      char* escaped_item;
      nperiodical_type = 4; /* this retrieves the first available periodical
			       name in the order full-abbrev-cust1-cust2. This
			       may not always be what we want so this should
			       be beefed up somehow */

      /* fetch journal of this article */
      if ((item = get_periodical(bibconns.conn, date_buffer, NULL, nperiodical_type, &errcode, 0, my_dbi_result_get_idval(ptr_rendinfo->dbires, "refdb_id"), NULL /* no frequency required */)) != NULL) {
	if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "  JOURNAL = {", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  return 801;
	}
	else {
	  *(ptr_rendinfo->ptr_ref) = new_ref;
	}

	if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
	  unload_style();
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  if (!n_multiple_db) {
	    dbi_conn_close(bibconns.conn_refdb);
	  }
	  return 801;
	}

	if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), escaped_item, ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  return 801;
	}
	else {
	  *(ptr_rendinfo->ptr_ref) = new_ref;
	  free(escaped_item);
	}
      }
    }

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "},\n", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
      unload_style();
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      return 801;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }
  }

  /*----------------------------------------------------------------*/
  /* publication title */
    
  item = get_refdb_booktitle_copy(ptr_rendinfo->dbires);
  if (item != NULL) {
    char titlestring[16] = "  TITLE = {";
    if (has_chapter_data(type)) {
      strcpy(titlestring, "  BOOKTITLE = {");
    }

    if (print_field_bibtex(item, ptr_rendinfo, titlestring) == NULL) {
      free((void*)item);
      clean_request(dbires);
      return 801;
    }
    free((void*)item);
  }

  /*----------------------------------------------------------------*/
  /* edition */
  item = get_refdb_edition(ptr_rendinfo->dbires);
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  EDITION = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }
  
  /*----------------------------------------------------------------*/
  /* volume */
  item = get_refdb_volume(ptr_rendinfo->dbires);
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  VOLUME = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }
  
  /*----------------------------------------------------------------*/
  /* issue */
  item = get_refdb_issue(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  NUMBER = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  /*----------------------------------------------------------------*/
  /* set data */
  if (has_set_data(type)) {
    if ((retval = add_setdata_bibtex(&buffer, &buffer_len, ptr_rendinfo)) != 0) {
      goto cleanup;
    }
    else {
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), buffer, ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	retval = 801;
	goto cleanup;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }
    }
    *buffer = '\0';
  }

  /* we're done using the style info. unload and close conn */
  unload_style();
  if (!n_multiple_db) {
    dbi_conn_close(bibconns.conn_refdb);
  }

  /*----------------------------------------------------------------*/
  /* startpage, endpage */
  item = get_refdb_startpage(ptr_rendinfo->dbires);
  if (item != NULL) {
    char* escaped_item;

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "  PAGES = {", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }

    if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
      unload_style();
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      if (!n_multiple_db) {
	dbi_conn_close(bibconns.conn_refdb);
      }
      return 801;
    }

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), escaped_item, ptr_rendinfo->ptr_ref_len, 0)) == NULL) { /* start page */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
      free(escaped_item);
    }

    item = get_refdb_endpage(ptr_rendinfo->dbires);
    if (item != NULL) {
      char* escaped_item;

      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "-", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
      }

      if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
	unload_style();
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	if (!n_multiple_db) {
	  dbi_conn_close(bibconns.conn_refdb);
	}
	return 801;
      }
      
      if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), escaped_item, ptr_rendinfo->ptr_ref_len, 0)) == NULL) { /* end page */
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*(ptr_rendinfo->ptr_ref) = new_ref;
	free(escaped_item);
      }
    }

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "},\n", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }
  }
  
  /*----------------------------------------------------------------*/
  /* publisher, school, institution */
  item = get_refdb_publisher(ptr_rendinfo->dbires);
  if (item != NULL) {
    char pubfield[20];

    if (strcmp(type, "THES") == 0) {
      strcpy(pubfield, "  SCHOOL = {");
    }
    else if (strcmp(type, "RPRT") == 0) {
      strcpy(pubfield, "  INSTITUTION = {");
    }
    else {
      strcpy(pubfield, "  PUBLISHER = {");
    }

    if (print_field_bibtex(item, ptr_rendinfo, pubfield) == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  /*----------------------------------------------------------------*/
  /* URL, L1 through L4, DOI */

  /* loop over all link types */
  for (i=0; i<6;i++) {
    dbires = request_ulinks(bibconns.conn, my_dbi_result_get_idval(ptr_rendinfo->dbires, "refdb_id"), 0 /* ref entry */, i /* link type */, 0 /* is_temp */, ptr_rendinfo->username);
    if (dbires == NULL) {
      return 234;
    }

    while ((item = get_ulink(dbires)) != NULL) {
      char* full_link = NULL;
      /* currently all types are rendered as URL, but this might need
	 a change later */
      if (i>0 && i<5) {
	full_link = add_root_to_link(item, ptr_rendinfo->pdfroot);
      }

      if (i == 0) { /* UR */
	if (print_field_bibtex(item, ptr_rendinfo, "  URL = { ") == NULL) {
	  clean_request(dbires);
	  return 801;
	}
      }
      else if (i == 1 && full_link) { /* L1 */
	if (print_field_bibtex(full_link, ptr_rendinfo, "  URL = { ") == NULL) {
	  clean_request(dbires);
	  free(full_link);
	  return 801;
	}
	free(full_link);
      }
      else if (i == 2 && full_link) { /* L2 */
	if (print_field_bibtex(full_link, ptr_rendinfo, "  URL = { ") == NULL) {
	  clean_request(dbires);
	  free(full_link);
	  return 801;
	}
	free(full_link);
      }
      else if (i == 3 && full_link) { /* L3 */
	if (print_field_bibtex(full_link, ptr_rendinfo, "  URL = { ") == NULL) {
	  clean_request(dbires);
	  free(full_link);
	  return 801;
	}
	free(full_link);
      }
      else if (i == 4 && full_link) { /* L4 */
	if (print_field_bibtex(full_link, ptr_rendinfo, "  URL = { ") == NULL) {
	  clean_request(dbires);
	  free(full_link);
	  return 801;
	}
	free(full_link);
      }
      else if (i == 5) { /* DOI */
	if (print_field_bibtex(item, ptr_rendinfo, "  DOI = { ") == NULL) {
	  clean_request(dbires);
	  return 801;
	}
      }
    }

    clean_request(dbires);
  } /* end for */

  /*----------------------------------------------------------------*/
  /* user-defined fields */

  /* loop over all field types */
  for (i=1;i<6;i++) {
    char keystring[16];

    item = get_refdb_user(ptr_rendinfo->dbires, i);
    if (!item || !*item) {
      continue;
    }

    sprintf(keystring, "  USER%d = {", i);
    if (print_field_bibtex(item, ptr_rendinfo, keystring) == NULL) {
      return 801;
    }
  }

  /*----------------------------------------------------------------*/
  /* misc fields */

  item = get_refdb_typeofwork(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  TYPE = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_area(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  AREA = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_ostype(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  OSTYPE = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_runningtime(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  RUNNINGTIME = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_classcodeintl(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  CLASSCODEINTL = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_classcodeus(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  CLASSCODEUS = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_senderemail(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  SENDEREMAIL = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_recipientemail(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  RECIPIENTEMAIL = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_mediatype(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  HOWPUBLISHED = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_numvolumes(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  NUMBEROFVOLUMES = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_computer(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  COMPUTER = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_conferencelocation(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  CONFERENCELOCATION = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_registrynum(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  REGISTRYNUMBER = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_classification(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  CLASSIFICATION = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_section(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  SECTION = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_pamphletnum(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  PAMPHLETNUMBER = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  item = get_refdb_chapternum(ptr_rendinfo->dbires);  
  if (item != NULL) {
    if (print_field_bibtex(item, ptr_rendinfo, "  CHAPTERNUMBER = {") == NULL) {
      clean_request(dbires);
      return 801;
    }
  }

  unload_style();

  /* eliminate the trailing comma and finish with an empty line */
  if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "\n}\n", ptr_rendinfo->ptr_ref_len, 2)) == NULL) {
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 801;
  }
  else {
    *(ptr_rendinfo->ptr_ref) = new_ref;
  }
/*      printf("%s\n", ref); */

 cleanup:
  free(buffer);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  add_partdata_bibtex(): writes part data into a buffer

  static int add_partdata_bibtex returns 0 if ok or an error code > 0

  char** ptr_buffer ptr to a buffer that will receive the output. The
                buffer will be reallocated as needed

  size_t* ptr_buffer_len ptr to the length of buffer. Will be updated
                if buffer is reallocated

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

  dbi_conn conn connection to the database

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int add_partdata_bibtex(char** ptr_buffer, size_t* ptr_buffer_len, struct renderinfo* ptr_rendinfo) {
  int n_have_author = 0;
  char* item;
  char* new_buffer;
  char *bibauthor;
  const char* citem;
  dbi_conn conn;
  dbi_result dbires;

  conn = dbi_result_get_conn(ptr_rendinfo->dbires);

  /*----------------------------------------------------------------*/
  /* authors */
  /* request authors */
  dbires = request_authors(conn, 1, NULL /* all roles */, NULL, 0, my_dbi_result_get_idval(ptr_rendinfo->dbires, "refdb_id"));
  if (dbires == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_get_numrows(dbires) > 0) {
    if ((new_buffer = mstrcat(*ptr_buffer, "  AUTHOR = {", ptr_buffer_len, 0)) == NULL) {
      clean_request(dbires);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }

    /* fetch all authors */
    while ((citem = get_author(dbires)) != NULL) {
      n_have_author = 1;

      if (bibtexify_author(&bibauthor, (char*)citem) == NULL) {
	clean_request(dbires);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }

      if ((new_buffer = mstrcat(*ptr_buffer, bibauthor, ptr_buffer_len, 0)) == NULL) {
	clean_request(dbires);
	free(bibauthor);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
	free(bibauthor);
      }

      if ((new_buffer = mstrcat(*ptr_buffer, " and ", ptr_buffer_len, 0)) == NULL) {
	clean_request(dbires);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
      }
    }
    clean_request(dbires);

    if (n_have_author) { /* eliminate the trailing and after the last author */
      if ((new_buffer = mstrcat(*ptr_buffer, "},\n", ptr_buffer_len, 5)) == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
      }
    }
  } /* end if have authors */

  /*----------------------------------------------------------------*/
  /* title */
  item = get_refdb_title_copy(ptr_rendinfo->dbires);
  if (item != NULL) {
    char* escaped_item;

    if ((new_buffer = mstrcat(*ptr_buffer, "  TITLE = {", ptr_buffer_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }

    if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }

    /* no longer required as we have an escaped copy */
    free(item);

    if ((new_buffer = mstrcat(*ptr_buffer, escaped_item, ptr_buffer_len, 0)) == NULL) { /* title */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      free(escaped_item);
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }
    
    free(escaped_item);
    
    if ((new_buffer = mstrcat(*ptr_buffer, "},\n", ptr_buffer_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  add_setdata_bibtex(): writes set data into a buffer

  static int add_setdata_bibtex returns 0 if ok or an error code > 0

  char** ptr_buffer ptr to a buffer that will receive the output. The
                buffer will be reallocated as needed

  size_t* ptr_buffer_len ptr to the length of buffer. Will be updated
                if buffer is reallocated

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

  dbi_conn conn connection to the database

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int add_setdata_bibtex(char** ptr_buffer, size_t* ptr_buffer_len, struct renderinfo* ptr_rendinfo) {
  int n_have_author = 0;
  char* item;
  char* new_buffer;
  char *bibauthor;
  const char* citem;
  dbi_conn conn;
  dbi_result dbires;

  conn = dbi_result_get_conn(ptr_rendinfo->dbires);

  /*----------------------------------------------------------------*/
  /* authors */
  /* request authors */
  dbires = request_authors(conn, 3 /* set editors */, NULL /* all roles */, NULL, 0, my_dbi_result_get_idval(ptr_rendinfo->dbires, "refdb_id"));
  if (dbires == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(234));
    return 234;
  }

  if (dbi_result_get_numrows(dbires) > 0) {
    if ((new_buffer = mstrcat(*ptr_buffer, "  EDITOR = {", ptr_buffer_len, 0)) == NULL) {
      clean_request(dbires);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }

    /* fetch all authors */
    while ((citem = get_author(dbires)) != NULL) {
      n_have_author++;

      if (bibtexify_author(&bibauthor, (char*)citem) == NULL) {
	clean_request(dbires);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }

      if ((new_buffer = mstrcat(*ptr_buffer, bibauthor, ptr_buffer_len, 0)) == NULL) {
	clean_request(dbires);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
      }

      if ((new_buffer = mstrcat(*ptr_buffer, " and ", ptr_buffer_len, 0)) == NULL) {
	clean_request(dbires);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
      }
    }
    clean_request(dbires);

    if (n_have_author) { /* eliminate the trailing comma after the last author */
      new_buffer = mstrcat(*ptr_buffer, "},\n", ptr_buffer_len, 5);
      
      if (new_buffer == NULL) {
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 801;
      }
      else {
	*ptr_buffer = new_buffer;
      }
    }
  }
  
  /*----------------------------------------------------------------*/
  /* title */
  item = get_refdb_title_series_copy(ptr_rendinfo->dbires);
  if (item != NULL) {
    char* escaped_item;

    if ((new_buffer = mstrcat(*ptr_buffer, "  SERIES = {", ptr_buffer_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }

    if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }

    /* no longer required as we have an escaped copy */
    free(item);

    if ((new_buffer = mstrcat(*ptr_buffer, escaped_item, ptr_buffer_len, 0)) == NULL) { /* title */
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      free(escaped_item);
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }
    
    free(escaped_item);
    
    if ((new_buffer = mstrcat(*ptr_buffer, "},\n", ptr_buffer_len, 0)) == NULL) {
      free(item);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 801;
    }
    else {
      *ptr_buffer = new_buffer;
    }
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_field_bibtex(): writes simple field contents to bibtex output

  char* print_field_bibtex returns a ptr to the modified string. Due to
                         reallocation this may be different from the
			 ptr passed to this function in the ptr_rendinfo
			 structure which is also updated accordingly

  const char* item ptr to the contents of the field to write

  struct renderinfo* ptr_rendinfo ptr to a structure with the info
                             how the reference should be rendered

  const char* start_string ptr to a string to be printed before the 
                         field contents

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char* print_field_bibtex(const char* item, struct renderinfo* ptr_rendinfo, const char* start_string) {
  char* new_ref;

  if (item != NULL) {
    char* escaped_item;

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), (char*)start_string, ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return NULL;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }

    if ((escaped_item = escape_latex_chars_copy(item, strlen(item))) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return NULL;
    }

    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), escaped_item, ptr_rendinfo->ptr_ref_len, 0)) == NULL) { 
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return NULL;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
      free(escaped_item);
    }
    if ((new_ref = mstrcat(*(ptr_rendinfo->ptr_ref), "},\n", ptr_rendinfo->ptr_ref_len, 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return NULL;
    }
    else {
      *(ptr_rendinfo->ptr_ref) = new_ref;
    }
  }
  return *(ptr_rendinfo->ptr_ref);
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  gettexbib(): executes client command gettexbib

  int gettexbib returns 0 on success and 1 on error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct bibinfo* ptr_biblio_info ptr to a structure containing formatting
                  information

  struct ADDRESULT* ptr_addresult ptr to a structure that will receive
                   the result of the command (number of successful
                   and failed references)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int gettexbib(struct CLIENT_REQUEST* ptr_clrequest, struct bibinfo* ptr_biblio_info, struct ADDRESULT* ptr_addresult) {
  int numbyte; /* number of bytes written */
  int not_done = 1;
  int cs_status;
  size_t n_bufsize;
  size_t nsql_command_len = 4096; /* should be enough */
  size_t buffer_len = 4096;
  char *sql_command = NULL;
  char *id_list = NULL;
  char *token = NULL;
  char *token_start = NULL;
  char *eof_string = NULL;
  char *dbstring;
  char *idstring;
  char *the_db;
  char *drivername;
  char *buffer;
  char prev_db[DBNAME_LENGTH] = "";
  dbi_conn conn;
/*   dbi_result dbires; */
  struct lilimem sentinel;
  struct renderinfo rendinfo;

  rendinfo.username = ptr_clrequest->username;
  rendinfo.database = ptr_clrequest->current_db;
  rendinfo.ptr_biblio_info = ptr_biblio_info;
  rendinfo.ref_format = -1;
  rendinfo.nuse_citestyle = 1;
  rendinfo.pdfroot = NULL;
  rendinfo.ptr_clrequest = ptr_clrequest;
  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  ptr_addresult->success = 0;
  ptr_addresult->failure = 0;

  n_bufsize = (size_t)atoi(ptr_clrequest->argument);

  id_list = malloc(n_bufsize);
  if (id_list == NULL || insert_lilimem(&sentinel, (void**)&id_list, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  sql_command = malloc(nsql_command_len);
  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* fill in variable elements of structure */
  buffer = malloc(buffer_len);
  if (buffer == NULL || insert_lilimem(&sentinel, (void**)&(buffer), NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* #2 error */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  buffer[0] = '\0';
  rendinfo.ptr_ref_len = &buffer_len;
  rendinfo.ptr_ref = &buffer;

  /* send acknowledgement to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO); /* #2 ok */

  /* status is always 0 */
  cs_status = read_status(ptr_clrequest->fd); /* #3 */

  /* read whitespace-separated id list from client*/
  numbyte = tread(ptr_clrequest->fd, id_list, n_bufsize);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(109));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  token = id_list; 
  eof_string = &id_list[strlen(id_list)]; /* ptr to end of string */

  /* connect to database server*/
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 204, TERM_NO); /* #4 error */
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* keep a copy of drivername as we may have to switch connections */
  drivername = mstrdup((char*)dbi_driver_get_name(dbi_conn_get_driver(conn)));
  if (drivername == NULL || insert_lilimem(&sentinel, (void**)&drivername, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO); /* #4 error */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* create a "header" if necessary */
  /* prepare_render resets sql_command */

  
  prepare_render_bibtex(&rendinfo);

  while (not_done) { 
    /* search for the start of a token */
    while (*token < 33 && token < eof_string) { /* all whitespace has ASCII values 32 or less */
      token++;
    }

    if (token == eof_string){
      break;
    }

    token_start = token; /* save start of token */

    /* Look for the end */
    while (*token > 32 && token < eof_string) {
      token++;
    }
    
    if (token == eof_string){
      not_done = 0;
    }
    else {
      *token = '\0'; /* terminate token */
      token++;
    }

    /* start test */
    idstring = get_bibtex_id(token_start, &dbstring); /* retrieve database and id parts */
    if (idstring == NULL) {
      continue; /* invalid ID string, try the next one */
    }
/*     dbstring = NULL; */
/*     idstring = token_start; */

    /* end test */
    if (dbstring == NULL) {
      the_db = ptr_clrequest->current_db;
    }
    else {
      the_db = dbstring;
    }

    /* prepare query string */
    sprintf(sql_command, "SELECT DISTINCT t_refdb.refdb_id, t_refdb.refdb_citekey, t_refdb.refdb_type, t_refdb.refdb_pubyear, t_refdb.refdb_startpage, t_refdb.refdb_endpage, t_refdb.refdb_abstract, t_refdb.refdb_title, t_refdb.refdb_volume, t_refdb.refdb_issue, t_refdb.refdb_booktitle, t_refdb.refdb_city, t_refdb.refdb_publisher, t_refdb.refdb_title_series, t_refdb.refdb_address, t_refdb.refdb_issn, t_refdb.refdb_periodical_id, t_refdb.refdb_pyother_info, t_refdb.refdb_secyear, t_refdb.refdb_secother_info, t_refdb.refdb_user1, t_refdb.refdb_user2, t_refdb.refdb_user3, t_refdb.refdb_user4, t_refdb.refdb_user5, t_refdb.refdb_typeofwork, t_refdb.refdb_area, t_refdb.refdb_ostype, t_refdb.refdb_degree, t_refdb.refdb_runningtime, t_refdb.refdb_classcodeintl, t_refdb.refdb_classcodeus, t_refdb.refdb_senderemail, t_refdb.refdb_recipientemail, t_refdb.refdb_mediatype, t_refdb.refdb_numvolumes, t_refdb.refdb_edition, t_refdb.refdb_computer, t_refdb.refdb_conferencelocation, t_refdb.refdb_registrynum, t_refdb.refdb_classification, t_refdb.refdb_section, t_refdb.refdb_pamphletnum, t_refdb.refdb_chapternum FROM t_refdb WHERE t_refdb.refdb_citekey='%s'", idstring);

    if (!strcmp(my_dbi_conn_get_cap(conn, "multiple_db"), "t")) {
      /* change the database if necessary. The string comparison is presumably
	 faster than a forced db switch in each cycle */
      if (strcmp(prev_db, the_db)) {
	if (dbi_conn_select_db(conn, the_db)) {
	  send_status(ptr_clrequest->fd, 204, TERM_NO); /* #4 error */
	  LOG_PRINT(LOG_WARNING, get_status_msg(204));
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  return 1;
	}
      }
      strcpy(prev_db, the_db); /* save current db for next cycle */
    }
    else {
      if (strcmp(prev_db, the_db)) {
	dbi_conn_close(conn);
	if ((conn = connect_to_db(ptr_clrequest, the_db, 0)) == NULL) {
	  send_status(ptr_clrequest->fd, 204, TERM_NO); /* #4 error */
	  LOG_PRINT(LOG_WARNING, get_status_msg(204));
	  delete_all_lilimem(&sentinel);
	  return 1;
	}
      }
      strcpy(prev_db, the_db); /* save current db for next cycle */
    }

    LOG_PRINT(LOG_DEBUG, sql_command);

    rendinfo.dbires = dbi_conn_query(conn, sql_command);
    if (!rendinfo.dbires) {
      send_status(ptr_clrequest->fd, 234, TERM_NO); /* #4 error */
      LOG_PRINT(LOG_ERR, get_status_msg(234));
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      return 1;
    }

    /* fetch all articles we're interested in */
    while (dbi_result_next_row(rendinfo.dbires)) {
      int cs_status;

      /* fill in variable elements of structure */
      /* todo: what's this? why -1?*/
      rendinfo.nref_counter = -1;
      rendinfo.database = the_db;
      rendinfo.dbname = dbstring;

      if ((cs_status = render_bibtex(&rendinfo)) != 0) {
	if (cs_status == 801
	    || cs_status == 269
	    || cs_status == 842) {
	  send_status(ptr_clrequest->fd, cs_status, TERM_NO); /* #4 error */
	  LOG_PRINT(LOG_ERR, get_status_msg(cs_status));
	  dbi_result_free(rendinfo.dbires);
	  dbi_conn_close(conn);
	  delete_all_lilimem(&sentinel);
	  return 1;
	}
	else {
	  ptr_addresult->failure++;
	}
      }

      ptr_addresult->success++;

    /* send ok status, then the terminated result string */
      send_status(ptr_clrequest->fd, 404, TERM_NO); /* #4 ok, more to come */
      iwrite(ptr_clrequest->fd, buffer, strlen(buffer));
      iwrite(ptr_clrequest->fd, cs_term, TERM_LEN);

      /* reset buffer string */
      buffer[0] = '\0';

      /* read client response */
      cs_status = read_status(ptr_clrequest->fd); /* #5 */
      
      if (cs_status) {
	break;
      }
    } /* end while */

  } /* end while have more tokens */

    /* create a "footer" if necessary */
    /* fill in variable elements of structure */
  if ((cs_status = finish_render_bibtex(&rendinfo)) != 0) {
    send_status(ptr_clrequest->fd, cs_status, TERM_NO); /* #4/#6 */
    LOG_PRINT(LOG_ERR, get_status_msg(cs_status));
    dbi_result_free(rendinfo.dbires);
    dbi_conn_close(conn);
    delete_all_lilimem(&sentinel);
    return 1;
  }

  send_status(ptr_clrequest->fd, 402, TERM_NO); /* #4/#6 */
  tiwrite(ptr_clrequest->fd, buffer, TERM_YES);

  dbi_conn_close(conn);

  delete_all_lilimem(&sentinel);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  bibtexify_author(): output authorname in BibTeX-compatible form as
                      F. M. Last lineage

  static char* bibtexify_author returns a ptr to the converted string
                      or NULL if an error occurs

  char** ptr_bibauthor ptr to a ptr to a string buffer. This fn will
                      allocate the memory needed for the converted
		      string and assign it to *ptr_bibauthor. The
		      calling function is responsible to free this
		      memory unless the return value is NULL.

  char* author ptr to the authorname to convert

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char* bibtexify_author(char** ptr_bibauthor, char* author) {
  int nauthor_len;
  char* escaped_item;
  struct AUTHORTOKENS atoken;
  Lilistring* ptr_middle;

  if (author == NULL) {
    return NULL;
  }

  nauthor_len = strlen(author);

  if (tokenize_author(author, &atoken) == NULL) {
    return NULL;
  }

  /* escaping may double the length, plus periods, commas, spaces */
  *ptr_bibauthor = malloc(2*nauthor_len+11);
  if (*ptr_bibauthor == NULL) {
    return NULL;
  }

  (*ptr_bibauthor)[0] = '\0'; /* terminate string */

  if ((escaped_item = escape_latex_chars_copy(atoken.sur, strlen(atoken.sur))) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }

  strcat(*ptr_bibauthor, escaped_item);
  free(escaped_item);
  
  if (*atoken.lineage) {
    strcat(*ptr_bibauthor, ", ");
    if ((escaped_item = escape_latex_chars_copy(atoken.lineage, strlen(atoken.lineage))) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    strcat(*ptr_bibauthor, escaped_item);
    free(escaped_item);
  }

  if (*atoken.first) {
    strcat(*ptr_bibauthor, ", ");
    if ((escaped_item = escape_latex_chars_copy(atoken.first, strlen(atoken.first))) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    strcat(*ptr_bibauthor, escaped_item);
    free(escaped_item);
    if (*(atoken.first+1) == '\0') {
      strcat(*ptr_bibauthor, ".");
    }
  }

  if (atoken.ptr_middlelist) {
    ptr_middle = atoken.ptr_middlelist;
    while ((ptr_middle = get_next_lilistring(ptr_middle)) != NULL) {
      strcat(*ptr_bibauthor, " ");
      if ((escaped_item = escape_latex_chars_copy(ptr_middle->token, strlen(ptr_middle->token))) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      strcat(*ptr_bibauthor, escaped_item);
      free(escaped_item);
      if (*(ptr_middle->token+1) == '\0') {
	strcat(*ptr_bibauthor, ".");
      }
    }
  }

  clean_authortokens(&atoken);
  return *ptr_bibauthor;
}




