/*++++++++++++++++++++
  refdbdcheckref.c: compare references
  markus@mhoenicka.de 2006-09-29

   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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <dbi/dbi.h>

#include "refdb.h"
#include "linklist.h"
#include "refdbd.h"
#include "backend.h"
#include "strfncs.h"
#include "connect.h"
#include "dbfncs.h"
#include "refdbdcheckref.h"
#include "backend-html.h"

/* prototypes */
static int find_duplicate_title(dbi_conn conn);
static int find_duplicate_location(dbi_conn conn);
static int find_duplicate_citekey(dbi_conn conn);
static int find_author_variants(dbi_conn conn);
static int find_keyword_variants(dbi_conn conn);
static int find_periodical_variants(dbi_conn conn);
char* create_periodical_regexp_string(const char* periodical_abbrev);
static int create_dupreport(struct CLIENT_REQUEST* ptr_clrequest, struct renderinfo* ptr_rendinfo, dbi_conn conn, int outformat);
static int create_dupreport_scrn(struct CLIENT_REQUEST* ptr_clrequest, dbi_conn conn);
static int create_dupreport_xhtml(struct CLIENT_REQUEST* ptr_clrequest, struct renderinfo* ptr_rendinfo, dbi_conn conn);
static char* format_title_line(char* destination, size_t* ptr_dest_len, const char* title, const char *temp_title, unsigned long long refdb_id, int title_level, const char* match_type);
static dbi_result retrieve_bibdata(dbi_conn conn, unsigned long long refdb_id);
static int write_tooltips(struct CLIENT_REQUEST* ptr_clrequest);


/* some globals */
extern int n_log_level; /* numeric version of log_level */

