//
// Options functions for Mini-XML, a small XML file parsing library.
//
// https://www.msweet.org/mxml
//
// Copyright © 2003-2025 by Michael R Sweet.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

#include "mxml-private.h"


//
// 'mxmlOptionsDelete()' - Free load/save options.
//

void
mxmlOptionsDelete(
    mxml_options_t *options)		// I - Options
{
  free(options);
}


//
// 'mxmlOptionsNew()' - Allocate load/save options.
//
// This function creates a new set of load/save options to use with the
// @link mxmlLoadFd@, @link mxmlLoadFile@, @link mxmlLoadFilename@,
// @link mxmlLoadIO@, @link mxmlLoadString@, @link mxmlSaveAllocString@,
// @link mxmlSaveFd@, @link mxmlSaveFile@, @link mxmlSaveFilename@,
// @link mxmlSaveIO@, and @link mxmlSaveString@ functions.  Options can be
// reused for multiple calls to these functions and should be freed using the
// @link mxmlOptionsDelete@ function.
//
// The default load/save options load values using the constant type
// `MXML_TYPE_TEXT` and save XML data with a wrap margin of 72 columns.
// The various `mxmlOptionsSet` functions are used to change the defaults,
// for example:
//
// ```
// mxml_options_t *options = mxmlOptionsNew();
//
// /* Load values as opaque strings */
// mxmlOptionsSetTypeValue(options, MXML_TYPE_OPAQUE);
// ```
//
// Note: The most common programming error when using the Mini-XML library is
// to load an XML file using the `MXML_TYPE_TEXT` node type, which returns
// inline text as a series of whitespace-delimited words, instead of using the
// `MXML_TYPE_OPAQUE` node type which returns the inline text as a single string
// (including whitespace).
//

mxml_options_t *			// O - Options
mxmlOptionsNew(void)
{
  mxml_options_t *options;		// Options


  if ((options = (mxml_options_t *)calloc(1, sizeof(mxml_options_t))) != NULL)
  {
    // Set default values...
    options->type_value = MXML_TYPE_TEXT;
    options->wrap       = 72;

    if ((options->loc = localeconv()) != NULL)
    {
      if (!options->loc->decimal_point || !strcmp(options->loc->decimal_point, "."))
	options->loc = NULL;
      else
	options->loc_declen = strlen(options->loc->decimal_point);
    }
  }

  return (options);
}


//
// 'mxmlOptionsSetCustomCallbacks()' - Set the custom data callbacks.
//
// This function sets the callbacks that are used for loading and saving custom
// data types. The load callback `load_cb` accepts the callback data pointer
// `cbdata`, a node pointer, and a data string and returns `true` on success and
// `false` on error, for example:
//
// ```c
// typedef struct
// {
//   unsigned year,    /* Year */
//            month,   /* Month */
//            day,     /* Day */
//            hour,    /* Hour */
//            minute,  /* Minute */
//            second;  /* Second */
//   time_t   unix;    /* UNIX time */
// } iso_date_time_t;
//
// void
// my_custom_free_cb(void *cbdata, void *data)
// {
//   free(data);
// }
//
// bool
// my_custom_load_cb(void *cbdata, mxml_node_t *node, const char *data)
// {
//   iso_date_time_t *dt;
//   struct tm tmdata;
//
//   /* Allocate custom data structure ... */
//   dt = calloc(1, sizeof(iso_date_time_t));
//
//   /* Parse the data string... */
//   if (sscanf(data, "%u-%u-%uT%u:%u:%uZ", &(dt->year), &(dt->month),
//              &(dt->day), &(dt->hour), &(dt->minute), &(dt->second)) != 6)
//   {
//     /* Unable to parse date and time numbers... */
//     free(dt);
//     return (false);
//   }
//
//   /* Range check values... */
//   if (dt->month < 1 || dt->month > 12 || dt->day < 1 || dt->day > 31 ||
//       dt->hour < 0 || dt->hour > 23 || dt->minute < 0 || dt->minute > 59 ||
//       dt->second < 0 || dt->second > 60)
//   {
//     /* Date information is out of range... */
//     free(dt);
//     return (false);
//   }
//
//   /* Convert ISO time to UNIX time in seconds... */
//   tmdata.tm_year = dt->year - 1900;
//   tmdata.tm_mon  = dt->month - 1;
//   tmdata.tm_day  = dt->day;
//   tmdata.tm_hour = dt->hour;
//   tmdata.tm_min  = dt->minute;
//   tmdata.tm_sec  = dt->second;
//
//   dt->unix = gmtime(&tmdata);
//
//   /* Set custom data and free function... */
//   mxmlSetCustom(node, data, my_custom_free, /*cbdata*/NULL);
//
//   /* Return with no errors... */
//   return (true);
// }
// ```
//
// The save callback `save_cb` accepts the callback data pointer `cbdata` and a
// node pointer and returns a malloc'd string on success and `NULL` on error,
// for example:
//
// ```c
// char *
// my_custom_save_cb(void *cbdata, mxml_node_t *node)
// {
//   char data[255];
//   iso_date_time_t *dt;
//
//   /* Get the custom data structure */
//   dt = (iso_date_time_t *)mxmlGetCustom(node);
//
//   /* Generate string version of the date/time... */
//   snprintf(data, sizeof(data), "%04u-%02u-%02uT%02u:%02u:%02uZ",
//            dt->year, dt->month, dt->day, dt->hour, dt->minute, dt->second);
//
//   /* Duplicate the string and return... */
//   return (strdup(data));
// }
// ```
//

