/* noteshandler.c: functions to write note datasets to a database */
/* markus@mhoenicka.de 2003-10-08 */

/*
   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/>

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

/* Overview */
/* This set of functions parses a XML file containing notes datasets. We use expat as a non-validating XML parser. We register three handlers for start tags, character data, and end tags. The elements are pushed on a stack in the start tags handler. Each structure defining an element contains a start element of another stack for the attributes of this element. These stacks are used in the character data handler and the end tag handler to retrieve parent and ancestor elements and attributes of the current element where necessary. The attribute stack of the current element is freed in the end tag handler and the current element is popped off the stack as well. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h> /* for strftime */
#include <errno.h>
#include <expat.h> /* header of the XML parser */
#include <syslog.h> /* priority levels of log messages */
#include <iconv.h>
#include <sys/types.h> /* for getpid() */
#include <unistd.h>    /* for getpid() */
#include <dbi/dbi.h>

#include "refdb.h"
#include "linklist.h"
#include "refdbd.h"
#include "strfncs.h"
#include "xmlhandler.h"
#include "noteshandler.h"
#include "risdb.h"
#include "dbfncs.h"
#include "connect.h"

extern int n_log_level;
extern int nongeek_offset;

/* forward declaration of local functions */
static int set_notesdata_field(const char* field, const char* value, dbi_conn conn, unsigned long long n_note_id);
static int set_notesdata_int_field(const char* field, int n_value, dbi_conn conn, unsigned long long n_note_id);
static int set_notesdata_longlong_field(const char* field, unsigned long long n_value, dbi_conn conn, unsigned long long n_note_id);