/* this chunk of xhtml code is inserted at the top of the xhtml output and contains the tooltips of the checkref command */
char tooltips[] = "<div class=\"tooltip\" id=\"TITLE-IDENT\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with the same title. If the references are identical, you should not add the new reference to avoid duplicates.</td></tr></table>\n"
"</div>"
"<div class=\"tooltip\" id=\"TITLE-CASE\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with a title that differs only in case. It is possible that the references are identical, in which case you don't want to add the new one.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"BOOKTITLE-IDENT\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with the same title. If the references are identical, you should not add the new reference to avoid duplicates.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"BOOKTITLE-CASE\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with a title that differs only in case. It is possible that the references are identical, in which case you don't want to add the new one.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"SERIESTITLE-IDENT\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with the same title. If the references are identical, you should not add the new reference to avoid duplicates.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"SERIESTITLE-CASE\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an entry with a title that differs only in case. It is possible that the references are identical, in which case you don't want to add the new one.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"LOCATION-IDENT\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains an article that was published in the same year, in the same periodical, and on the same starting page of the same volume and issue. The references are likely to be identical, so you should not add this reference to avoid duplicates.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"CITEKEY-IDENT\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains a reference with the same citation key. If the checked reference specifies a citation key, you should either change it, or remove it and thus let RefDB choose one. If the reference does not specify a citation key, RefDB will automatically choose a unique citation key.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"AUTHOR-ABBREV\">\n"
"<table width=\"250px\">\n"
"<tr><td>Check whether the abbreviated authorname and the"
"full authorname belong to the same person. In this case you should expand the abbreviated name to the full name.</td></tr></table>\n"
"</div>""<div class=\"tooltip\" id=\"KEYWORD-LIKE\">\n"
"<table width=\"250px\">\n"
"<tr><td>This keyword is similar to one already used in the database. To make your searches more consistent, you may want to change the keyword.</td></tr></table>\n"
"</div>"
"</div>""<div class=\"tooltip\" id=\"PERIODICAL-LIKE\">\n"
"<table width=\"250px\">\n"
"<tr><td>The database contains a periodical name which may be the expanded version of the abbreviation in the checked reference or vice versa. If this is the case, you may want to specify both the full and the abbreviated name in the checked reference to tell RefDB the periodicals are identical.</td></tr></table>\n"
"</div>";

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  check_references(): check references for duplicates

  int check_references returns 0 if ok, >0 if an error occurred

  struct CLIENT_REQUEST ptr_clrequest ptr to struct containing connection
                    information

  struct renderinfo* ptr_rendinfo ptr to struct containing rendering hints

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  int outformat output format (REFSCRN|REFXHTML)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int check_references(struct CLIENT_REQUEST* ptr_clrequest, struct renderinfo* ptr_rendinfo, dbi_conn conn, int outformat) {
  int retval = 0;

  /* create additional temporary table to hold the relationships between
     references and possible duplicates */
  if (!strcmp(ptr_clrequest->dbserver, "mysql")) {
    retval = create_xdup_tables_mysql(conn);
  }
  else if (!strcmp(ptr_clrequest->dbserver, "pgsql")) {
    retval = create_xdup_tables_pgsql(conn);
  }
  else if (!strcmp(ptr_clrequest->dbserver, "sqlite")) {
    retval = create_xdup_tables_sqlite(conn);
  }
  else if (!strcmp(ptr_clrequest->dbserver, "sqlite3")) {
    retval = create_xdup_tables_sqlite3(conn);
  }
  else {
    /* we're not supposed to ever hit this */
    retval = 1; /* error */
  }

  if (retval) {
    return retval;
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "TX")) {
    /* check titles */
    retval = find_duplicate_title(conn);
  
    if (retval) {
      return retval;
    }
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "PY")) {
    /* check pubdate, volume, issue, pages */
    retval = find_duplicate_location(conn);
    
    if (retval) {
      return retval;
    }
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "CK")) {
    /* check citation keys */
    retval = find_duplicate_citekey(conn);
    
    if (retval) {
      return retval;
    }
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "AX")) {
    /* check authorlists */
    retval = find_author_variants(conn);
    
    if (retval) {
      return retval;
    }
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "KW")) {
    /* check keywords */
    retval = find_keyword_variants(conn);
  
    if (retval) {
      return retval;
    }
  }

  if (!*(ptr_clrequest->check_string)
      || strstr(ptr_clrequest->check_string, "JO")) {
    /* check periodical synonyms */
    retval = find_periodical_variants(conn);
  
    if (retval) {
      return retval;
    }
  }

  /* compile report */
  retval = create_dupreport(ptr_clrequest, ptr_rendinfo, conn, outformat);

  /* the temporary tables will be dropped automatically as soon as the
     calling function terminates the connection */

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_duplicate_title(): check references for duplicate titles

  int find_duplicate_title returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_duplicate_title(dbi_conn conn) {
  char* sql_command;
  const char* my_title;
  const char* my_temp_title;
  const char* my_booktitle;
  const char* my_temp_booktitle;
  const char* my_title_series;
  const char* my_temp_title_series;
  char* my_quoted_title;
  char* my_quoted_temp_title;
  char* my_quoted_booktitle;
  char* my_quoted_temp_booktitle;
  char* my_quoted_title_series;
  char* my_quoted_temp_title_series;
  size_t sql_cmd_len;
  unsigned long long my_refdb_id;
  unsigned long long my_temp_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_result dbires2;

  sql_cmd_len = 1024;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  /* check for identical title */
  snprintf(sql_command, sql_cmd_len, "SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id, t_refdb.refdb_title, t_%srefdb.refdb_title, t_refdb.refdb_booktitle, t_%srefdb.refdb_booktitle, t_refdb.refdb_title_series, t_%srefdb.refdb_title_series FROM t_refdb,t_%srefdb WHERE t_refdb.refdb_title=t_%srefdb.refdb_title OR t_refdb.refdb_booktitle=t_%srefdb.refdb_booktitle OR t_refdb.refdb_title_series=t_%srefdb.refdb_title_series", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX);

  LOG_PRINT(LOG_DEBUG, sql_command);
    
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  while (dbi_result_next_row(dbires)) {
    my_refdb_id = my_dbi_result_get_idval_idx(dbires, 1);
    my_temp_refdb_id = my_dbi_result_get_idval_idx(dbires, 2);
    my_title = my_dbi_result_get_string_idx(dbires, 3);
    my_temp_title = my_dbi_result_get_string_idx(dbires, 4);
    my_booktitle = my_dbi_result_get_string_idx(dbires, 5);
    my_temp_booktitle = my_dbi_result_get_string_idx(dbires, 6);
    my_title_series = my_dbi_result_get_string_idx(dbires, 7);
    my_temp_title_series = my_dbi_result_get_string_idx(dbires, 8);

    if (!my_refdb_id || !my_temp_refdb_id) {
      continue;
    }

    if (my_title && my_temp_title) {
      my_quoted_title = strdup(my_title);
      my_quoted_temp_title = strdup(my_temp_title);

      if (!my_quoted_title
	  || !dbi_conn_quote_string(conn, &my_quoted_title)
	  || !my_quoted_temp_title
	  || !dbi_conn_quote_string(conn, &my_quoted_temp_title)) {
	dbi_result_free(dbires);
	return 801;
      }
    }
    else {
      my_quoted_title = NULL;
      my_quoted_temp_title = NULL;
    }

    if (my_booktitle && my_temp_booktitle) {
      my_quoted_booktitle = strdup(my_booktitle);
      my_quoted_temp_booktitle = strdup(my_temp_booktitle);

      if (!my_quoted_booktitle
	  || !dbi_conn_quote_string(conn, &my_quoted_booktitle)
	  || !my_quoted_temp_booktitle
	  || !dbi_conn_quote_string(conn, &my_quoted_temp_booktitle)) {
	dbi_result_free(dbires);
	return 801;
      }
    }
    else {
      my_quoted_booktitle = NULL;
      my_quoted_temp_booktitle = NULL;
    }

    if (my_title_series && my_temp_title_series) {
      my_quoted_title_series = strdup(my_title_series);
      my_quoted_temp_title_series = strdup(my_temp_title_series);

      if (!my_quoted_title_series
	  || !dbi_conn_quote_string(conn, &my_quoted_title_series)
	  || !my_quoted_temp_title_series
	  || !dbi_conn_quote_string(conn, &my_quoted_temp_title_series)) {
	dbi_result_free(dbires);
	return 801;
      }
    }
    else {
      my_quoted_title_series = NULL;
      my_quoted_temp_title_series = NULL;
    }


    sprintf(sql_command,
	    "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name, value_name_2, temp_value_name_2, value_name_3, temp_value_name_3) VALUES (\'TITLE\',\'IDENT\',"ULLSPEC","ULLSPEC",%s,%s,%s,%s,%s,%s)",
	    (unsigned long long)my_temp_refdb_id,
	    (unsigned long long)my_refdb_id,
	    (my_quoted_title) ? my_quoted_title:"NULL",
	    (my_quoted_temp_title) ? my_quoted_temp_title:"NULL",
	    (my_quoted_booktitle) ? my_quoted_booktitle:"NULL",
	    (my_quoted_temp_booktitle) ? my_quoted_temp_booktitle:"NULL",
	    (my_quoted_title_series) ? my_quoted_title_series:"NULL",
	    (my_quoted_temp_title_series) ? my_quoted_temp_title_series:"NULL");
    LOG_PRINT(LOG_INFO, sql_command);

    dbires1 = dbi_conn_query(conn, sql_command);
    if (!dbires1) {
      /* TODO: bail out? */
      continue;
    }

    dbi_result_free(dbires1);

    free(my_quoted_title);
    free(my_quoted_temp_title);
    free(my_quoted_booktitle);
    free(my_quoted_temp_booktitle);
    free(my_quoted_title_series);
    free(my_quoted_temp_title_series);
  }

  dbi_result_free(dbires);

  /* check for identical title (case-insensitive) */
  /* this may not always work as expected as the conversion from
     lowercase to uppercase depends on the character encoding. Some
     database engines use the C library toupper function which works
     correctly only on ASCII strings */

  if (!strcmp(my_dbi_conn_get_cap(conn, "except"), "t")) {
    /* the EXCEPT clause removes all references from the result set
       which are already marked in the duplicates table as TITLE IDENT */
    snprintf(sql_command, sql_cmd_len, "SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id, t_refdb.refdb_title, t_%srefdb.refdb_title, t_refdb.refdb_booktitle, t_%srefdb.refdb_booktitle, t_refdb.refdb_title_series, t_%srefdb.refdb_title_series FROM t_refdb,t_%srefdb,t_temp_xdup WHERE UPPER(t_refdb.refdb_title)=UPPER(t_%srefdb.refdb_title) OR UPPER(t_refdb.refdb_booktitle)=UPPER(t_%srefdb.refdb_booktitle) OR UPPER(t_refdb.refdb_title_series)=UPPER(t_%srefdb.refdb_title_series) EXCEPT SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id, t_refdb.refdb_title, t_%srefdb.refdb_title, t_refdb.refdb_booktitle, t_%srefdb.refdb_booktitle, t_refdb.refdb_title_series, t_%srefdb.refdb_title_series FROM t_refdb,t_%srefdb,t_temp_xdup WHERE t_temp_xdup.refdb_id=t_refdb.refdb_id AND t_temp_xdup.xdup_type=\'TITLE\' and t_temp_xdup.match_type=\'IDENT\'", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX);
    LOG_PRINT(LOG_DEBUG, sql_command);
    
    dbires = dbi_conn_query(conn, sql_command);
    if (!dbires) {
      free(sql_command);
      return 207;
    }

    /* loop over all matching references and create a TITLE CASE entry
       in the duplicates table */
    while (dbi_result_next_row(dbires)) {
      my_refdb_id = my_dbi_result_get_idval_idx(dbires, 1);
      my_temp_refdb_id = my_dbi_result_get_idval_idx(dbires, 2);
      my_title = my_dbi_result_get_string_idx(dbires, 3);
      my_temp_title = my_dbi_result_get_string_idx(dbires, 4);
      my_booktitle = my_dbi_result_get_string_idx(dbires, 5);
      my_temp_booktitle = my_dbi_result_get_string_idx(dbires, 6);
      my_title_series = my_dbi_result_get_string_idx(dbires, 7);
      my_temp_title_series = my_dbi_result_get_string_idx(dbires, 8);

      if (!my_refdb_id || !my_temp_refdb_id) {
	continue;
      }

      if (my_title) {
	my_quoted_title = strdup(my_title);
	my_quoted_temp_title = strdup(my_temp_title);

	if (!my_quoted_title
	    || !dbi_conn_quote_string(conn, &my_quoted_title)
	    || !my_quoted_temp_title
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_title)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_title = NULL;
	my_quoted_temp_title = NULL;
      }

      if (my_booktitle) {
	my_quoted_booktitle = strdup(my_booktitle);
	my_quoted_temp_booktitle = strdup(my_temp_booktitle);

	if (!my_quoted_booktitle
	    || !dbi_conn_quote_string(conn, &my_quoted_booktitle)
	    || !my_quoted_temp_booktitle
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_booktitle)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_booktitle = NULL;
	my_quoted_temp_booktitle = NULL;
      }

      if (my_title_series) {
	my_quoted_title_series = strdup(my_title_series);
	my_quoted_temp_title_series = strdup(my_temp_title_series);

	if (!my_quoted_title_series
	    || !dbi_conn_quote_string(conn, &my_quoted_title_series)
	    || !my_quoted_temp_title_series
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_title_series)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_title_series = NULL;
	my_quoted_temp_title_series = NULL;
      }

      sprintf(sql_command,
	      "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name, value_name_2, temp_value_name_2, value_name_3, temp_value_name_3) VALUES (\'TITLE\',\'CASE\',"ULLSPEC","ULLSPEC",%s,%s,%s,%s,%s,%s)",
	      (unsigned long long)my_temp_refdb_id,
	      (unsigned long long)my_refdb_id,
	      (my_quoted_title) ? my_quoted_title:"NULL",
	      (my_quoted_temp_title) ? my_quoted_temp_title:"NULL",
	      (my_quoted_booktitle) ? my_quoted_booktitle:"NULL",
	      (my_quoted_temp_booktitle) ? my_quoted_temp_booktitle:"NULL",
	      (my_quoted_title_series) ? my_quoted_title_series:"NULL",
	      (my_quoted_temp_title_series) ? my_quoted_temp_title_series:"NULL");
      LOG_PRINT(LOG_INFO, sql_command);

      dbires2 = dbi_conn_query(conn, sql_command);
      if (!dbires2) {
	/* TODO: bail out? */
	continue;
      }

      dbi_result_free(dbires2);
      free(my_quoted_title);
      free(my_quoted_temp_title);
      free(my_quoted_booktitle);
      free(my_quoted_temp_booktitle);
      free(my_quoted_title_series);
      free(my_quoted_temp_title_series);
    }
  }
  else {
    /* without EXCEPT, we need an additional query */
    snprintf(sql_command, sql_cmd_len, "SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id, t_refdb.refdb_title, t_%srefdb.refdb_title, t_refdb.refdb_booktitle, t_%srefdb.refdb_booktitle, t_refdb.refdb_title_series, t_%srefdb.refdb_title_series FROM t_refdb,t_%srefdb WHERE UPPER(t_refdb.refdb_title)=UPPER(t_%srefdb.refdb_title) OR UPPER(t_refdb.refdb_booktitle)=UPPER(t_%srefdb.refdb_booktitle) OR UPPER(t_refdb.refdb_title_series)=UPPER(t_%srefdb.refdb_title_series)", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX);

    LOG_PRINT(LOG_DEBUG, sql_command);
    
    dbires = dbi_conn_query(conn, sql_command);
    if (!dbires) {
      free(sql_command);
      return 207;
    }

    /* loop over all matching references */
    while (dbi_result_next_row(dbires)) {
      my_refdb_id = my_dbi_result_get_idval_idx(dbires, 1);
      my_temp_refdb_id = my_dbi_result_get_idval_idx(dbires, 2);
      my_title = my_dbi_result_get_string_idx(dbires, 3);
      my_temp_title = my_dbi_result_get_string_idx(dbires, 4);
      my_booktitle = my_dbi_result_get_string_idx(dbires, 5);
      my_temp_booktitle = my_dbi_result_get_string_idx(dbires, 6);
      my_title_series = my_dbi_result_get_string_idx(dbires, 7);
      my_temp_title_series = my_dbi_result_get_string_idx(dbires, 8);

      if (!my_refdb_id || !my_temp_refdb_id) {
	continue;
      }

      if (my_title && my_temp_title) {
	my_quoted_title = strdup(my_title);
	my_quoted_temp_title = strdup(my_temp_title);

	if (!my_quoted_title
	    || !dbi_conn_quote_string(conn, &my_quoted_title)
	    || !my_quoted_temp_title
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_title)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_title = NULL;
	my_quoted_temp_title = NULL;
      }

      if (my_booktitle && my_temp_booktitle) {
	my_quoted_booktitle = strdup(my_booktitle);
	my_quoted_temp_booktitle = strdup(my_temp_booktitle);

	if (!my_quoted_booktitle
	    || !dbi_conn_quote_string(conn, &my_quoted_booktitle)
	    || !my_quoted_temp_booktitle
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_booktitle)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_booktitle = NULL;
	my_quoted_temp_booktitle = NULL;
      }

      if (my_title_series && my_temp_title_series) {
	my_quoted_title_series = strdup(my_title_series);
	my_quoted_temp_title_series = strdup(my_temp_title_series);

	if (!my_quoted_title_series
	    || !dbi_conn_quote_string(conn, &my_quoted_title_series)
	    || !my_quoted_temp_title_series
	    || !dbi_conn_quote_string(conn, &my_quoted_temp_title_series)) {
	  dbi_result_free(dbires);
	  return 801;
	}
      }
      else {
	my_quoted_title_series = NULL;
	my_quoted_temp_title_series = NULL;
      }

      /* check whether the reference is already in the duplicates
	 table as TITLE IDENT */
      sprintf(sql_command, "SELECT DISTINCT xdup_id FROM t_temp_xdup WHERE refdb_id="ULLSPEC" AND xdup_type=\'TITLE\' AND match_type=\'IDENT\'", (unsigned long long)my_refdb_id);

      dbires1 = dbi_conn_query(conn, sql_command);
      if (!dbires1) {
	continue;
      }

      /* add a CASE entry if there is no IDENT entry */
      if (!dbi_result_get_numrows(dbires1)) {
	sprintf(sql_command,
		"INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name, value_name_2, temp_value_name_2, value_name_3, temp_value_name_3) VALUES (\'TITLE\',\'CASE\',"ULLSPEC","ULLSPEC",%s,%s,%s,%s,%s,%s)",
		(unsigned long long)my_temp_refdb_id,
		(unsigned long long)my_refdb_id,
		(my_quoted_title) ? my_quoted_title:"NULL",
		(my_quoted_temp_title) ? my_quoted_temp_title:"NULL",
		(my_quoted_booktitle) ? my_quoted_booktitle:"NULL",
		(my_quoted_temp_booktitle) ? my_quoted_temp_booktitle:"NULL",
		(my_quoted_title_series) ? my_quoted_title_series:"NULL",
		(my_quoted_temp_title_series) ? my_quoted_temp_title_series:"NULL");
	LOG_PRINT(LOG_INFO, sql_command);
	
	dbires2 = dbi_conn_query(conn, sql_command);
	if (!dbires2) {
	  /* TODO: bail out? */
	  continue;
	}

	dbi_result_free(dbires2);
      }
      dbi_result_free(dbires1);
      free(my_quoted_title);
      free(my_quoted_temp_title);
      free(my_quoted_booktitle);
      free(my_quoted_temp_booktitle);
      free(my_quoted_title_series);
      free(my_quoted_temp_title_series);
    }
  }

  dbi_result_free(dbires);


  /* todo: check for title words of at least 3 chars, and compare the sequence of these words */

  free(sql_command);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_duplicate_location(): check references for duplicate location

  int find_duplicate_location returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_duplicate_location(dbi_conn conn) {
  char* sql_command;
  const char* rlike;
  size_t sql_cmd_len;
  unsigned long long my_refdb_id;
  unsigned long long my_temp_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;

  sql_cmd_len = 2048;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  /* check for identical location, i.e. same periodical, same publication year, same volum, same issue and same start page */
  /* PostgreSQL for some reason needs a RLIKE instead of an equality check for the periodical names, could not find out why */
  rlike = my_dbi_conn_get_cap(conn, "rlike");
  snprintf(sql_command, sql_cmd_len,
	   "SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id FROM t_refdb,t_%srefdb,t_periodical,t_%speriodical WHERE"
	   "((t_%srefdb.refdb_type=\'JOUR\' OR t_%srefdb.refdb_type=\'JFULL\' OR t_%srefdb.refdb_type=\'NEWS\' OR t_%srefdb.refdb_type=\'MGZN\') AND t_refdb.refdb_pubyear=t_%srefdb.refdb_pubyear AND t_refdb.refdb_volume=t_%srefdb.refdb_volume AND t_refdb.refdb_issue=t_%srefdb.refdb_issue AND t_refdb.refdb_startpage=t_%srefdb.refdb_startpage AND t_refdb.refdb_periodical_id=t_periodical.periodical_id AND t_%srefdb.refdb_periodical_id=t_%speriodical.periodical_id AND (t_periodical.periodical_name %s t_%speriodical.periodical_name OR t_periodical.periodical_abbrev %s t_%speriodical.periodical_abbrev))"
	   " OR "
	   "((t_%srefdb.refdb_type=\'CONF\' OR t_%srefdb.refdb_type=\'CHAP\') AND t_refdb.refdb_booktitle=t_%srefdb.refdb_booktitle AND t_refdb.refdb_pubyear=t_%srefdb.refdb_pubyear)", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX,  TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, rlike, TEMP_TABLE_NAME_PREFIX, rlike, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX);
  LOG_PRINT(LOG_DEBUG, sql_command);
    
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  /* loop over all matching references */
  while (dbi_result_next_row(dbires)) {
    my_refdb_id = my_dbi_result_get_idval_idx(dbires, 1);
    my_temp_refdb_id = my_dbi_result_get_idval_idx(dbires, 2);

    LOG_PRINT(LOG_DEBUG, sql_command);

    if (!my_refdb_id || !my_temp_refdb_id) {
      continue;
    }

    /* create a LOCATION IDENT entry in the duplicates table */
    sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, refdb_id) VALUES (\'LOCATION\',\'IDENT\',"ULLSPEC","ULLSPEC")", (unsigned long long)my_temp_refdb_id, (unsigned long long)my_refdb_id);
    LOG_PRINT(LOG_INFO, sql_command);
    
    dbires1 = dbi_conn_query(conn, sql_command);
    if (!dbires1) {
      /* TODO: bail out? */
      continue;
    }

    dbi_result_free(dbires1);
  }

  dbi_result_free(dbires);

  free(sql_command);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_duplicate_citekey(): check references for duplicate citation keys

  int find_duplicate_citekey returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_duplicate_citekey(dbi_conn conn) {
  char* sql_command;
  const char* my_citekey;
  size_t sql_cmd_len;
  unsigned long long my_refdb_id;
  unsigned long long my_temp_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;

  sql_cmd_len = 512;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  /* check for identical citation key */
  snprintf(sql_command, sql_cmd_len, "SELECT DISTINCT t_refdb.refdb_id, t_%srefdb.refdb_id, t_refdb.refdb_citekey FROM t_refdb INNER JOIN t_%srefdb ON t_refdb.refdb_citekey=t_%srefdb.refdb_citekey", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX);

  LOG_PRINT(LOG_DEBUG, sql_command);
    
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  while (dbi_result_next_row(dbires)) {
    my_refdb_id = my_dbi_result_get_idval_idx(dbires, 1);
    my_temp_refdb_id = my_dbi_result_get_idval_idx(dbires, 2);
    my_citekey = my_dbi_result_get_string_idx(dbires, 3);

    if (!my_refdb_id || !my_temp_refdb_id || !my_citekey) {
      continue;
    }

    sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name) VALUES (\'CITEKEY\',\'IDENT\',"ULLSPEC","ULLSPEC",'%s','%s')", (unsigned long long)my_temp_refdb_id, (unsigned long long)my_refdb_id, my_citekey, my_citekey);
    LOG_PRINT(LOG_INFO, sql_command);

    dbires1 = dbi_conn_query(conn, sql_command);
    if (!dbires1) {
      /* TODO: bail out? */
      continue;
    }

    dbi_result_free(dbires1);

  }

  dbi_result_free(dbires);

  free(sql_command);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_author_variants(): check author names for spelling variants

  int find_author_variants returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_author_variants(dbi_conn conn) {
  char* sql_command;
  const char* author_name;
  const char* temp_author_name;
  char* quoted_author_name;
  char* quoted_temp_author_name;
  size_t sql_cmd_len;
  unsigned long long n_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_result dbires2;

  sql_cmd_len = 1024;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  /* check for name duplicates. The query is supposed to find
     the following authorname pairs:
     - lastnames are identical
     - one firstname is abbreviated
     - the other is not */
  snprintf(sql_command,
	   sql_cmd_len,
	   "SELECT DISTINCT t_author.author_name, t_%sauthor.author_name "
	   "FROM t_author, t_%sauthor "
	   "WHERE t_author.author_lastname=t_%sauthor.author_lastname "
	   "AND ((%s(t_author.author_firstname)=1 AND %s(t_%sauthor.author_firstname)>1 AND t_author.author_firstname=%s(t_%sauthor.author_firstname%s1%s1)) "
	   "OR (%s(t_%sauthor.author_firstname)=1 AND %s(t_author.author_firstname)>1 AND t_%sauthor.author_firstname=%s(t_author.author_firstname%s1%s1)))",
	   TEMP_TABLE_NAME_PREFIX,
	   TEMP_TABLE_NAME_PREFIX,
	   TEMP_TABLE_NAME_PREFIX,
	   my_dbi_conn_get_cap(conn, "charlength"),
	   my_dbi_conn_get_cap(conn, "charlength"),
	   TEMP_TABLE_NAME_PREFIX,
	   my_dbi_conn_get_cap(conn, "substring"),
	   TEMP_TABLE_NAME_PREFIX,
	   my_dbi_conn_get_cap(conn, "substring_from"),
	   my_dbi_conn_get_cap(conn, "substring_for"),
	   my_dbi_conn_get_cap(conn, "charlength"),
	   TEMP_TABLE_NAME_PREFIX,
	   my_dbi_conn_get_cap(conn, "charlength"),
	   TEMP_TABLE_NAME_PREFIX,
	   my_dbi_conn_get_cap(conn, "substring"),
	   my_dbi_conn_get_cap(conn, "substring_from"),
	   my_dbi_conn_get_cap(conn, "substring_for"));

  LOG_PRINT(LOG_DEBUG, sql_command);
    
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  /* loop over all author names */
  while (dbi_result_next_row(dbires)) {
    author_name = my_dbi_result_get_string_idx(dbires, 1);
    temp_author_name = my_dbi_result_get_string_idx(dbires, 2);

    if (!author_name || !temp_author_name) {
      continue;
    }

    quoted_author_name = strdup(author_name);
    quoted_temp_author_name = strdup(temp_author_name);

    if (!quoted_author_name
	|| !dbi_conn_quote_string(conn, &quoted_author_name)
	|| !quoted_temp_author_name
	|| !dbi_conn_quote_string(conn, &quoted_temp_author_name)) {
      dbi_result_free(dbires);
      return 801;
    }

    /* find out which temp datasets are affected. We need this info to
       list the duplicate authors with the references that contain
       them */

    sprintf(sql_command, "SELECT DISTINCT t_%sxauthor.refdb_id FROM t_%sauthor INNER JOIN t_%sxauthor ON t_%sauthor.author_id=t_%sxauthor.author_id WHERE t_%sauthor.author_name=%s", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, quoted_temp_author_name);
    LOG_PRINT(LOG_DEBUG, sql_command);
    dbires1 = dbi_conn_query(conn, sql_command);
    if (!dbires1) {
      /* TODO: bail out? */
      continue;
    }

    /* add one entry per reference, so the authornames will be listed
       with each reference they appear in */
    while (dbi_result_next_row(dbires1)) {
      n_refdb_id = my_dbi_result_get_idval_idx(dbires1, 1);
      if (!n_refdb_id) {
	continue;
      }
      sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, temp_value_name, value_name) VALUES (\'AUTHOR\',\'ABBREV\',"ULLSPEC",%s, %s)", (unsigned long long)n_refdb_id, quoted_temp_author_name, quoted_author_name);
      LOG_PRINT(LOG_DEBUG, sql_command);

      dbires2 = dbi_conn_query(conn, sql_command);
      if (!dbires2) {
	/* TODO: bail out? */
	continue;
      }

      dbi_result_free(dbires2);
    }

    free(quoted_author_name);
    free(quoted_temp_author_name);

    dbi_result_free(dbires1);
  }

  dbi_result_free(dbires);

  free(sql_command);

  return 0;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_keyword_variants(): check keywords for variants

  int find_keyword_variants returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_keyword_variants(dbi_conn conn) {
  char* sql_command;
  char* start;
  const char* rlike;
  const char* drivername;
  size_t sql_cmd_len;

  /* todo: make this a setting sent by the client */
  size_t req_token_length = 3;

  unsigned long long n_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_result dbires2;


  sql_cmd_len = 1024;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

  rlike = my_dbi_conn_get_cap(conn, "rlike");

  /* use the permanent keywords to create regexp and find matches in
     the temporary keywords */
  sprintf(sql_command, "SELECT DISTINCT keyword_name FROM t_keyword");
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  /* loop over all keyword names */
  while (dbi_result_next_row(dbires)) {
    char* keyword_name;
    char* quoted_keyword_name;
    const char* temp_keyword_name;
    char* quoted_temp_keyword_name;

    keyword_name = my_dbi_result_get_string_copy_idx(dbires, 1);
    
    quoted_keyword_name = strdup(keyword_name);

    if (!quoted_keyword_name
	|| !dbi_conn_quote_string(conn, &quoted_keyword_name)) {
      dbi_result_free(dbires);
      return 801;
    }

    /* replace regexp special characters */
    replace_regexp_chars(keyword_name, "()*?[]\\", ' ');

    /* loop over all space-separated tokens in the keyword */
    for (start = strtok(keyword_name, " "); start; start = strtok(NULL, " ")) {
      char* quoted_keyword_token;

      start = stripwhite(start, 0, 0);
      if (strlen(start) > req_token_length) {
	/* todo: use special regexp quote function */
	quoted_keyword_token = strdup(start);

	if (!quoted_keyword_token
	    || !dbi_conn_quote_string(conn, &quoted_keyword_token)) {
	  dbi_result_free(dbires);
	  return 801;
	}

	sprintf(sql_command, "SELECT DISTINCT t_%skeyword.keyword_name, t_%sxkeyword.xref_id FROM t_%skeyword INNER JOIN t_%sxkeyword ON t_%skeyword.keyword_id=t_%sxkeyword.keyword_id WHERE upper(t_%skeyword.keyword_name) %s upper(%s)", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, rlike, quoted_keyword_token);
	free(quoted_keyword_token);

	LOG_PRINT(LOG_DEBUG, sql_command);

	dbires1 = dbi_conn_query(conn, sql_command);

	if (!dbires1) {
	  continue;
	}

	/* loop over all matching keywords */
	while (dbi_result_next_row(dbires1)) {
	  n_refdb_id = my_dbi_result_get_idval_idx(dbires1, 2);
	  temp_keyword_name = my_dbi_result_get_string_idx(dbires1, 1);

	  if (!n_refdb_id) {
	    continue;
	  }

	  quoted_temp_keyword_name = strdup(temp_keyword_name);

	  if (!quoted_temp_keyword_name
	      || !dbi_conn_quote_string(conn, &quoted_temp_keyword_name)) {
	    dbi_result_free(dbires);
	    return 801;
	  }

	  /* use only if the keywords are not identical */
	  if (strcmp(quoted_temp_keyword_name, quoted_keyword_name)) {
	    sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, temp_value_name, value_name) VALUES (\'KEYWORD\',\'LIKE\',"ULLSPEC",%s, %s)", (unsigned long long)n_refdb_id, quoted_temp_keyword_name, quoted_keyword_name);

	    LOG_PRINT(LOG_DEBUG, sql_command);

	    dbires2 = dbi_conn_query(conn, sql_command);
	    if (!dbires2) {
	      /* TODO: bail out? */
	      continue;
	    }
	    dbi_result_free(dbires2);
	  } /* end if */
	  free(quoted_temp_keyword_name);
	} /* end while */

	dbi_result_free(dbires1);
      } /* end if */
    } /* end for */
    free(quoted_keyword_name);
    free(keyword_name);

  } /* end while */

  dbi_result_free(dbires);

  /* use the temporary keywords to create regexp and find matches in
     the permanent keywords */
  sprintf(sql_command, "SELECT DISTINCT keyword_name FROM t_%skeyword", TEMP_TABLE_NAME_PREFIX);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  /* loop over all keyword names */
  while (dbi_result_next_row(dbires)) {
    char* temp_keyword_name;
    char* quoted_temp_keyword_name;
    char* quoted_keyword_name;
    const char* keyword_name;

    temp_keyword_name = my_dbi_result_get_string_copy_idx(dbires, 1);
    
    quoted_temp_keyword_name = strdup(temp_keyword_name);

    if (!quoted_temp_keyword_name
	|| !dbi_conn_quote_string(conn, &quoted_temp_keyword_name)) {
      dbi_result_free(dbires);
      return 801;
    }

    /* replace regexp special characters */
    replace_regexp_chars(temp_keyword_name, "()*?[]\\", ' ');

    /* loop over all space-separated tokens in the keyword */
    for (start = strtok(temp_keyword_name, " "); start; start = strtok(NULL, " ")) {
      char* quoted_temp_keyword_token = NULL;

      start = stripwhite(start, 0, 0);

      if (strlen(start) > req_token_length) {
	quoted_temp_keyword_token = strdup(start);

	if (!quoted_temp_keyword_token
	    || !dbi_conn_quote_string(conn, &quoted_temp_keyword_token)) {
	  dbi_result_free(dbires);
	  return 801;
	}

	sprintf(sql_command, "SELECT DISTINCT t_keyword.keyword_name, t_%sxkeyword.xref_id FROM t_keyword, t_%skeyword INNER JOIN t_%sxkeyword ON t_%skeyword.keyword_id=t_%sxkeyword.keyword_id WHERE upper(t_keyword.keyword_name) %s upper(%s) AND t_%skeyword.keyword_name=%s", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, rlike, quoted_temp_keyword_token, TEMP_TABLE_NAME_PREFIX, quoted_temp_keyword_name);

	LOG_PRINT(LOG_DEBUG, sql_command);

	dbires1 = dbi_conn_query(conn, sql_command);

	if (!dbires1) {
	  continue;
	}

	/* loop over all matching keywords */
	while (dbi_result_next_row(dbires1)) {
	  n_refdb_id = my_dbi_result_get_idval_idx(dbires1, 2);
	  keyword_name = my_dbi_result_get_string_idx(dbires1, 1);

	  if (!n_refdb_id) {
	    continue;
	  }

	  quoted_keyword_name = strdup(keyword_name);

	  if (!quoted_keyword_name
	      || !dbi_conn_quote_string(conn, &quoted_keyword_name)) {
	    dbi_result_free(dbires);
	    return 801;
	  }

	  if (strcmp(quoted_temp_keyword_name, quoted_keyword_name)) {
	    sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, temp_value_name, value_name) VALUES (\'KEYWORD\',\'LIKE\',"ULLSPEC",%s, %s)", (unsigned long long)n_refdb_id, quoted_temp_keyword_name, quoted_keyword_name);

	    free(quoted_keyword_name);

	    LOG_PRINT(LOG_DEBUG, sql_command);
	    
	    dbires2 = dbi_conn_query(conn, sql_command);
	    if (!dbires2) {
	      /* TODO: bail out? */
	      continue;
	    }
	    dbi_result_free(dbires2);
	  }
	  else {
	    free(quoted_keyword_name);
	  } /* end if */
	} /* end while */

	dbi_result_free(dbires1);
      } /* end if */
      if (quoted_temp_keyword_token) {
	free(quoted_temp_keyword_token);
      }
    } /* end for */
    free(temp_keyword_name);
    free(quoted_temp_keyword_name);
  } /* end while */

  dbi_result_free(dbires);
  free(sql_command);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  find_periodical_variants(): check periodical names for variants

  int find_periodical_variants returns 0 if ok, >0 if an error occurred

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int find_periodical_variants(dbi_conn conn) {
  char* sql_command;
  char* new_sql_command;
  char* periodical_regexp;
  const char* rlike;
  const char* periodical_abbrev;
  const char* match_periodical_name;
  const char* match_periodical_abbrev;
  char* quoted_periodical_abbrev = NULL;
  char* quoted_periodical_regexp = NULL;
  char* quoted_match_periodical_value = NULL;
  size_t sql_cmd_len;
  unsigned long long n_refdb_id;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_result dbires2;
  dbi_result dbires3;


  sql_cmd_len = 1024;

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    return 801;
  }

  rlike = my_dbi_conn_get_cap(conn, "rlike");

  /* find all periodicals in the temporary tables */
  sprintf(sql_command, "SELECT DISTINCT periodical_name, periodical_abbrev FROM t_%speriodical", TEMP_TABLE_NAME_PREFIX);
  LOG_PRINT(LOG_DEBUG, sql_command);
    
  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    return 207;
  }

  /* loop over all periodical names */
  while (dbi_result_next_row(dbires)) {
    periodical_abbrev = my_dbi_result_get_string_idx(dbires, 2);

    if (!periodical_abbrev || !*periodical_abbrev) {
      continue;
    }


    quoted_periodical_abbrev = strdup(periodical_abbrev);

    if (!quoted_periodical_abbrev
	|| !dbi_conn_quote_string(conn, &quoted_periodical_abbrev)) {
      dbi_result_free(dbires);
      return 801;
    }

    /* construct a regular expression from the tokens of the abbreviated name. Use only the abbreviated (i.e. followed by a dot) tokens as stems of regular expressions */
    periodical_regexp = create_periodical_regexp_string(periodical_abbrev);

/*     if (!*periodical_regexp) { */
/*       sprintf(sql_command, "periodical_abbrev went to:%s<<\n", periodical_abbrev); */
/*       LOG_PRINT(LOG_DEBUG, sql_command); */
/*     } */
    quoted_periodical_regexp = strdup(periodical_regexp);

    if (!quoted_periodical_regexp
	|| !dbi_conn_quote_string(conn, &quoted_periodical_regexp)) {
      dbi_result_free(dbires);
      return 801;
    }

    if (sql_cmd_len < (2*strlen(quoted_periodical_regexp)) + 160) {
      new_sql_command = realloc(sql_command, (2*strlen(quoted_periodical_regexp)) + 160);
      if (!new_sql_command) {
	dbi_result_free(dbires);
	return 801;
      }
      else {
	sql_command = new_sql_command;
      }
    }
      
    sprintf(sql_command, "SELECT DISTINCT periodical_name, periodical_abbrev from t_periodical WHERE periodical_name %s %s OR periodical_abbrev %s %s", rlike, quoted_periodical_regexp, rlike, quoted_periodical_regexp);

    LOG_PRINT(LOG_DEBUG, sql_command);
    dbires1 = dbi_conn_query(conn, sql_command);
    if (!dbires1) {
      /* TODO: bail out? */
      continue;
    }

    /* loop over all matching periodicals in the permanent table */
    while (dbi_result_next_row(dbires1)) {
      match_periodical_name = my_dbi_result_get_string_idx(dbires1, 1);
      match_periodical_abbrev = my_dbi_result_get_string_idx(dbires1, 2);

      quoted_match_periodical_value = strdup((match_periodical_name && *match_periodical_name) ? match_periodical_name:match_periodical_abbrev);

      if (!quoted_match_periodical_value
	  || !dbi_conn_quote_string(conn, &quoted_match_periodical_value)) {
	dbi_result_free(dbires);
	return 801;
      }
      /* find out which temp datasets are affected. We need this info to
	 list the duplicate periodicals with the references that contain
	 them */

      sprintf(sql_command, "SELECT DISTINCT t_%srefdb.refdb_id FROM t_%srefdb INNER JOIN t_%speriodical ON t_%srefdb.refdb_periodical_id=t_%speriodical.periodical_id WHERE t_%speriodical.periodical_abbrev=%s", TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, TEMP_TABLE_NAME_PREFIX, quoted_periodical_abbrev);
      LOG_PRINT(LOG_DEBUG, sql_command);
      dbires2 = dbi_conn_query(conn, sql_command);
      if (!dbires2) {
	/* TODO: bail out? */
	continue;
      }

      /* add one entry per reference, so the periodicals will be listed
	 with each reference they appear in */
      while (dbi_result_next_row(dbires2)) {
	n_refdb_id = my_dbi_result_get_idval_idx(dbires2, 1);
	if (!n_refdb_id) {
	  continue;
	}
	sprintf(sql_command, "INSERT INTO t_temp_xdup (xdup_type, match_type, temp_refdb_id, temp_value_name, value_name) VALUES (\'PERIODICAL\',\'LIKE\',"ULLSPEC",%s, %s)", (unsigned long long)n_refdb_id, quoted_periodical_abbrev, quoted_match_periodical_value);
	LOG_PRINT(LOG_DEBUG, sql_command);
	
	dbires3 = dbi_conn_query(conn, sql_command);
	if (!dbires3) {
	  /* TODO: bail out? */
	  continue;
	}

	dbi_result_free(dbires3);
      }
      free(quoted_match_periodical_value);
      dbi_result_free(dbires2);
    }
    free(periodical_regexp);
    free(quoted_periodical_regexp);
    free(quoted_periodical_abbrev);
    dbi_result_free(dbires1);
  }

  dbi_result_free(dbires);
  free(sql_command);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  create_periodical_regexp_string(): turns an abbreviated periodical
                    name into a regexp to find possible matching full
		    names

  char* create_periodical_regexp_string returns an allocated string containing
                    the regexp, or NULL if there was an error

  const char* periodical_abbrev the abbreviation of the periodical name

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* create_periodical_regexp_string(const char* periodical_abbrev) {
  char regexp_code[] = "[^\\.]+";
  char* regexp;
  char* start;
  char* period;
  size_t regexp_len;
  size_t n_periods;

  n_periods = count_the_flowers(periodical_abbrev, ".", 1);
  if (!n_periods) {
    /* no periods */
    regexp = strdup(periodical_abbrev);
    /* no need to check result. if strdup failed, NULL is the correct
       return value of the function */
    return regexp;
  }

  /* allocate a string that holds periodical_abbrev plus n_periods
     times the length of the regexp code plus the trailing zero */
  regexp_len = strlen(periodical_abbrev) + n_periods*strlen(regexp_code) + 1;

  if ((regexp = malloc(regexp_len)) == NULL) {
    return NULL;
  }

  strcpy(regexp, periodical_abbrev);
  start = regexp;

  /* loop over all periods in the string */
  while ((period = strchr(start, (int)'.')) != NULL) {
    replace_char_string(period, regexp_code, 1);
    start = period+strlen(regexp_code);
  }
   
  return regexp;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  create_dupreport(): create the report about duplicates

  int create_dupreport returns 0 if ok, >0 if an error occurred

  struct CLIENT_REQUEST ptr_clrequest ptr to struct containing connection
                    information

  struct renderinfo* ptr_rendinfo ptr to struct containing rendering hints

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  int outformat output format (REFSCRN|REFXHTML)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int create_dupreport(struct CLIENT_REQUEST* ptr_clrequest, struct renderinfo* ptr_rendinfo, dbi_conn conn, int outformat) {
  int retval;

  if (outformat == REFXHTML) {
    retval = create_dupreport_xhtml(ptr_clrequest, ptr_rendinfo, conn);
  }
  else {
    retval = create_dupreport_scrn(ptr_clrequest, conn);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  create_dupreport_scrn(): create the screen report about duplicates

  int create_dupreport_scrn returns 0 if ok, >0 if an error occurred

  struct CLIENT_REQUEST ptr_clrequest ptr to struct containing connection
                    information

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int create_dupreport_scrn(struct CLIENT_REQUEST* ptr_clrequest, dbi_conn conn) {
  int retval = 0;
  char sql_command[512];
  char* msg_buf;
  const char* xdup_type;
  const char* match_type;
  const char* value_name;
  const char* temp_value_name;
  const char* value_name_2;
  const char* temp_value_name_2;
  const char* value_name_3;
  const char* temp_value_name_3;
  size_t msg_buf_len = 1024;
  int numbyte;
  unsigned long long temp_refdb_id;
  unsigned long long refdb_id;
  dbi_result dbires;

  if ((msg_buf = malloc(msg_buf_len)) == NULL) {
    return 801;
  }

  *msg_buf = '\0';

  /* retrieve duplicates sorted by input dataset */
  snprintf(sql_command, 512, "SELECT DISTINCT xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name, value_name_2, temp_value_name_2, value_name_3, temp_value_name_3 FROM t_temp_xdup ORDER BY temp_refdb_id, xdup_type, match_type, temp_value_name, value_name");

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
      /* TODO: bail out? */
  }

  while (dbi_result_next_row(dbires)) {
    temp_refdb_id = my_dbi_result_get_idval(dbires, "temp_refdb_id");
    refdb_id = my_dbi_result_get_idval(dbires, "refdb_id");
    xdup_type = my_dbi_result_get_string(dbires, "xdup_type");
    match_type = my_dbi_result_get_string(dbires, "match_type");
    temp_value_name = my_dbi_result_get_string(dbires, "temp_value_name");
    value_name = my_dbi_result_get_string(dbires, "value_name");
    temp_value_name_2 = my_dbi_result_get_string(dbires, "temp_value_name_2");
    value_name_2 = my_dbi_result_get_string(dbires, "value_name_2");
    temp_value_name_3 = my_dbi_result_get_string(dbires, "temp_value_name_3");
    value_name_3 = my_dbi_result_get_string(dbires, "value_name_3");

    /* these values must be non-zero for all match types */
    if (!temp_refdb_id || !xdup_type) {
      /* todo: bail out? */
      continue;
    }

    if (!strcmp(xdup_type, "LOCATION")) {
      sprintf(msg_buf, "%s %s: temp_id:"ULLSPEC" id:"ULLSPEC"\n", xdup_type, match_type, (unsigned long long)temp_refdb_id, (unsigned long long)refdb_id);
    }
    else if (!strcmp(xdup_type, "AUTHOR")
	     || !strcmp(xdup_type, "KEYWORD")
	     || !strcmp(xdup_type, "PERIODICAL")
	     || !strcmp(xdup_type, "CITEKEY")) {
      sprintf(msg_buf, "%s %s: temp_id:"ULLSPEC" temp_value:%s value:%s\n", xdup_type, match_type, (unsigned long long)temp_refdb_id, temp_value_name, value_name);
    }
    else if (!strcmp(xdup_type, "TITLE")) {
      sprintf(msg_buf, "%s %s: temp_id:"ULLSPEC" temp_title:%s id:"ULLSPEC" title:%s temp_booktitle: %s booktitle: %s temp_title_series: %s title_series: %s\n", xdup_type, match_type, (unsigned long long)temp_refdb_id, temp_value_name, (unsigned long long)refdb_id, value_name, temp_value_name_2, value_name_2, temp_value_name_3, value_name_3);
    }

/*     printf("trying to write %d byte\n", strlen(msg_buf)); */
    numbyte = tiwrite(ptr_clrequest->fd, msg_buf, TERM_NO);
    if (numbyte == -1) {
      LOG_PRINT(LOG_INFO, get_status_msg(110));
      retval = 110;
      break;
    }
  }

  dbi_result_free(dbires);
  free(msg_buf);

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  create_dupreport_xhtml(): create the xhtml report about duplicates

  int create_dupreport_xhtml returns 0 if ok, >0 if an error occurred

  struct CLIENT_REQUEST ptr_clrequest ptr to struct containing connection
                    information

  struct renderinfo* ptr_rendinfo ptr to struct containing rendering hints

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int create_dupreport_xhtml(struct CLIENT_REQUEST* ptr_clrequest, struct renderinfo* ptr_rendinfo, dbi_conn conn) {
  int retval = 0;
  char sql_command[512];
  char* msg_buf;
  char* new_msg_buf;
  const char* xdup_type;
  const char* match_type;
  const char* value_name;
  const char* temp_value_name;
  const char* value_name_2;
  const char* temp_value_name_2;
  const char* value_name_3;
  const char* temp_value_name_3;
  size_t msg_buf_len = 1024;
  int numbyte;
  unsigned long long temp_refdb_id;
  unsigned long long prev_temp_id = 0; /* used in loop to detect id switch */
  unsigned long long refdb_id;
  dbi_result dbires;

  if (write_tooltips(ptr_clrequest)) {
    return 1;
  }

  if ((msg_buf = malloc(msg_buf_len)) == NULL) {
    return 801;
  }

  *msg_buf = '\0';


  /* retrieve duplicates sorted by input dataset */
  snprintf(sql_command, 512, "SELECT DISTINCT xdup_type, match_type, temp_refdb_id, refdb_id, value_name, temp_value_name, value_name_2, temp_value_name_2, value_name_3, temp_value_name_3 FROM t_temp_xdup ORDER BY temp_refdb_id, xdup_type, match_type, temp_value_name, value_name");

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
      /* TODO: bail out? */
  }

  /* loop over all temporary datasets */
  while (dbi_result_next_row(dbires)) {
    temp_refdb_id = my_dbi_result_get_idval(dbires, "temp_refdb_id");
    refdb_id = my_dbi_result_get_idval(dbires, "refdb_id");
    xdup_type = my_dbi_result_get_string(dbires, "xdup_type");
    match_type = my_dbi_result_get_string(dbires, "match_type");
    temp_value_name = my_dbi_result_get_string(dbires, "temp_value_name");
    value_name = my_dbi_result_get_string(dbires, "value_name");
    temp_value_name_2 = my_dbi_result_get_string(dbires, "temp_value_name_2");
    value_name_2 = my_dbi_result_get_string(dbires, "value_name_2");
    temp_value_name_3 = my_dbi_result_get_string(dbires, "temp_value_name_3");
    value_name_3 = my_dbi_result_get_string(dbires, "value_name_3");

    *msg_buf = '\0'; /* truncate string */

    /* these values must be non-zero for all match types */
    if (!temp_refdb_id || !xdup_type) {
      /* todo: bail out? */
      continue;
    }

    if (prev_temp_id != 0 && prev_temp_id != temp_refdb_id) {
      /* close previous table */
      sprintf(msg_buf, "</table>\n");

      numbyte = tiwrite(ptr_clrequest->fd, msg_buf, TERM_NO);
      if (numbyte == -1) {
	LOG_PRINT(LOG_INFO, get_status_msg(110));
	retval = 110;
	break;
      }
    }

    if (prev_temp_id != temp_refdb_id) {
      /* retrieve bibliographic data of the temporary dataset */
/*       printf("start retrieving bib data\n"); */
      ptr_rendinfo->dbires = retrieve_bibdata(conn, temp_refdb_id);
      **(ptr_rendinfo->ptr_ref) = '\0'; /* truncate string */

      /* render dataset */
      
/*       printf("start rendering bib data\n"); */
      if (render_html(ptr_rendinfo, 2 /* temporary tables, but frequency data from the permanent tables */)) {
	/* todo: bail out? */
/* 	printf("rendering html failed\n"); */
	continue;
      }
      else {
	numbyte = tiwrite(ptr_clrequest->fd, *(ptr_rendinfo->ptr_ref), TERM_NO);
	if (numbyte == -1) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  retval = 110;
	  break;
	}
      }

      sprintf(msg_buf, "<table class='duplist' summary='listing of possible duplicates'>\n<tr><th>field</th><th>match type</th><th>check value</th><th>database value</th></tr>\n");
      numbyte = tiwrite(ptr_clrequest->fd, msg_buf, TERM_NO);
      if (numbyte == -1) {
	LOG_PRINT(LOG_INFO, get_status_msg(110));
	retval = 110;
	break;
      }

      *msg_buf = '\0'; /* truncate string */
    }
    prev_temp_id = temp_refdb_id; /* set for next cycle */

    if (!strcmp(xdup_type, "LOCATION")) {
      sprintf(msg_buf, "<tr class='%s'><td class='match-field'>%s</td><td class='match-type' onmouseover=\"showTOOLTIP('LOCATION-%s')\" onmouseout=\"hideTOOLTIP()\">%s</td><td class='check-value'></td><td class='database-value'>ID "ULLSPEC"</td></tr>\n", match_type, xdup_type, match_type, match_type, (unsigned long long)refdb_id);
    }
    else if (!strcmp(xdup_type, "AUTHOR")
	     || !strcmp(xdup_type, "KEYWORD")
	     || !strcmp(xdup_type, "PERIODICAL")
	     || !strcmp(xdup_type, "CITEKEY")) {
      sprintf(msg_buf, "<tr class='%s'><td class='match-field'>%s</td><td class='match-type' onmouseover=\"showTOOLTIP('%s-%s')\" onmouseout=\"hideTOOLTIP()\">%s</td><td class='check-value'>%s</td><td class='database-value'>%s</td></tr>\n", match_type, xdup_type, xdup_type, match_type, match_type, temp_value_name, value_name);
    }
    else if (!strcmp(xdup_type, "TITLE")) {
      if ((temp_value_name && *temp_value_name)
	  || (value_name && *value_name)) {
	if ((new_msg_buf = format_title_line(msg_buf, &msg_buf_len, value_name, temp_value_name, refdb_id, 1, match_type)) == NULL) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  retval = 801;
	  break;
	}
	else {
	  msg_buf = new_msg_buf;
	}
      }

      if ((temp_value_name_2 && *temp_value_name_2)
	  || (value_name_2 && *value_name_2)) {
	if ((new_msg_buf = format_title_line(msg_buf, &msg_buf_len, value_name_2, temp_value_name_2, refdb_id, 2, match_type)) == NULL) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  retval = 801;
	  break;
	}
	else {
	  msg_buf = new_msg_buf;
	}
      }

      if ((temp_value_name_3 && *temp_value_name_3)
	  || (value_name_3 && *value_name_3)) {
	if ((new_msg_buf = format_title_line(msg_buf, &msg_buf_len, value_name_3, temp_value_name_3, refdb_id, 3, match_type)) == NULL) {
	  LOG_PRINT(LOG_INFO, get_status_msg(110));
	  retval = 801;
	  break;
	}
	else {
	  msg_buf = new_msg_buf;
	}
      }
    }
    else {
      /* should never happen */
/*       printf("unknown duplicate type\n"); */
    }

    numbyte = tiwrite(ptr_clrequest->fd, msg_buf, TERM_NO);
    if (numbyte == -1) {
      LOG_PRINT(LOG_INFO, get_status_msg(110));
      retval = 110;
      break;
    }
  }

  sprintf(msg_buf, "</table>\n<p></p><p></p><hr/><p></p>");
  
  numbyte = tiwrite(ptr_clrequest->fd, msg_buf, TERM_NO);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(110));
    retval = 110;
  }

  dbi_result_free(dbires);
  free(msg_buf);

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  format_title_line(): writes out one line in the duplicate table listing
                       one of the title matches

  char* format_title_line returns the buffer containing the result,
                       or NULL if there was an error.

  char* destination   an allocated buffer that will receive the result.
                       The buffer may be reallocated during assembly, so use
		       the return value of this function to access the results

  size_t* ptr_dest_len ptr to an integer holding the length of the allocated
                       buffer

  const char* title    ptr to the title in the database
 
  const char* temp_title  ptr to the title in the temporary table

  unsigned long long refdb_id the ID of the dataset to retrieve from the
                    temporary table

  int title_level 1=title 2=booktitle 3=series_title

  const char* match_type string that specifies how the titles are matching

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char* format_title_line(char* destination, size_t* ptr_dest_len, const char* title, const char *temp_title, unsigned long long refdb_id, int title_level, const char* match_type) {
  char buffer[256];
  char* new_dest;

  if (title_level == 1) {
    sprintf(buffer, "<tr class='%s'><td class='match-field'>title</td><td class='match-type' onmouseover=\"showTOOLTIP('TITLE-%s')\" onmouseout=\"hideTOOLTIP()\">%s</td><td class='check-value'>", match_type, match_type, match_type);
  }
  else if (title_level == 2) {
    sprintf(buffer, "<tr class='%s'><td class='match-field'>booktitle</td><td class='match-type' onmouseover=\"showTOOLTIP('BOOKTITLE-%s')\" onmouseout=\"hideTOOLTIP()\">%s</td><td class='check-value'>", match_type, match_type, match_type);
  }
  else {
    sprintf(buffer, "<tr class='%s'><td class='match-field'>series title</td><td class='match-type' onmouseover=\"showTOOLTIP('SERIESTITLE-%s')\" onmouseout=\"hideTOOLTIP()\">%s</td><td class='check-value'>", match_type, match_type, match_type);
  }

  if ((new_dest = mstrcat(destination, buffer, ptr_dest_len, 0)) == NULL) {
    return NULL;
  }
  else {
    destination = new_dest;
  }

  if ((new_dest = mstrcat(destination, (char*)temp_title, ptr_dest_len, 0)) == NULL) {
    return NULL;
  }
  else {
    destination = new_dest;
  }

  sprintf(buffer, "</td><td class='database-value'>ID "ULLSPEC": ", (unsigned long long)refdb_id);

  if ((new_dest = mstrcat(destination, buffer, ptr_dest_len, 0)) == NULL) {
    return NULL;
  }
  else {
    destination = new_dest;
  }

  if ((new_dest = mstrcat(destination, (char*)title, ptr_dest_len, 0)) == NULL) {
    return NULL;
  }
  else {
    destination = new_dest;
  }

  if ((new_dest = mstrcat(destination, "</td></tr>\n", ptr_dest_len, 0)) == NULL) {
    return NULL;
  }
  else {
    destination = new_dest;
  }

  return destination;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  retrieve_bibdata(): retrieves the bibliographic data used for the duplicate
                      listing

  dbi_result retrieve_bibdata returns a dbires structure containing the 
                      query results or NULL if there was an error

  dbi_conn conn pointer to a data structure for a previously
                    established database session. The caller is
                    responsible to establish this connection and
                    to take it down again after this function
                    returns.

  unsigned long long refdb_id the ID of the dataset to retrieve from the
                    temporary table

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static dbi_result retrieve_bibdata(dbi_conn conn, unsigned long long refdb_id) {
  dbi_result dbires;
  char* sql_command;

  if ((sql_command = malloc(1024)) == NULL) {
    return NULL;
  }

  sprintf(sql_command, "SELECT DISTINCT refdb_id, refdb_type, refdb_pubyear, refdb_startpage, refdb_endpage, refdb_abstract, refdb_title, refdb_volume, refdb_issue, refdb_booktitle, refdb_city, refdb_publisher, refdb_title_series, refdb_address, refdb_issn, refdb_periodical_id, refdb_pyother_info, refdb_secyear, refdb_secother_info, refdb_user1, refdb_user2, refdb_user3, refdb_user4, refdb_user5, refdb_typeofwork, refdb_area, refdb_ostype, refdb_degree, refdb_runningtime, refdb_classcodeintl, refdb_classcodeus, refdb_senderemail, refdb_recipientemail, refdb_mediatype, refdb_numvolumes, refdb_edition, refdb_computer, refdb_conferencelocation, refdb_registrynum, refdb_classification, refdb_section, refdb_pamphletnum, refdb_chapternum, refdb_citekey FROM t_%srefdb WHERE refdb_id="ULLSPEC, TEMP_TABLE_NAME_PREFIX, (unsigned long long)refdb_id);

  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);
  free(sql_command);

  if (!dbires) {
    return NULL;
  }
  else if (!dbi_result_next_row(dbires)) {
    dbi_result_free(dbires);
    return NULL;
  }
  
  return dbires;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  write_tooltips(): creates the xhtml tooltip output

  int write_tooltip returns 0 if ok, >0 if an error occurred

  struct CLIENT_REQUEST ptr_clrequest ptr to struct containing connection
                    information

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int write_tooltips(struct CLIENT_REQUEST* ptr_clrequest) {
  int numbyte;

  numbyte = tiwrite(ptr_clrequest->fd, tooltips, TERM_NO);
  if (numbyte == -1) {
    LOG_PRINT(LOG_INFO, get_status_msg(110));
    return 110;
  }

  return 0;
}

