/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  xmlhelper.c defines some functions to output xml elements
  markus@mhoenicka.de 2003-10-15

  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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <dbi/dbi.h> /* need this to make backend.h happy */

#include "backend.h"
#include "linklist.h"
#include "refdb.h"
#include "refdbd.h"
#include "xmlhelper.h"
#include "strfncs.h"
#include "connect.h"
#include "outformats.h"


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

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_element_x() prints out a risx element

  char* print_element_x returns ptr to the buffer if successful,
  NULL if failed

  const char* elvalue ptr to string with value of element

  char** ptr_buffer ptr to ptr to buffer that will receive the output

  size_t* ptr_buflen ptr to var holding size of *ptr_buffer

  const char* elname name of element to print

  const char* attname name of attribute, if any

  const char* attvalue value of attribute, if any

  const char* attname1 name of another attribute, if any

  const char* attvalue1 value of another attribute, if any

  struct xmlindent* ptr_indent ptr to struct with indent info

  const char* namespace namespace prefix, or NULL if no namespace prefix

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_element_x(const char* elvalue, char** ptr_buffer, size_t* ptr_buflen, const char* elname, const char* attname, const char* attvalue, const char* attname1, const char* attvalue1, struct xmlindent* ptr_indent, const char* namespace) {
  char* new_ref;
  char* entitize_buf;
  int is_empty = 1;

  if (elvalue && *elvalue) {
    is_empty = 0;
    if ((entitize_buf = mstrdup((char*)elvalue)) == NULL) {
      return NULL;
    }
      
    if (sgml_entitize(&entitize_buf, RISX) == NULL) {
      free(entitize_buf);
      return NULL;
    }
  }

  if ((new_ref = print_elstart_x(ptr_buffer, ptr_buflen, elname, attname, attvalue, attname1, attvalue1, NULL, NULL, NULL, NULL, NULL, NULL, is_empty, ptr_indent, namespace)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  if (elvalue && *elvalue) {
    if ((new_ref = mstrcat(*ptr_buffer, entitize_buf, ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      free(entitize_buf);
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }
    free(entitize_buf);

    if ((new_ref = print_elend_x(ptr_buffer, ptr_buflen, elname, ptr_indent, namespace)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }
  }
  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_elstart_x() prints out a risx element start tag

  char* print_elstart_x returns ptr to the buffer if successful,
  NULL if failed

  char** ptr_buffer ptr to ptr to buffer that will receive the output

  size_t* ptr_buflen ptr to var holding size of *ptr_buffer

  const char* elname name of element to print

  const char* attname name of attribute, if any

  const char* attvalue value of attribute, if any

  const char* attname1 name of 2nd attribute, if any

  const char* attvalue1 value of 2nd attribute, if any

  const char* attname2 name of 3rd attribute, if any

  const char* attvalue2 value of 3rd attribute, if any

  const char* attname3 name of 4th attribute, if any

  const char* attvalue3 value of 4th attribute, if any

  const char* attname4 name of 4th attribute, if any

  const char* attvalue4 value of 4th attribute, if any

  int is_empty if non-zero, element is assumed to be empty

  struct xmlindent* ptr_indent ptr to struct with indent info

  const char* namespace namespace prefix, or NULL if no namespace prefix

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_elstart_x(char** ptr_buffer, size_t* ptr_buflen, const char* elname, const char* attname, const char* attvalue, const char* attname1, const char* attvalue1, const char* attname2, const char* attvalue2, const char* attname3, const char* attvalue3, const char* attname4, const char* attvalue4, int is_empty, struct xmlindent* ptr_indent, const char* namespace) {
  /* ToDo: The attname/attname1 thing is pretty much like
     cheating. This could be done in a cleaner way using an attribute
     stack or something */
  char buffer[PREFS_BUF_LEN+2];
  char* new_ref;

  if (elname != NULL && *elname) {
    int indent_real = (ptr_indent->indent_current > INDENT_MAX) ? INDENT_MAX : ptr_indent->indent_current;
    ptr_indent->maybe_in_leaf = 1;

    /* safety check */
    if (ptr_indent->indent_current < 0) {
      LOG_PRINT(LOG_CRIT, "negative indentation at line: " STRINGIFY(__LINE__)
		" in " __FILE__);
      return NULL;
    }

    if (ptr_indent->indent_toodeep
	|| ptr_indent->inline_mode) {
      /* then just a '<' */
      if ((new_ref = mstrcat(*ptr_buffer, "<", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }
    else {
      /* skip a few lines before each entry */
      if (ptr_indent->isentry(elname)) {
	if ((new_ref = mstrcat(*ptr_buffer, "\n\n", ptr_buflen, 0)) == NULL) {
	  LOG_PRINT(LOG_WARNING, get_status_msg(801));
	  return NULL;
	}
	else {
	  *ptr_buffer = new_ref;
	}
      }

      /* linefeed just before indent */
      if ((new_ref = mstrcat(*ptr_buffer, "\n", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }

      /* do indent + '<' */
      if ((new_ref = mstrcat(*ptr_buffer,
			     & ptr_indent->indent_string[INDENT_MAX-indent_real],
			     ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }

    ptr_indent->indent_current += INDENT_INC;

    /* prevents too much indentation */
    if (ptr_indent->notbelow(elname)) {
      ptr_indent->indent_toodeep = 1; 
    }

    /* end of indentation + '<' code */


    if (namespace && *namespace) {
      sprintf(buffer, "%s:", namespace);
      if ((new_ref = mstrcat(*ptr_buffer, buffer, ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }

    if ((new_ref = mstrcat(*ptr_buffer, (char*)elname, ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }

    if (attname && *attname) {
      if (!print_attribute_x(ptr_buffer, ptr_buflen, attname, attvalue)) {
	return NULL;
      }
    } /* end if attname */

    if (attname1 && *attname1) {
      if (!print_attribute_x(ptr_buffer, ptr_buflen, attname1, attvalue1)) {
	return NULL;
      }
    } /* end if attname */

    if (attname2 && *attname2) {
      if (!print_attribute_x(ptr_buffer, ptr_buflen, attname2, attvalue2)) {
	return NULL;
      }
    } /* end if attname */

    if (attname3 && *attname3) {
      if (!print_attribute_x(ptr_buffer, ptr_buflen, attname3, attvalue3)) {
	return NULL;
      }
    } /* end if attname */

    if (attname4 && *attname4) {
      if (!print_attribute_x(ptr_buffer, ptr_buflen, attname4, attvalue4)) {
	return NULL;
      }
    } /* end if attname */

    if (is_empty) {
      ptr_indent->maybe_in_leaf = 0;
      ptr_indent->indent_current -= INDENT_INC;

      if ((new_ref = mstrcat(*ptr_buffer, "/>", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }
    else {
      if ((new_ref = mstrcat(*ptr_buffer, ">", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }
  }
  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_attribute_x() prints out an XML attribute key/value pair

  char* print_attribute_x returns ptr to the buffer if successful,
  NULL if failed

  char** ptr_buffer ptr to ptr to buffer that will receive the output

  size_t* ptr_buflen ptr to var holding size of *ptr_buffer

  const char* attname name of attribute, if any

  const char* attvalue value of attribute, if any

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_attribute_x(char** ptr_buffer, size_t* ptr_buflen, const char* attname, const char* attvalue) {
  char* new_ref;

  if (!attname || !*attname || !attvalue || !*attvalue) {
    /* nothing to do */
    return *ptr_buffer;
  }

  /* need to print a space, the attribute name, a '=', the
     quoted attribute value */
  if ((new_ref = mstrcat(*ptr_buffer, " ", ptr_buflen, 0)) == NULL) { 
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }
  if ((new_ref = mstrcat(*ptr_buffer, (char*)attname, ptr_buflen, 0)) == NULL) { 
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }
  if ((new_ref = mstrcat(*ptr_buffer, "=\"", ptr_buflen, 0)) == NULL) { 
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }
  if ((new_ref = mstrcat(*ptr_buffer, (char*)attvalue, ptr_buflen, 0)) == NULL) { 
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }
  if ((new_ref = mstrcat(*ptr_buffer, "\"", ptr_buflen, 0)) == NULL) { 
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_elend_x() prints out a risx element end tag

  static char* print_elend_x returns ptr to the buffer if successful,
  NULL if failed

  char** ptr_buffer ptr to ptr to buffer that will receive the output

  size_t* ptr_buflen ptr to var holding size of *ptr_buffer

  const char* elname name of element to print

  struct xmlindent* ptr_indent ptr to structure with indentation info

  const char* namespace namespace prefix, or NULL if no namespace prefix

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_elend_x(char** ptr_buffer, size_t* ptr_buflen, const char* elname, struct xmlindent* ptr_indent, const char* namespace) {
  char* new_ref;
  char buffer[PREFS_BUF_LEN+2];

  if (elname && *elname) {

    ptr_indent->indent_current -= INDENT_INC;

    /* safety check */
    if (ptr_indent->indent_current < 0) {
      LOG_PRINT(LOG_CRIT, "negative indentation at line: " STRINGIFY(__LINE__)
		" in " __FILE__);
      return NULL;
    }

    if (ptr_indent->maybe_in_leaf /* here now we are sure */
	|| ptr_indent->indent_toodeep
	|| ptr_indent->inline_mode) { 
      ptr_indent->maybe_in_leaf = 0;
      if ((new_ref = mstrcat(*ptr_buffer, "<", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    }
    else { /* NOT in leaf: linefeed + indent + '<' */
      unsigned indent_real =
	ptr_indent->indent_current > INDENT_MAX ? INDENT_MAX : ptr_indent->indent_current;
      if ((new_ref = mstrcat(*ptr_buffer, "\n", ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }      
      if ((new_ref = mstrcat(*ptr_buffer, & ptr_indent->indent_string[INDENT_MAX-indent_real], ptr_buflen, 0)) == NULL) {
	LOG_PRINT(LOG_WARNING, get_status_msg(801));
	return NULL;
      }
      else {
	*ptr_buffer = new_ref;
      }
    } 

    if (ptr_indent->notbelow(elname)) {
      ptr_indent->indent_toodeep = 0;
    }
    /* end of indentation + '<' code */

    if (namespace && *namespace) {
      sprintf(buffer, "/%s:", namespace);
    }
    else {
      strcpy(buffer, "/");
    }

    if ((new_ref = mstrcat(*ptr_buffer, buffer, ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }

    if ((new_ref = mstrcat(*ptr_buffer, (char*)elname, ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }
    if ((new_ref = mstrcat(*ptr_buffer, ">", ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }
  }
  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  initialize_xmlindent() initializes an xmlindent structure

  int initialize_xmlindent currently always returns 0

  struct xmlindent* ptr_xindent ptr to structure with indentation info

  int indent_current set the initial indentation level
  
  int (*notbelow)(const char*) ptr to function which limits indentation

  int (*isentry)(const char*) ptr to function which determines the
                              top-level element that starts a new entry

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int initialize_xmlindent(struct xmlindent* ptr_xindent, int indent_current, int (*notbelow)(const char*), int (*isentry)(const char*)) {
  ptr_xindent->indent_current = indent_current;
  ptr_xindent->maybe_in_leaf = 0;
  ptr_xindent->indent_toodeep = 0;
  ptr_xindent->inline_mode = 0;
  ptr_xindent->notbelow = notbelow;
  ptr_xindent->isentry = isentry;
  memset (ptr_xindent->indent_string, ' ', INDENT_MAX);
  ptr_xindent->indent_string[INDENT_MAX] = '<';
  ptr_xindent->indent_string[INDENT_MAX+1] = 0;

  return 0;
}
  
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  inline_xmlindent() toggles the inline mode of an xmlindent structure
                     the inline mode is important in mixed content
		     where whitespace matters. The indentation code
		     does not add newlines or indentation while in
		     inline mode.

  int inline_xmlindent returns the previous value of the inline_mode
                       variable

  struct xmlindent* ptr_xindent ptr to structure with indentation info

  int n_inline if 0, toggle inline mode off, if not 0, toggle on
  
 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int inline_xmlindent(struct xmlindent* ptr_xindent, int n_inline) {
  int n_prev_inline;

  n_prev_inline = ptr_xindent->inline_mode;
  ptr_xindent->inline_mode = (n_inline) ? 1:0;

  return n_prev_inline;
}