void
mxmlOptionsSetCustomCallbacks(
    mxml_options_t     *options,	// I - Options
    mxml_custload_cb_t load_cb,		// I - Custom load callback function
    mxml_custsave_cb_t save_cb,		// I - Custom save callback function
    void               *cbdata)		// I - Custom callback data
{
  if (options)
  {
    options->custload_cb = load_cb;
    options->custsave_cb = save_cb;
    options->cust_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetEntityCallback()' - Set the entity lookup callback to use when loading XML data.
//
// This function sets the callback that is used to lookup named XML character
// entities when loading XML data.  The callback function `cb` accepts the
// callback data pointer `cbdata` and the entity name.  The function returns a
// Unicode character value or `-1` if the entity is not known.  For example, the
// following entity callback supports the "euro" entity:
//
// ```c
// int my_entity_cb(void *cbdata, const char *name)
// {
//   if (!strcmp(name, "euro"))
//     return (0x20ac);
//   else
//     return (-1);
// }
// ```
//
// Mini-XML automatically supports the "amp", "apos", "gt", "lt", and "quot"
// character entities which are required by the base XML specification.
//

void
mxmlOptionsSetEntityCallback(
    mxml_options_t   *options,		// I - Options
    mxml_entity_cb_t cb,		// I - Entity callback function
    void             *cbdata)		// I - Entity callback data
{
  if (options)
  {
    options->entity_cb     = cb;
    options->entity_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetErrorCallback()' - Set the error message callback.
//
// This function sets a function to use when reporting errors.  The callback
// `cb` accepts the data pointer `cbdata` and a string pointer containing the
// error message:
//
// ```c
// void my_error_cb(void *cbdata, const char *message)
// {
//   fprintf(stderr, "myprogram: %s\n", message);
// }
// ```
//
// The default error callback writes the error message to the `stderr` file.
//

void
mxmlOptionsSetErrorCallback(
    mxml_options_t  *options,		// I - Options
    mxml_error_cb_t cb,			// I - Error callback function
    void            *cbdata)		// I - Error callback data
{
  if (options)
  {
    options->error_cb     = cb;
    options->error_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetSAXCallback()' - Set the SAX callback to use when reading XML data.
//
// This function sets a SAX callback to use when reading XML data.  The SAX
// callback function `cb` and associated callback data `cbdata` are used to
// enable the Simple API for XML streaming mode.  The callback is called as the
// XML node tree is parsed and receives the `cbdata` pointer, the `mxml_node_t`
// pointer, and an event code.  The function returns `true` to continue
// processing or `false` to stop:
//
// ```c
// bool
// sax_cb(void *cbdata, mxml_node_t *node,
//        mxml_sax_event_t event)
// {
//   ... do something ...
//
//   /* Continue processing... */
//   return (true);
// }
// ```
//
// The event will be one of the following:
//
// - `MXML_SAX_EVENT_CDATA`: CDATA was just read.
// - `MXML_SAX_EVENT_COMMENT`: A comment was just read.
// - `MXML_SAX_EVENT_DATA`: Data (integer, opaque, real, or text) was just read.
// - `MXML_SAX_EVENT_DECLARATION`: A declaration was just read.
// - `MXML_SAX_EVENT_DIRECTIVE`: A processing directive/instruction was just read.
// - `MXML_SAX_EVENT_ELEMENT_CLOSE` - A close element was just read \(`</element>`)
// - `MXML_SAX_EVENT_ELEMENT_OPEN` - An open element was just read \(`<element>`)
//
// Elements are *released* after the close element is processed.  All other nodes
// are released after they are processed.  The SAX callback can *retain* the node
// using the [mxmlRetain](@@) function.
//

void
mxmlOptionsSetSAXCallback(
    mxml_options_t *options,		// I - Options
    mxml_sax_cb_t  cb,			// I - SAX callback function
    void           *cbdata)		// I - SAX callback data
{
  if (options)
  {
    options->sax_cb     = cb;
    options->sax_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetTypeCallback()' - Set the type callback for child/value nodes.
//
// The load callback function `cb` is called to obtain the node type child/value
// nodes and receives the `cbdata` pointer and the `mxml_node_t` pointer, for
// example:
//
// ```c
// mxml_type_t
// my_type_cb(void *cbdata, mxml_node_t *node)
// {
//   const char *type;
//
//  /*
//   * You can lookup attributes and/or use the element name,
//   * hierarchy, etc...
//   */
//
//   type = mxmlElementGetAttr(node, "type");
//   if (type == NULL)
//     type = mxmlGetElement(node);
//   if (type == NULL)
//     type = "text";
//
//   if (!strcmp(type, "integer"))
//     return (MXML_TYPE_INTEGER);
//   else if (!strcmp(type, "opaque"))
//     return (MXML_TYPE_OPAQUE);
//   else if (!strcmp(type, "real"))
//     return (MXML_TYPE_REAL);
//   else
//     return (MXML_TYPE_TEXT);
// }
// ```
//

void
mxmlOptionsSetTypeCallback(
    mxml_options_t *options,		// I - Options
    mxml_type_cb_t cb,			// I - Type callback function
    void           *cbdata)		// I - Type callback data
{
  if (options)
  {
    options->type_cb     = cb;
    options->type_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetTypeValue()' - Set the type to use for all child/value nodes.
//
// This functions sets a constant node type to use for all child/value nodes.
//

void
mxmlOptionsSetTypeValue(
    mxml_options_t *options,		// I - Options
    mxml_type_t    type)		// I - Value node type
{
  if (options)
  {
    options->type_cb    = NULL;
    options->type_value = type;
  }
}


//
// 'mxmlOptionsSetWhitespaceCallback()' - Set the whitespace callback.
//
// This function sets the whitespace callback that is used when saving XML data.
// The callback function `cb` specifies a function that returns a whitespace
// string or `NULL` before and after each element.  The function receives the
// callback data pointer `cbdata`, the `mxml_node_t` pointer, and a "when"
// value indicating where the whitespace is being added, for example:
//
// ```c
// const char *my_whitespace_cb(void *cbdata, mxml_node_t *node, mxml_ws_t when)
// {
//   if (when == MXML_WS_BEFORE_OPEN || when == MXML_WS_AFTER_CLOSE)
//     return ("\n");
//   else
//     return (NULL);
// }
// ```
//

void
mxmlOptionsSetWhitespaceCallback(
    mxml_options_t *options,		// I - Options
    mxml_ws_cb_t   cb,			// I - Whitespace callback function
    void           *cbdata)		// I - Whitespace callback data
{
  if (options)
  {
    options->ws_cb     = cb;
    options->ws_cbdata = cbdata;
  }
}


//
// 'mxmlOptionsSetWrapMargin()' - Set the wrap margin when saving XML data.
//
// This function sets the wrap margin used when saving XML data.  Wrapping is
// disabled when `column` is `0`.
//

void
mxmlOptionsSetWrapMargin(
    mxml_options_t *options,		// I - Options
    int            column)		// I - Wrap column
{
  if (options)
    options->wrap = column;
}


//
// '_mxml_entity_string()' - Get the entity that corresponds to the character, if any.
//

const char *				// O - Entity or `NULL` for none
_mxml_entity_string(int ch)		// I - Character
{
  switch (ch)
  {
    case '&' :
        return ("&amp;");

    case '\'' :
        return ("&apos;");

    case '<' :
        return ("&lt;");

    case '>' :
        return ("&gt;");

    case '\"' :
        return ("&quot;");

    default :
        return (NULL);
  }
}


//
// '_mxml_entity_value()' - Get the character corresponding to a named entity.
//
// The entity name can also be a numeric constant. `-1` is returned if the
// name is not known.
//

int					// O - Unicode character
_mxml_entity_value(
    mxml_options_t *options,		// I - Options
    const char     *name)		// I - Entity name
{
  int		ch = -1;		// Unicode character


  if (!name)
  {
    // No name...
    return (-1);
  }
  else if (*name == '#')
  {
    // Numeric entity...
    if (name[1] == 'x')
      ch = (int)strtol(name + 2, NULL, 16);
    else
      ch = (int)strtol(name + 1, NULL, 10);
  }
  else if (!strcmp(name, "amp"))
  {
    // Ampersand
    ch = '&';
  }
  else if (!strcmp(name, "apos"))
  {
    // Apostrophe
    ch = '\'';
  }
  else if (!strcmp(name, "gt"))
  {
    // Greater than
    ch = '>';
  }
  else if (!strcmp(name, "lt"))
  {
    // Less than
    ch = '<';
  }
  else if (!strcmp(name, "quot"))
  {
    // Double quote
    ch = '\"';
  }
  else if (options && options->entity_cb)
  {
    // Use callback
    ch = (options->entity_cb)(options->entity_cbdata, name);
  }

  return (ch);
}


//
// '_mxml_error()' - Display an error message.
//

void
_mxml_error(mxml_options_t *options,	// I - Load/save options
            const char     *format,	// I - Printf-style format string
            ...)			// I - Additional arguments as needed
{
  va_list	ap;			// Pointer to arguments
  char		s[1024];		// Message string


  // Range check input...
  if (!format)
    return;

  // Format the error message string...
  va_start(ap, format);
  vsnprintf(s, sizeof(s), format, ap);
  va_end(ap);

  // And then display the error message...
  if (options && options->error_cb)
    (options->error_cb)(options->error_cbdata, s);
  else
    fprintf(stderr, "%s\n", s);
}