/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  notes_start_handler(): handler for start tags
  
  void notes_start_handler has no return value

  void* ptr_data this is a ptr to "non-global" global data that all
             handlers share - will be cast to type struct addnotes_data*

  const char *el ptr to a string containing the element name

  const char** ptr_attr ptr to an array of attributes (name-value pairs)

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void notes_start_handler(void *ptr_data, const char *el, const char **ptr_attr) {
  const char* user = NULL;
  const char* id = NULL;
  const char* key = NULL;
  const char* date = NULL;
  const char* share = NULL;
  char* new_msgpool;
  char sql_command[1024] = "";
  int i;
  int result;
  struct elstack* ptr_el_new;
  struct attrlist* ptr_attr_new;
  struct addnotes_data* ptr_andata;
  dbi_result dbires;

  ptr_andata = (struct addnotes_data*)ptr_data;

/*    printf("start_handler found el:%s<< attname:%s<< attvalue:%s<<ndb_error:%d<< nmem_error:%d<< n_skip:%d<<\n", el, ptr_attr[0], ptr_attr[1], ptr_andata->ndb_error, ptr_andata->nmem_error, ptr_andata->n_skip);  */

  if (!ptr_andata->ndb_error && !ptr_andata->nmem_error && !ptr_andata->n_skip) {
    if (!strcmp(el, "xnoteset")) {
      (ptr_andata->depth_adjust)++;
    }
    else if (!strcmp(el, "xnote")) {
      time_t the_time;
      /* If the db server supports it, start a transaction. We want one
	 transaction per reference */
      if (my_dbi_conn_begin(ptr_andata->conn)) {
	(ptr_andata->ndb_error)++;
	LOG_PRINT(LOG_WARNING, "begin transaction failed");
	return;
      }
  
      /* lock the tables we'll write to to prevent concurrent writes
	 from different clients */
      if (my_dbi_conn_lock_note(ptr_andata->conn)) {
	(ptr_andata->ndb_error)++;
	LOG_PRINT(LOG_WARNING, "Cannot lock tables");
	return;
      }

      /* reset a few variables relevant to entries */
      ptr_andata->create_new = 1;
      ptr_andata->share = -1; /* undecided */
      *(ptr_andata->date_buffer) = '\0';
      *(ptr_andata->content_type) = '\0';
      *(ptr_andata->content_xmllang) = '\0';
      *(ptr_andata->real_key) = '\0';

      /* isolate attributes */
      for (i = 0; ptr_attr[i]; i += 2) {
	if (!strcmp(ptr_attr[i], "user")) {
	  user = ptr_attr[i+1];
	}
	else if (!strcmp(ptr_attr[i], "id")) {
	  id = ptr_attr[i+1];
	}
	else if (!strcmp(ptr_attr[i], "citekey")) {
	  key = ptr_attr[i+1];
	}
	else if (!strcmp(ptr_attr[i], "date")) {
	  date = ptr_attr[i+1];
	}
	else if (!strcmp(ptr_attr[i], "share")) {
	  share = ptr_attr[i+1];
	}
      }

      /* we're out of luck if the user attribute is too long */
      if (user && strlen(user) > DBUSER_LENGTH) {
	(ptr_andata->ndb_error)++;
	LOG_PRINT(LOG_WARNING, "user attribute missing or too long");
	return;
      }
      else if (!user) {
	user = ptr_andata->user;
      }
      else {
	strncpy(ptr_andata->user, user, DBUSER_LENGTH);
	ptr_andata->user[DBUSER_LENGTH-1] = '\0';
      }

      if (share && *share) {
	if (!strcmp(share, "public")) {
	  ptr_andata->share = 1;
	}
	else if (!strcmp(share, "private")) {
	  ptr_andata->share = 0;
	}
      }

      if (date && strlen(date) == 10) {
	/* try to parse date according to YYYY-MM-DD */
	int year;
	int month;
	int day;
	char my_date[11];

	/* get a copy that we can modify. length is checked above */
	strcpy(my_date, date);

	if (my_date[4] == '-') {
	  my_date[4] = '\0';
	  year = atoi(my_date);

	  if (year >= 1900 && year <= 2036) {
	    if (my_date[7] == '-') {
	      my_date[7] = '\0';
	      month = atoi(my_date+5);

	      if (month > 0 && month < 13) {
		day = atoi(my_date+8);

		if (day > 0 && day < 32) {
		  strcpy(ptr_andata->date_buffer, date);
		  strcpy(ptr_andata->year, my_date);
		}
		/* else: day out of range */
	      }
	      /* else: month out of range */
	    }
	    /* else: invalid month */
	  }
	  /* else: year out of range */
	}
	/* else: invalid year */
      }
      
      if (!*(ptr_andata->date_buffer)) {
	/* set date to current time in case no date is specified */
	time(&the_time);
	strftime(ptr_andata->date_buffer, 12, "%Y-%m-%d", gmtime(&the_time));
	strftime(ptr_andata->year, 5, "%Y", gmtime(&the_time));
      }

      /* todo: check whether current user is owner of the existing note */
      /* see whether id or key already exist in the database */
      if (key && *key) {
	char *new_key;

	/* preprocess and truncate */
	if ((new_key = preprocess_citekey_copy(key, 255)) == NULL) {
	  return;
	}
	else {
	  /* new_key is already truncated */
	  strcpy(ptr_andata->real_key, new_key);
	  free(new_key);
	}
	sprintf(sql_command, "SELECT t_note.note_id, t_user.user_name FROM t_note, t_user WHERE t_note.note_user_id=t_user.user_id AND t_note.note_key=\'%s\'", ptr_andata->real_key);
      }
      else if (id && *id) {
	if (strlen(id) > 20) { /* largest 8-byte integer */
	  (ptr_andata->ndb_error)++;
	  LOG_PRINT(LOG_WARNING, "ID value too long");
	  return ;
	}
	sprintf(sql_command, "SELECT t_note.note_id, t_user.user_name FROM t_note, t_user WHERE t_note.note_user_id=t_user.user_id AND t_note.note_id=%s", id);
      }

      if (*sql_command) {
	/* search for existing entry */
	LOG_PRINT(LOG_DEBUG, sql_command);
      
	dbires = dbi_conn_query(ptr_andata->conn, sql_command);
	if (!dbires) {
	  (ptr_andata->ndb_error)++;
	  LOG_PRINT(LOG_WARNING, "Search ID failed");
	  return;
	}

	/* we have to consider these cases:
	   - addnote: entry with same ID or key doesn't exist-> add
	   - addnote: entry with same ID/key exists-> error
	   - updatenote: entry with same ID/key doesn't exist-> add
	   - updatenote: entry with same ID/key exists-> update if same user */
    
	if (dbi_result_next_row(dbires)) { /* requested ID exists */
	  if (ptr_andata->replace_note) {
	    const char* existing_user = my_dbi_result_get_string(dbires, "user_name");
	    if (strcmp(existing_user, user)) {
	      sprintf(sql_command, "only owner can overwrite existing note %s", ptr_andata->real_key);
	      LOG_PRINT(LOG_INFO, sql_command);
	      (ptr_andata->n_skip)++;
	      sprintf(sql_command, "422:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), ptr_andata->real_key);
	      if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
		(ptr_andata->nmem_error)++;
		return;
	      }
	      else {
		ptr_andata->msgpool = new_msgpool;
	      }
	      return;
	    }
	    ptr_andata->create_new = 0;
	    ptr_andata->n_note_id = my_dbi_result_get_idval(dbires, "note_id");
	  }
	  else { /* we're supposed to add */
	    if (*(ptr_andata->real_key)) {/* we can't add lest we overwrite existing data */
	      sprintf(sql_command, "refused to overwrite existing note %s", ptr_andata->real_key);
	      LOG_PRINT(LOG_INFO, sql_command);
	      (ptr_andata->n_skip)++;
	      sprintf(sql_command, "407:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), ptr_andata->real_key);
	      if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
		(ptr_andata->nmem_error)++;
		return;
	      }
	      else {
		ptr_andata->msgpool = new_msgpool;
	      }
	      return;
	    }
	    else { /* ignore numerical ID and assign a new one */
	      LOG_PRINT(LOG_INFO, "numerical ID ignored");
	      sprintf(sql_command, "409:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), id);
	      if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
		(ptr_andata->nmem_error)++;
		return;
	      }
	      else {
		ptr_andata->msgpool = new_msgpool;
	      }
	    }
	  }
	}
	else { /* requested ID could not be found */
	  if (ptr_andata->replace_note == 1) {
	    ptr_andata->create_new = 1;
	  }
	  else if (ptr_andata->replace_note == 2) {
	    dbi_result_free(dbires);
	    (ptr_andata->ndb_error)++;
	    LOG_PRINT(LOG_WARNING, "ID not found");
	      sprintf(sql_command, "411:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), ptr_andata->real_key);
	    if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	      (ptr_andata->nmem_error)++;
	      return;
	    }
	    else {
	      ptr_andata->msgpool = new_msgpool;
	    }
	    return;
	  }
	  else if (!*(ptr_andata->real_key)) { /* if add, ignore numerical ID and assign a new one */
	    LOG_PRINT(LOG_INFO, "numerical ID ignored");
	      sprintf(sql_command, "409:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), id);
	    if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	      (ptr_andata->nmem_error)++;
	      return;
	    }
	    else {
	      ptr_andata->msgpool = new_msgpool;
	    }
	  }
	}
	dbi_result_free(dbires);
      }
      else { /* no ID string */
	if (ptr_andata->replace_note == 1) {
	  ptr_andata->create_new = 1; /* if no ID string, simply add the dataset */
	}
	else if (ptr_andata->replace_note == 2){
	  LOG_PRINT(LOG_WARNING, "ID missing");
	  (ptr_andata->ndb_error)++;
	  sprintf(sql_command, "412:"ULLSPEC"\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset));
	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  return;
	}
	/* else: add will assign new ID anyway */
      }

      /* if we're replacing, first remove the existing entry as far as necessary */
      if (!ptr_andata->create_new && ptr_andata->replace_note != 2) {
/* 	sprintf(sql_command, "try to replace note "ULLSPEC"\n", (unsigned long long)(ptr_andata->n_note_id)); */
/* 	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) { */
/* 	  (ptr_andata->nmem_error)++; */
/* 	  return; */
/* 	} */
/* 	else { */
/* 	  ptr_andata->msgpool = new_msgpool; */
/* 	} */

	/* search orphans in t_keyword */
	/* printf("orphans in t_keyword\n"); */
	result = remove_keyword_entries(ptr_andata->n_note_id, ptr_andata->conn, 1);
	if (result) {
	  if (result == 1) {
	    strcpy(sql_command, "select from t_xkeyword failed\n");
	  }
	  else if (result == 2) {
	    strcpy(sql_command, "delete from t_keyword failed\n");
	  }
	  else if (result == 3) {
	    strcpy(sql_command, "delete from t_xkeyword failed\n");
	  }

	  (ptr_andata->ndb_error)++;

	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  return;
	}
	/* else: all fine */

	result = remove_ulink_entries(ptr_andata->n_note_id, ptr_andata->user, ptr_andata->conn, 1);
	if (result) {
	  if (result == 1) {
	    strcpy(sql_command, "select from t_xlink failed\n");
	  }
	  else if (result == 2) {
	    strcpy(sql_command, "delete from t_link failed\n");
	  }
	  else if (result == 3) {
	    strcpy(sql_command, "delete from t_xlink failed\n");
	  }

	  (ptr_andata->ndb_error)++;

	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  return;
	}
	/* else: all fine */


	/* search orphans in t_xnote */
	result = remove_xnote_entries(ptr_andata->n_note_id, 0 /* any link target */, ptr_andata->conn, 4);

	if (result && result < 4) { /* 4 indicates no link found */
	  if (result == 1) {
	    strcpy(sql_command, "select from t_xnote failed\n");
	  }
	  else if (result == 3) {
	    strcpy(sql_command, "delete from t_xnote failed\n");
	  }

	  (ptr_andata->ndb_error)++;

	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  return;
	}

	/* reset all values in t_note, keep key, use new type */
	sprintf(sql_command, "UPDATE t_note SET note_title=NULL, note_content=NULL WHERE note_id="ULLSPEC, (unsigned long long)(ptr_andata->n_note_id));

	LOG_PRINT(LOG_DEBUG, sql_command);

	dbires = dbi_conn_query(ptr_andata->conn, sql_command);
	if (!dbires) {
	  LOG_PRINT(LOG_WARNING, "deletenote failed");
	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, "deletenote failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  (ptr_andata->ndb_error)++;
	  return;
	}
	dbi_result_free(dbires);
      }


      if (ptr_andata->create_new) {
	/* generate temporary key if necessary */
	if (!*(ptr_andata->real_key)) {
	  /* use pid to avoid problems with concurrent inserts */
	  snprintf(ptr_andata->real_key, 256, "dummy%s%d", ptr_andata->ptr_clrequest->my_hostname, getpid());
	}

	/* insert a new empty dataset into the main table to start with */
	sprintf(sql_command, "INSERT INTO t_note (note_key) VALUES (\'%s\')", ptr_andata->real_key);
	dbires = dbi_conn_query(ptr_andata->conn, sql_command);
	LOG_PRINT(LOG_DEBUG, sql_command);
	if (!dbires) {
	  (ptr_andata->ndb_error)++;
	  LOG_PRINT(LOG_WARNING, "insert into t_note failed");
	  return;
	}

	dbi_result_free(dbires);

	/* retrieve note_id of newly created dataset */
	if (!strcmp(my_dbi_conn_get_cap(ptr_andata->conn, "named_seq"), "f")) {
	  ptr_andata->n_note_id = dbi_conn_sequence_last(ptr_andata->conn, NULL);
	}
	else {
	  ptr_andata->n_note_id = dbi_conn_sequence_last(ptr_andata->conn, "t_note_note_id_seq");
	}

/* 	sprintf(sql_command, "try to add set as note "ULLSPEC"\n", (unsigned long long)(ptr_andata->n_note_id)); */
/* 	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) { */
/* 	  LOG_PRINT(LOG_WARNING, get_status_msg(801)); */
/* 	  (ptr_andata->nmem_error)++; */
/* 	  return; */
/* 	} */
/* 	else { */
/* 	  ptr_andata->msgpool = new_msgpool; */
/* 	} */
      } /* end if create_new */

      /* create user and xuser entries */
      result = insert_user(user, ptr_andata->n_note_id, &(ptr_andata->n_user_id), ptr_andata->conn, ptr_andata->drivername, ptr_andata->replace_note, 1);
      
      if (result == 1) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "search user failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 2) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert t_user failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
/*       else if (result == 3) { */
/*       } */
      else if (result == 4) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "username too long\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 5) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert user failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
/*       else if (result == 6) { */
/*       } */

      result = set_notesdata_longlong_field("user_id", ptr_andata->n_user_id, ptr_andata->conn, ptr_andata->n_note_id);

      if (result == 1) {
	LOG_PRINT(LOG_WARNING, "out of memory");
      }
      else if (result == 2) {
	LOG_PRINT(LOG_WARNING, "insert into t_note failed");
      }

      result = set_notesdata_int_field("share", ptr_andata->share, ptr_andata->conn, ptr_andata->n_note_id);

      if (result == 1) {
	LOG_PRINT(LOG_WARNING, "out of memory");
      }
      else if (result == 2) {
	LOG_PRINT(LOG_WARNING, "insert into t_note failed");
      }
    } 
    else if (!strcmp(el, "content")) {
      ptr_andata->notepool_len = ELVALUE_LENGTH;
      ptr_andata->notepool = malloc(ELVALUE_LENGTH);
      if (ptr_andata->notepool == NULL) {
	(ptr_andata->nmem_error)++;
      }
      else {
	*(ptr_andata->notepool) = '\0';
      }

      for (i = 0; ptr_attr[i]; i += 2) {
	if (!strcmp(ptr_attr[i], "type")) {
	  strncpy(ptr_andata->content_type, ptr_attr[i+1], 255);
	  ptr_andata->content_type[255] = '\0';
	}
	else if (!strcmp(ptr_attr[i], "xml:lang")) {
	  strncpy(ptr_andata->content_xmllang, ptr_attr[i+1], 255);
	  ptr_andata->content_xmllang[255] = '\0';
	}
      }
    }
    else {
      if (is_descendant_of(ptr_andata->ptr_first, "content")) {
	char* new_notepool;
	char attribute_buf[EL_LENGTH];

	new_notepool = mstrcat(ptr_andata->notepool, "<", &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}

/* 	printf("found content descentant %s<<\n", el); */
	new_notepool = mstrcat(ptr_andata->notepool, (char*)el, &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}


	/* insert attributes */

	for (i = 0; ptr_attr[i]; i += 2) {
	  sprintf(attribute_buf, " %s=\'%s\'", ptr_attr[i], ptr_attr[i+1]);

	  new_notepool = mstrcat(ptr_andata->notepool, attribute_buf, &(ptr_andata->notepool_len), 0);
	
	  if (new_notepool) {
	    ptr_andata->notepool = new_notepool;
	  }
	  else {
	    (ptr_andata->nmem_error)++;
	  }
	}



	new_notepool = mstrcat(ptr_andata->notepool, ">", &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}
/* 	printf("notepool went to %s<<\n", ptr_andata->notepool); */
      }
      /* else: ignore */
    } /* end if "xnoteset" */

    /* add current element to element stack */
    ptr_el_new = malloc(sizeof(struct elstack));
    if (ptr_el_new == NULL) {
      (ptr_andata->nmem_error)++;
    }
    else {
      /*    printf("have memory\n"); */
      strncpy(ptr_el_new->elname, el, 63);
      ptr_el_new->elname[63] = '\0'; /* terminate just in case */
      ptr_el_new->n_elvalue_len = ELVALUE_LENGTH;
      ptr_el_new->ptr_elvalue = malloc(ELVALUE_LENGTH);
      if (ptr_el_new->ptr_elvalue == NULL) {
	(ptr_andata->nmem_error)++;
      }
      else {
	*(ptr_el_new->ptr_elvalue) = '\0';
      }
      ptr_el_new->ptr_next = ptr_andata->ptr_first;
      ptr_el_new->ptr_attr_first = NULL;
      ptr_andata->ptr_first = ptr_el_new;
/*       printf("%s", ptr_el_new->elname); */
      
      /* add current attributes to the element */
      /*    printf("add attributes\n"); */
      for (i = 0; ptr_attr[i]; i += 2) {
	ptr_attr_new = malloc(sizeof(struct attrlist));
	if (ptr_attr_new == NULL) {
	  (ptr_andata->nmem_error)++;
	  break;
	}
	strncpy(ptr_attr_new->attribute_name, ptr_attr[i], 63);
	ptr_attr_new->attribute_name[63] = '\0';
	strncpy(ptr_attr_new->attribute_value, ptr_attr[i+1], 63);
	ptr_attr_new->attribute_value[63] = '\0';
	ptr_attr_new->ptr_next = (ptr_andata->ptr_first)->ptr_attr_first;
	(ptr_andata->ptr_first)->ptr_attr_first = ptr_attr_new;
/*         printf(" %s='%s'", ptr_attr[i], ptr_attr[i + 1]); */
      }
    /*    printf("done adding attributes\n"); */
      (ptr_andata->depth)++;
    }
  }

  return;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  notes_end_handler(): handler for end tags

  void notes_end_handler has no return value

  void* ptr_data this is a ptr to "non-global" global data that all
             handlers share - will be cast to type struct addnotes_data*

  const char *el ptr to a string containing the element name

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void notes_end_handler(void *ptr_data, const char *el) {
  char sql_command[256];
  char* new_msgpool = NULL;
  int result = 0;
  struct elstack* ptr_el_remove;
  struct attrlist* ptr_attr_remove;
  struct addnotes_data* ptr_andata;
  dbi_result dbires;

  ptr_andata = (struct addnotes_data*)ptr_data;

/*   printf("end handler found el:%s<<ndb_error:%d<< nmem_error:%d<<\n", el, ptr_andata->ndb_error, ptr_andata->nmem_error); */

  if (ptr_andata->n_skip) {
    if (!strcmp(el, "xnote")) {
      (ptr_andata->n_skip)--;
      (ptr_andata->skipped_count)++;

      /* close transaction of skipped dataset, if any */
      my_dbi_conn_rollback(ptr_andata->conn);
    }
    return;
  }

  if (ptr_andata->depth) {
    (ptr_andata->depth)--;
  }

  if (!ptr_andata->ndb_error && !ptr_andata->nmem_error) {
    /* do a character conversion if required. expat dumps all
       character data as UTF-8 regardless of the input encoding */
    size_t inlength;
    size_t outlength;
    char* my_elvalue = NULL; /* this ptr will be modified by iconv() */
    char* my_elvalue_start = NULL; /* records initial state of my_elvalue */
    const char* my_instring = NULL; /* this ptr will be modified by iconv() */

    if (ptr_andata->conv_descriptor && *((ptr_andata->ptr_first)->ptr_elvalue)) {
      inlength = strlen((ptr_andata->ptr_first)->ptr_elvalue)/*  + 1 */;
      /* with the encodings supported by our database engines, the converted
	 string can't be longer than the input string */
      outlength = inlength + 1;

      if ((my_elvalue = malloc(outlength)) == NULL) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->nmem_error)++;
	return;
      }

      /* keep start of the converted string */
      my_elvalue_start = my_elvalue;

      /* variable will be modified by iconv, so don't use original */
      my_instring = (const char*)((ptr_andata->ptr_first)->ptr_elvalue);

      /* now actually do the conversion */
      if (iconv(ptr_andata->conv_descriptor, &my_instring, &inlength, &my_elvalue, &outlength) == (size_t)(-1)) {
	if (errno == EILSEQ) {
	  new_msgpool = mstrcat(ptr_andata->msgpool, "iconv: invalid input character sequence\n", &(ptr_andata->msgpool_len), 0);
	  LOG_PRINT(LOG_WARNING, "iconv: invalid input character sequence");
	}
	else if (errno == E2BIG) {
	  new_msgpool = mstrcat(ptr_andata->msgpool, "iconv: output buffer too small\n", &(ptr_andata->msgpool_len), 0);
	  LOG_PRINT(LOG_WARNING, "iconv: output buffer too small");
	}
	else if (errno == EINVAL) {
	  new_msgpool = mstrcat(ptr_andata->msgpool, "iconv: incomplete input character\n", &(ptr_andata->msgpool_len), 0);
	  LOG_PRINT(LOG_WARNING, "iconv: incomplete input character");
	}

	if (new_msgpool == NULL) {
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      /* else: conversion went ok. We free the original string and replace
	 it with the converted copy */
      if ((ptr_andata->ptr_first)->ptr_elvalue) {
	free((ptr_andata->ptr_first)->ptr_elvalue);
      }

      *my_elvalue = '\0'; /* terminate converted string */
      (ptr_andata->ptr_first)->ptr_elvalue = my_elvalue_start;
      (ptr_andata->ptr_first)->n_elvalue_len = strlen((ptr_andata->ptr_first)->ptr_elvalue);
    }
    /* else: no conversion required */

    /* ------------------------------------------------------------ */
    if (!strcmp(el, "title") && ptr_andata->replace_note != 2) {
      result = 0;

      result = set_notesdata_field("title", (ptr_andata->ptr_first)->ptr_elvalue, ptr_andata->conn, ptr_andata->n_note_id);

      if (result == 1) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
    }

    /* ------------------------------------------------------------ */
    else if (!strcmp(el, "link") && ptr_andata->replace_note != 2) {
      char* type;
      char* target;

      type = get_attr(ptr_andata->ptr_first, "type");
      target = get_attr(ptr_andata->ptr_first, "target");
      
      result = insert_link(type, target, ptr_andata->conn, ptr_andata->n_note_id);

      if (result == 1 /* out of memory */) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 2 /* incorrect link type */) {
	LOG_PRINT(LOG_WARNING, "insert_link(): incorrect link type");
	sprintf(sql_command, "416:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), type);
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 3 /* xref query failed */) {
	LOG_PRINT(LOG_WARNING, "insert_link(): xref query failed");
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "xref query failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 4 /* missing target */) {
	LOG_PRINT(LOG_INFO, "insert_link(): missing link target");
	sprintf(sql_command, "415:"ULLSPEC":%s:%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), type, target);
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
      }
      else if (result == 5 /* xnote query failed */) {
	LOG_PRINT(LOG_WARNING, "insert_link(): xnote query failed");
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "xnote query failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 6 /* insert into t_xnote failed */) {
	LOG_PRINT(LOG_WARNING, "insert_link(): insert into t_xnote failed");
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert into t_xnote failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 7 /* link already exists */) {
	sprintf(sql_command, "418:"ULLSPEC":%s:%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), type, target);
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
      }
      else { /* all fine, report what happened */
	sprintf(sql_command, "421:"ULLSPEC":%s:%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), type, target);
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
      }
    }
    /* ------------------------------------------------------------ */
    else if (!strcmp(el, "content")) {
      if (ptr_andata->notepool && *(ptr_andata->notepool)) {
	result = set_notesdata_field("content", ptr_andata->notepool, ptr_andata->conn, ptr_andata->n_note_id);
/* 	printf("notepool went to %s<<\n", ptr_andata->notepool); */
	free(ptr_andata->notepool);
	ptr_andata->notepool = NULL;
      }
      else if (ptr_andata->ptr_first->ptr_elvalue && *ptr_andata->ptr_first->ptr_elvalue) {
	result = set_notesdata_field("content", ptr_andata->ptr_first->ptr_elvalue, ptr_andata->conn, ptr_andata->n_note_id);
      }

      if (result == 1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 2) {
	LOG_PRINT(LOG_WARNING, "update note failed");
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "update note failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      /* else: all fine */

      if (*(ptr_andata->content_type)) {
	result = set_notesdata_field("content_type", ptr_andata->content_type, ptr_andata->conn, ptr_andata->n_note_id);

	if (result == 1) {
	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  (ptr_andata->ndb_error)++;
	  return;
	}
      }

      if (*(ptr_andata->content_xmllang)) {
	result = set_notesdata_field("content_xmllang", ptr_andata->content_xmllang, ptr_andata->conn, ptr_andata->n_note_id);

	if (result == 1) {
	  if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	    (ptr_andata->nmem_error)++;
	    return;
	  }
	  else {
	    ptr_andata->msgpool = new_msgpool;
	  }
	  (ptr_andata->ndb_error)++;
	  return;
	}
      }
    }

    /* ------------------------------------------------------------ */
    else if (!strcmp(el, "keyword") && ptr_andata->replace_note != 2) {
      result = insert_keyword((ptr_andata->ptr_first)->ptr_elvalue, ptr_andata->n_note_id, ptr_andata->conn, ptr_andata->driver, ptr_andata->drivername, 1 /* mode=note */, ptr_andata->replace_note);

      if (result == 1) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 2) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "query KW failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 3) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert KW failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 4) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "driver not supported\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 5) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert KW x failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      /* else: all fine */
    }

    /* ------------------------------------------------------------ */
    else if (!strcmp(el, "ulink") && ptr_andata->replace_note != 2) {
      char *type;
      int n_type = 0;
      type = get_attr(ptr_andata->ptr_first, "type");

      if (!strcmp(type, "url")) {
	n_type = 0;
      }
      else if (!strcmp(type, "pdf")) {
	n_type = 1;
      }
      else if (!strcmp(type, "fulltext")) {
	n_type = 2;
      }
      else if (!strcmp(type, "related")) {
	n_type = 3;
      }
      else if (!strcmp(type, "image")) {
	n_type = 4;
      }

      result = insert_ulink((ptr_andata->ptr_first)->ptr_elvalue, n_type, ptr_andata->n_note_id, ptr_andata->conn, ptr_andata->driver, ptr_andata->drivername, ptr_andata->n_user_id, 1 /* mode=note */, ptr_andata->replace_note);

      if (result == 1) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, (char*)get_status_msg(801), &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 2) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "query UR failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 3) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert UR failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      else if (result == 5) {
	if ((new_msgpool = mstrcat(ptr_andata->msgpool, "insert UR x failed\n", &(ptr_andata->msgpool_len), 0)) == NULL) {
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
	(ptr_andata->ndb_error)++;
	return;
      }
      /* else: all fine */
    }

    /* ------------------------------------------------------------ */
    else if (!strcmp(el, "xnote")) {
      const char* key;
      int result;

      /* insert new citekey if required */
      if (!strncmp(ptr_andata->real_key, "dummy", 5) && ptr_andata->create_new) {
	key = get_unique_citekey(ptr_andata->conn, ptr_andata->user, atoi(ptr_andata->year), 1 /* notes */, 0 /* regular tables */);
	
	if (key && set_notesdata_field("key", key, ptr_andata->conn, ptr_andata->n_note_id) != 0) {
	  if (key) {
	    free((char*)key);
	  }
	  (ptr_andata->ndb_error)++;
	}
	strcpy(ptr_andata->real_key, key);
	free((char*)key);
      }


      /* ToDo: check return */

      /* set date */
      result = set_notesdata_field("date", ptr_andata->date_buffer, ptr_andata->conn, ptr_andata->n_note_id);

      /* close transaction, if any */
      my_dbi_conn_commit(ptr_andata->conn);
      
      if (insert_lilid(ptr_andata->ptr_id_sentinel, ptr_andata->n_note_id)) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
      }
  
      if (*(ptr_andata->real_key)) { /* still empty if we update the reference */
	sprintf(sql_command, "406:"ULLSPEC":%s\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset), ptr_andata->real_key);

	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  LOG_PRINT(LOG_WARNING, get_status_msg(801));
	  (ptr_andata->nmem_error)++;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
      }

      if (ptr_andata->ndb_error || ptr_andata->nmem_error) {
	if (strcmp(my_dbi_conn_get_cap(ptr_andata->conn, "transaction"), "t")) {
	  /* we have to delete the junk reference manually */
	  if (*(ptr_andata->real_key)) {
	    sprintf(sql_command, "DELETE FROM t_note WHERE note_key=\'%s\'", ptr_andata->real_key);
	  }
	  else {
	    sprintf(sql_command, "DELETE FROM t_note WHERE note_key=\'dummy%d\'", getpid());
	  }

	  LOG_PRINT(LOG_DEBUG, sql_command);
	  dbires = dbi_conn_query(ptr_andata->conn, sql_command);
	  if (!dbires) {
	    LOG_PRINT(LOG_INFO, "removing junk reference failed");
	  }
	  dbi_result_free(dbires);
	}
	else {
	  my_dbi_conn_rollback(ptr_andata->conn);
	}
	my_dbi_conn_unlock(ptr_andata->conn);
      }
      else {
	my_dbi_conn_unlock(ptr_andata->conn);

	if (ptr_andata->create_new) {
/* 	  sprintf(sql_command, "Adding input set "ULLSPEC" successful\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset)); */
	  sprintf(sql_command, "408:"ULLSPEC"\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset));
	  (ptr_andata->added_count)++;
	}
	else {
/* 	  sprintf(sql_command, "Updating input set "ULLSPEC" successful\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset)); */
	  sprintf(sql_command, "413:"ULLSPEC"\n", (unsigned long long)(ptr_andata->set_count + nongeek_offset));
	  (ptr_andata->updated_count)++;
	}

	(ptr_andata->set_count)++;

	if ((new_msgpool = mstrcat(ptr_andata->msgpool, sql_command, &(ptr_andata->msgpool_len), 0)) == NULL) {
	  LOG_PRINT(LOG_WARNING, get_status_msg(801));
	  (ptr_andata->nmem_error)++;
	  return;
	}
	else {
	  ptr_andata->msgpool = new_msgpool;
	}
      }
    }
    else {
      if (is_descendant_of(ptr_andata->ptr_first, "content")) {
	char* new_notepool;
	new_notepool = mstrcat(ptr_andata->notepool, (ptr_andata->ptr_first)->ptr_elvalue, &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}

	new_notepool = mstrcat(ptr_andata->notepool, "</", &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}
	new_notepool = mstrcat(ptr_andata->notepool, ptr_andata->ptr_first->elname, &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}
	new_notepool = mstrcat(ptr_andata->notepool, ">", &(ptr_andata->notepool_len), 0);
	
	if (new_notepool) {
	  ptr_andata->notepool = new_notepool;
	}
	else {
	  (ptr_andata->nmem_error)++;
	}

/* 	printf("in endhandler: notepool went to %s<<\n", ptr_andata->notepool); */
      }
      /* else: ignore */
    }
  }

/*   printf("removing %s from elstack\n", (ptr_andata->ptr_first)->elname); */

  /* remove attributes of the current element from the list */
  if (ptr_andata->depth && ptr_andata->ptr_first) {
    ptr_attr_remove = (ptr_andata->ptr_first)->ptr_attr_first;
    while (ptr_attr_remove) {
      (ptr_andata->ptr_first)->ptr_attr_first = ((ptr_andata->ptr_first)->ptr_attr_first)->ptr_next;
      free(ptr_attr_remove);
      ptr_attr_remove = (ptr_andata->ptr_first)->ptr_attr_first;
    }
    
    /* free element value string */
    if ((ptr_andata->ptr_first)->ptr_elvalue) {
      free((ptr_andata->ptr_first)->ptr_elvalue);
    }

    /* remove current element from element stack */
    ptr_el_remove = ptr_andata->ptr_first;
    ptr_andata->ptr_first = (ptr_andata->ptr_first)->ptr_next;
    if (ptr_el_remove) {
      free(ptr_el_remove);
    }
  }

  return;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  notes_char_handler(): handler for character data.

  void notes_char_handler has no return value

  void* ptr_data this is a ptr to "non-global" global data that all
             handlers share - will be cast to type struct addnotes_data*

  const char *string ptr to a string containing the char data
                     this string is not \0 terminated and could be
                     only a part of the character data of an element!

  int len length length of string

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void notes_char_handler(void *ptr_data, const char *string, int len) {
  struct addnotes_data* ptr_andata;
  char* new_elvalue;
  char* buffer;

/*   printf("char handler found data:<<\n"); */

  ptr_andata = (struct addnotes_data*)ptr_data;
  
  if (ptr_andata->ndb_error || ptr_andata->nmem_error || ptr_andata->n_skip) {
    return;
  }

  /* get one byte extra for the terminating \0 */
  buffer = malloc(len+1);

  if (!buffer) {
    (ptr_andata->nmem_error)++;
    return;
  }

  strncpy(buffer, string, len);
  buffer[len] = '\0';

  /* concatenate with existing elvalue. The latter will be of zero
     length if this is the first chunk */
  new_elvalue = mstrcat((ptr_andata->ptr_first)->ptr_elvalue, buffer, &((ptr_andata->ptr_first)->n_elvalue_len), 0);

  free(buffer);

  if (new_elvalue) {
    (ptr_andata->ptr_first)->ptr_elvalue = new_elvalue;
  }
  else {
    (ptr_andata->nmem_error)++;
  }
  return;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  set_notesdata_field(): sets a value in t_notes

  static int set_notesdata_field returns 0 if ok, 1 if out of memory,
                         2 if query error

  const char *field ptr to a string containing the field name

  const char *value ptr to a string containing the field value

  dbi_conn conn connection to database server

  unsigned long long n_note_id id value of note

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int set_notesdata_field(const char* field, const char* value, dbi_conn conn, unsigned long long n_note_id) {
  char* sql_command;
  char* quoted_value;
  size_t sql_cmd_len = 128;
  dbi_result dbires;

  if (!field || !*field || !value || !*value) {
    /* nothing to do */
    return 0;
  }
  
  /* field length is limited but value length isn't */
  sql_cmd_len += strlen(value);

  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }

  quoted_value = mstrdup((char*)value);
  if (!quoted_value) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }
  if (dbi_conn_quote_string(conn, &quoted_value) == 0) {
    free(quoted_value);
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }

  /* assemble query */
  sprintf(sql_command, "UPDATE t_note SET note_%s=%s WHERE note_id="ULLSPEC, field, quoted_value, (unsigned long long)n_note_id); 

  free(quoted_value);

  LOG_PRINT(LOG_DEBUG, sql_command);

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

  if (!dbires) {
    return 2;
  }
  dbi_result_free(dbires);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  set_notesdata_int_field(): sets an int value in t_notes

  static int set_notesdata_int_field returns 0 if ok, 1 if out of memory,
                         2 if query error

  const char *field ptr to a string containing the field name

  int n_value the field value

  dbi_conn conn connection to database server

  unsigned long long n_note_id id value of note

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int set_notesdata_int_field(const char* field, int n_value, dbi_conn conn, unsigned long long n_note_id) {
  char* sql_command;
  size_t sql_cmd_len = 128;
  dbi_result dbires;

  if (!field || !*field) {
    /* nothing to do */
    return 0;
  }
  
  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }

  /* assemble query */
  sprintf(sql_command, "UPDATE t_note SET note_%s=%d WHERE note_id="ULLSPEC, field, n_value, (unsigned long long)n_note_id); 

  LOG_PRINT(LOG_DEBUG, sql_command);

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

  if (!dbires) {
    return 2;
  }
  dbi_result_free(dbires);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  set_notesdata_longlong_field(): sets a longlong value in t_notes

  static int set_notesdata_longlong_field returns 0 if ok, 1 if out of memory,
                         2 if query error

  const char *field ptr to a string containing the field name

  unsigned long long n_value the field value

  dbi_conn conn connection to database server

  unsigned long long n_note_id id value of note

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int set_notesdata_longlong_field(const char* field, unsigned long long n_value, dbi_conn conn, unsigned long long n_note_id) {
  char* sql_command;
  size_t sql_cmd_len = 128;
  dbi_result dbires;

  if (!field || !*field) {
    /* nothing to do */
    return 0;
  }
  
  if ((sql_command = malloc(sql_cmd_len)) == NULL) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }

  /* assemble query */
  sprintf(sql_command, "UPDATE t_note SET note_%s="ULLSPEC" WHERE note_id="ULLSPEC, field, (unsigned long long)n_value, (unsigned long long)n_note_id); 

  LOG_PRINT(LOG_DEBUG, sql_command);

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

  if (!dbires) {
    return 2;
  }
  dbi_result_free(dbires);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  insert_link(): inserts an entry in t_xnotes

  int insert_link returns 0 if ok, 1 if out of memory,
                         2 if incorrect link type, 3 if xref query
			 failed, 4 if missing target, 5 if
			 xnote query failed, 6 if insert into t_xnote
			 failed, 7 if link already exists

  const char *field ptr to a string containing the field name

  const char *value ptr to a string containing the field value

  dbi_conn conn connection to database server

  unsigned long long n_note_id id value of note

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int insert_link(const char* type, const char* target, dbi_conn conn, unsigned long long n_note_id) {
  char* sql_command;
  char* my_type;
  char* quoted_target;
  char xref_type[11];
  size_t sql_cmd_len = 512;
  unsigned long long n_xref_id;
  dbi_result dbires;
  dbi_result dbires1;

  if (!type || !*type || !target || !*target) {
    /* we're done */
    return 0;
  }

  if (!(sql_command = malloc(sql_cmd_len))) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    return 1;
  }

  if ((my_type = strdup(type)) == NULL) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    free(sql_command);
    return 1;
  }

  strup(my_type);

  /* quote target to make it usable for a SQL query */
  if ((quoted_target = strdup(target)) == NULL
      || dbi_conn_quote_string(conn, &quoted_target) == 0) {
    LOG_PRINT(LOG_WARNING, "malloc failed");
    free(sql_command);
    free(my_type);
    return 1;
  }

  /* first search xref_id of requested target */
  if (!strcmp(my_type, "JOURNALFULL")) {
    sprintf(sql_command, "SELECT periodical_id from t_periodical WHERE periodical_name=%s", quoted_target);
    strcpy(xref_type, "PERIODICAL");
  }
  else if (!strcmp(my_type, "JOURNALABBREV")) {
    sprintf(sql_command, "SELECT periodical_id from t_periodical WHERE periodical_abbrev=%s", quoted_target);
    strcpy(xref_type, "PERIODICAL");
  }
  else if (!strcmp(my_type, "JOURNALCUSTABBREV1")) {
    sprintf(sql_command, "SELECT periodical_id from t_periodical WHERE periodical_custabbrev1=%s", quoted_target);
    strcpy(xref_type, "PERIODICAL");
  }
  else if (!strcmp(my_type, "JOURNALCUSTABBREV2")) {
    sprintf(sql_command, "SELECT periodical_id from t_periodical WHERE periodical_custabbrev2=%s", quoted_target);
    strcpy(xref_type, "PERIODICAL");
  }
  else if (!strcmp(my_type, "KEYWORD")) {
    sprintf(sql_command, "SELECT keyword_id from t_keyword WHERE keyword_name=%s", quoted_target);
    strcpy(xref_type, "KEYWORD");
  }
  else if (!strcmp(my_type, "AUTHOR")) {
    sprintf(sql_command, "SELECT author_id from t_author WHERE author_name=%s", quoted_target);
    strcpy(xref_type, "AUTHOR");
  }
  else if (!strcmp(my_type, "REFID")) {
    sprintf(sql_command, "SELECT refdb_id from t_refdb WHERE refdb_id=%s", quoted_target);
    strcpy(xref_type, "REFERENCE");
  }
  else if (!strcmp(my_type, "REFERENCE")) {
    sprintf(sql_command, "SELECT refdb_id from t_refdb WHERE refdb_citekey=%s", quoted_target);
    strcpy(xref_type, "REFERENCE");
  }
  else {
    LOG_PRINT(LOG_WARNING, "incorrect link type");
    free(my_type);
    free(sql_command);
    return 2;
  }

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

  dbires = dbi_conn_query(conn, sql_command);
  
  if (!dbires) {
    free(sql_command);
    LOG_PRINT(LOG_WARNING, "query xref failed");
    return 3;
  }

  if (dbi_result_next_row(dbires)) {
    n_xref_id = my_dbi_result_get_idval_idx(dbires, 1);
  }
  else {
    n_xref_id = 0;
  }

  dbi_result_free(dbires);

  if (!n_xref_id) {
    free(sql_command);
    return 4;
  }

  /* check for existing link */
  sprintf(sql_command, "SELECT xnote_id FROM t_xnote WHERE note_id="ULLSPEC" AND xnote_type=\'%s\' AND xref_id="ULLSPEC, (unsigned long long)n_note_id, xref_type, (unsigned long long)n_xref_id);
  LOG_PRINT(LOG_DEBUG, sql_command);

  free(my_type);

  dbires = dbi_conn_query(conn, sql_command);
  if (!dbires) {
    free(sql_command);
    LOG_PRINT(LOG_WARNING, "query xnote failed");
    return 5;
  }

  if (dbi_result_next_row(dbires) == 0) {
    /* finally create entry in notes xref table */
    sprintf(sql_command, "INSERT INTO t_xnote (note_id, xref_id, xnote_type) VALUES ("ULLSPEC", "ULLSPEC", \'%s\')", (unsigned long long)n_note_id, (unsigned long long)n_xref_id, xref_type);

    LOG_PRINT(LOG_DEBUG, sql_command);
    
    dbires1 = dbi_conn_query(conn, sql_command);
    free(sql_command);
  
    if (!dbires1) {
      dbi_result_free(dbires);
      LOG_PRINT(LOG_WARNING, "insert xnote failed");
      return 6;
    }
    dbi_result_free(dbires1);
  }
  else {
    free(sql_command);
    dbi_result_free(dbires);
    return 7;    
  }

  dbi_result_free(dbires);

  return 0;
}

