/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * gsf-libxml.c :
 *
 * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include <gsf-config.h>
#include <gsf/gsf-libxml.h>
#include <gsf/gsf-input.h>
#include <gsf/gsf-output.h>
#include <gsf/gsf-input-gzip.h>
#include <gsf/gsf-impl-utils.h>
#include <gsf/gsf-utils.h>
#include <gsf/gsf-timestamp.h>

#include <math.h>
#include <string.h>

static GObjectClass *parent_class;

static gint
glade_enum_from_string (GType type, const char *string)
{
    GEnumClass *eclass;
    GEnumValue *ev;
    gchar *endptr;
    gint ret = 0;

    ret = strtoul(string, &endptr, 0);
    if (endptr != string) /* parsed a number */
	return ret;

    eclass = g_type_class_ref(type);
    ev = g_enum_get_value_by_name(eclass, string);
    if (!ev) ev = g_enum_get_value_by_nick(eclass, string);
    if (ev)  ret = ev->value;

    g_type_class_unref(eclass);

    return ret;
}

static char const *
glade_string_from_enum (GType type, gint value)
{
    GEnumClass *eclass;
    GEnumValue *ev;

    eclass = g_type_class_ref(type);

    ev = g_enum_get_value (eclass, value);

    g_type_class_unref(eclass);

    return ev ? ev->value_name : "0";
}

static guint
glade_flags_from_string (GType type, const char *string)
{
    GFlagsClass *fclass;
    gchar *endptr, *prevptr;
    guint i, j, ret = 0;
    char *flagstr;

    ret = strtoul(string, &endptr, 0);
    if (endptr != string) /* parsed a number */
	return ret;

    fclass = g_type_class_ref(type);


    flagstr = g_strdup (string);
    for (ret = i = j = 0; ; i++) {
	gboolean eos;

	eos = flagstr [i] == '\0';
	
	if (eos || flagstr [i] == '|') {
	    GFlagsValue *fv;
	    const char  *flag;
	    gunichar ch;

	    flag = &flagstr [j];
            endptr = &flagstr [i];

	    if (!eos) {
		flagstr [i++] = '\0';
		j = i;
	    }

            /* trim spaces */
	    for (;;)
	      {
		ch = g_utf8_get_char (flag);
		if (!g_unichar_isspace (ch))
		  break;
		flag = g_utf8_next_char (flag);
	      }

	    while (endptr > flag)
	      {
		prevptr = g_utf8_prev_char (endptr);
		ch = g_utf8_get_char (prevptr);
		if (!g_unichar_isspace (ch))
		  break;
		endptr = prevptr;
	      }

	    if (endptr > flag)
	      {
		*endptr = '\0';
		fv = g_flags_get_value_by_name (fclass, flag);

		if (!fv)
		  fv = g_flags_get_value_by_nick (fclass, flag);

		if (fv)
		  ret |= fv->value;
		else
		  g_warning ("Unknown flag: '%s'", flag);
	      }

	    if (eos)
		break;
	}
    }
    
    g_free (flagstr);

    g_type_class_unref(fclass);

    return ret;
}
static gchar *
glade_string_from_flags (GType type, guint flags)
{
    GFlagsClass *flags_class;
    GString *string;
    char *ret;

    flags_class = g_type_class_ref (type);

    string = g_string_new ("");

    if (flags_class->n_values)
      {
	GFlagsValue *fval;
      
	for (fval = flags_class->values; fval->value_name; fval++)
	  {
	    /* We have to be careful as some flags include 0 values, e.g.
	       BonoboDockItemBehavior uses 0 for BONOBO_DOCK_ITEM_BEH_NORMAL.
	       If a 0 value is available, we only output it if the entire
	       flags value is 0, otherwise we check if the bit-flag is set. */
	    if ((fval->value == 0 && flags == 0)
		|| (fval->value && (fval->value & flags) == fval->value))
	      {
		if (string->len)
		  g_string_append_c (string, '|');
		g_string_append (string, fval->value_name);
	      }
	  }
      }

    ret = string->str;
    g_string_free (string, FALSE);

    g_type_class_unref (flags_class);

    return ret;
}

/**
 * gsf_xml_gvalue_from_str :
 * @res : Result value
 * @t : Type of data
 * @str : Value string
 *
 * Try to parse @str as a value of type @t into @res.
 *
 * Returns: True when parsing of @str as a value of type @t was succesfull;
 * false otherwise.
 */
gboolean
gsf_xml_gvalue_from_str (GValue *res, GType t, char const *str)
{
	g_return_val_if_fail (res != NULL, FALSE);
	g_return_val_if_fail (str != NULL, FALSE);

	g_value_init (res, t);

	/* If the passed-in type is derived from G_TYPE_ENUM
	 * or G_TYPE_FLAGS, we cannot switch on its type
	 * because we don't know its GType at compile time.
	 * We just pretend to have a G_TYPE_ENUM/G_TYPE_FLAGS.
	 */
	if (G_TYPE_FUNDAMENTAL (t) == G_TYPE_ENUM ||
	    G_TYPE_FUNDAMENTAL (t) == G_TYPE_FLAGS) {
		t = G_TYPE_FUNDAMENTAL (t);
	}

	switch (t) {
	case G_TYPE_CHAR:
		g_value_set_char (res, str[0]);
		break;
	case G_TYPE_UCHAR:
		g_value_set_uchar (res, (guchar)str[0]);
		break;
	case G_TYPE_BOOLEAN:
		g_value_set_boolean (res, 
			g_ascii_tolower (str[0]) == 't' ||
			g_ascii_tolower (str[0]) == 'y' ||
			strtol (str, NULL, 0));
		break;
	case G_TYPE_INT:
		g_value_set_int (res, strtol (str, NULL, 0));
		break;
	case G_TYPE_UINT:
		g_value_set_uint (res, strtoul (str, NULL, 0));
		break;
	case G_TYPE_LONG:
		g_value_set_long (res, strtol (str, NULL, 0));
		break;
	case G_TYPE_ULONG:
		g_value_set_ulong (res, strtoul (str, NULL, 0));
		break; 
	case G_TYPE_ENUM:
		g_value_set_enum (res, glade_enum_from_string (G_VALUE_TYPE (res), str));
		break;
	case G_TYPE_FLAGS:
		g_value_set_flags (res, glade_flags_from_string (G_VALUE_TYPE (res), str));
		break;
	case G_TYPE_FLOAT:
		g_value_set_float (res, g_strtod (str, NULL));
		break;
	case G_TYPE_DOUBLE:
		g_value_set_double (res, g_strtod (str, NULL));
		break;
	case G_TYPE_STRING:
		g_value_set_string (res, str);
		break;

	default:
		if (GSF_TIMESTAMP_TYPE == t) {
			GsfTimestamp ts;
			if (gsf_timestamp_parse (str, &ts)) {
				gsf_value_set_timestamp (res, &ts);
				break;
			}
		} else g_warning ("gsf_xml_gvalue_from_str(): Don't know how to handle type '%s'", g_type_name (t));

		return FALSE;
	}
	return TRUE;
}

/* Note: libxml erroneously declares the length argument as int.  */
static int
gsf_libxml_read (void *context, guint8 *buffer, int len)
{
	gsf_off_t remaining = gsf_input_remaining ((GsfInput *)context);
	guint8* res;

	if (len > remaining)
		len = remaining;
	res = (guint8 *) gsf_input_read ((GsfInput *)context,
					 (size_t)len, buffer);
	if (res == NULL && len > 0) /* Not an error if len == 0 */
		return -1;
	return len;
}

static int
gsf_libxml_write (void *context, char const *buffer, int len)
{
	if (!gsf_output_write ((GsfOutput *)context, (size_t)len, buffer))
		return -1;
	return len;
}

static int
gsf_libxml_close (void *context)
{
	g_object_unref (G_OBJECT (context));
	return TRUE;
}

static xmlParserCtxtPtr
gsf_xml_parser_context_full (GsfInput *input, xmlSAXHandlerPtr sax, gpointer user)
{
	GsfInput *gzip;
	xmlParserCtxtPtr res;

	g_return_val_if_fail (GSF_IS_INPUT (input), NULL);

	gzip = gsf_input_gzip_new (input, NULL);
	if (gzip != NULL)
		input = gzip;
	else
		g_object_ref (G_OBJECT (input));

	res = xmlCreateIOParserCtxt (
		sax, user,
		(xmlInputReadCallback) gsf_libxml_read, 
		(xmlInputCloseCallback) gsf_libxml_close,
		input, XML_CHAR_ENCODING_NONE);

	if (res)
		res->replaceEntities = TRUE;
	else
		g_object_unref (input);

	return res;
}

/**
 * gsf_xml_parser_context :
 * @input : #GsfInput
 *
 * Create a libxml2 pull style parser context wrapper around gsf input @input.
 * This signature will probably change to supply a SAX structure.
 *
 * <note>This adds a reference to @input.</note>
 * <note>A simple wrapper around a cleaner implementation that will fold in
 * when we add other api changes.  Its not worth bumping just for this.</note>
 *
 * Returns: A parser context or %NULL
 **/
xmlParserCtxtPtr
gsf_xml_parser_context (GsfInput *input)
{
	return gsf_xml_parser_context_full (input, NULL, NULL);
}

/**
 * gsf_xml_output_buffer_new :
 * @output:
 * @encoding: optionally %NULL.
 *
 * <note>This adds a reference to @output.</note>
 * <note>This is <emphasis>not</emphasis> releated to #GsfXMLOut.</note>
 */
static xmlOutputBufferPtr
gsf_xml_output_buffer_new (GsfOutput *output,
			   xmlCharEncodingHandlerPtr handler)
{
	xmlOutputBufferPtr res = xmlAllocOutputBuffer (handler);
	if (res != NULL) {
		g_object_ref (G_OBJECT (output));
		res->context = (void *)output;
		res->writecallback = gsf_libxml_write;
		res->closecallback = gsf_libxml_close;
	}

	return res;
}

int
gsf_xmlDocFormatDump (GsfOutput *output, xmlDocPtr cur, char const *encoding,
		      gboolean format)
{
	xmlOutputBufferPtr buf;
	xmlCharEncodingHandlerPtr handler = NULL;

	if (cur == NULL) {
#ifdef DEBUG_TREE
		xmlGenericError(xmlGenericErrorContext,
				"xmlDocDump : document == NULL\n");
#endif
		return(-1);
	}

	if (encoding != NULL) {
		xmlCharEncoding enc;

		enc = xmlParseCharEncoding(encoding);

		if (cur->charset != XML_CHAR_ENCODING_UTF8) {
			xmlGenericError(xmlGenericErrorContext,
					"xmlDocDump: document not in UTF8\n");
			return(-1);
		}
		if (enc != XML_CHAR_ENCODING_UTF8) {
			handler = xmlFindCharEncodingHandler(encoding);
			if (handler == NULL) {
				xmlFree((char *) cur->encoding);
				cur->encoding = NULL;
			}
		}
	}
	buf = gsf_xml_output_buffer_new (output, handler);
	return xmlSaveFormatFileTo (buf, cur, encoding, format);
}

/***************************************************************************/

typedef struct {
	GsfXMLInNode pub;
	/* internal state */
	GSList *groups;
	GSList *extensions;
} GsfXMLInNodeInternal;
struct _GsfXMLInDoc {
	GsfXMLInNodeInternal const *root_node;
	GHashTable      *symbols; /* GsfXMLInNodeInternal hashed by id */
	GsfXMLInNS const*ns;
	GsfXMLInUnknownFunc	unknown_handler;
};
typedef struct {
	GsfXMLIn	pub;
	GsfInput	*input;	/* TODO : Move to pub for 1.16.0 */

	int		  default_ns_id;   /* <0 => no default ns */
	GSList	 	 *ns_stack;
	GHashTable	 *ns_prefixes;
	GPtrArray	 *ns_by_id;
	GSList	 	 *contents_stack;
	gboolean          initialized;
	gint	  	  unknown_depth; /* handle recursive unknown tags */
	gboolean	  from_unknown_handler;

	GSList	 	 *extension_stack; /* stack of GsfXMLInExtension */
} GsfXMLInInternal;
typedef struct {
	char    *tag;
	unsigned taglen;
	unsigned ref_count;
} GsfXMLInNSInstance;
typedef struct {
	int	ns_id;
	GSList *elem;
} GsfXMLInNodeGroup;
typedef struct {
	GsfXMLInExtDtor	   dtor;
	gpointer	   state;
	GsfXMLInDoc const *doc;
	gboolean	   from_unknown;
} GsfXMLInExtension;

static char const *
node_name (GsfXMLInNode const *node)
{
	return (node->name != NULL) ? node->name : "{catch all)}";
}

static void
push_child (GsfXMLInInternal *state, GsfXMLInNode const *node, int default_ns_id,
	    xmlChar const **attrs, GsfXMLInExtension *ext)
{
	if (node->has_content == GSF_XML_CONTENT) {
		if (state->pub.content->len) {
			state->contents_stack =	g_slist_prepend
				(state->contents_stack, state->pub.content);
			state->pub.content = g_string_sized_new (128);
		} else {
			state->contents_stack = g_slist_prepend
				(state->contents_stack, NULL);
		}
	}
	state->pub.node_stack	= g_slist_prepend (state->pub.node_stack,
		(gpointer)state->pub.node);
	state->ns_stack		= g_slist_prepend (state->ns_stack,
		GINT_TO_POINTER (state->default_ns_id));
	state->pub.node = node;
	state->default_ns_id = default_ns_id;

	state->extension_stack	= g_slist_prepend (state->extension_stack, ext);
	if (NULL != ext) {
		GsfXMLInDoc const *old_doc = state->pub.doc;
		state->pub.doc = ext->doc;
		ext->doc = old_doc;
		if (NULL != ext->state) {
			gpointer old_state = state->pub.user_state;
			state->pub.user_state = ext->state;
			ext->state = old_state;
		}
	}
	if (NULL != node->start)
		node->start (&state->pub, attrs);
}

static gboolean
lookup_child (GsfXMLInInternal *state, int default_ns_id,
	      GSList *groups, xmlChar const *name,
	      xmlChar const **attrs, GsfXMLInExtension *ext)
{
	GsfXMLInNodeGroup  *group;
	GsfXMLInNode	   *node;
	GsfXMLInNSInstance *inst;
	GSList *elem, *ptr;
	char const *tmp;

	for (ptr = groups ; ptr != NULL ; ptr = ptr->next) {
		group = ptr->data;
		/* does the namespace match */
		if (group->ns_id >= 0 && group->ns_id != default_ns_id) {

			if ((int)state->ns_by_id->len <= group->ns_id)
				continue;

			inst = g_ptr_array_index (state->ns_by_id, group->ns_id);
			if (inst == NULL || 0 != strncmp (name, inst->tag, inst->taglen))
				continue;
			tmp = name + inst->taglen;
		} else {
#if 0
			g_return_val_if_fail ((int)state->ns_by_id->len > group->ns_id, FALSE);
			inst = g_ptr_array_index (state->ns_by_id, group->ns_id);
			g_warning ("accepted ns = '%s' looking for '%s'", inst->tag, name);
#endif
			tmp = name;
		}
		for (elem = group->elem ; elem != NULL ; elem = elem->next) {
			node = elem->data;
			if (node->name == NULL || !strcmp (tmp, node->name)) {
				push_child (state, node, default_ns_id, attrs, ext);
				return TRUE;
			}
		}
	}
	return FALSE;
}

static void
gsf_xml_in_start_element (GsfXMLInInternal *state, xmlChar const *name, xmlChar const **attrs)
{
	GsfXMLInNSInstance *inst;
	GsfXMLInNS const   *ns;
	int default_ns_id = state->default_ns_id;
	GsfXMLInNodeInternal const *node;
	xmlChar const **ns_ptr;
	GSList *ptr;
	char const *tmp;
	int i;

	/* Scan for namespace declarations.  Yes it is ugly to have the api
	 * flag that its children can declare namespaces. However, given that a
	 * we need to know which namespace we are in before we can recognize
	 * the current node there is no choice.
	 * eg <gnm:Workbook xmlns:gnm="www.gnumeric.org"/> we can not know
	 * that we are in node 'Workbook' without recognizing ns=gnm, which we
	 * would not do unless we checked for a namespace */
	ns = state->pub.doc->ns;
	if (ns != NULL && state->pub.node->check_children_for_ns) {
		for (ns_ptr = attrs; ns_ptr != NULL && ns_ptr[0] && ns_ptr[1] ; ns_ptr += 2) {
			if (strncmp (*ns_ptr, "xmlns", 5))
				continue;
			if (ns_ptr[0][5] != '\0' && ns_ptr[0][5] != ':')
				continue;
			for (i = 0; (tmp = ns[i].uri) != NULL ; i++) {
				if (strcmp (tmp, ns_ptr[1]))
					continue;

				if (ns_ptr[0][5] == '\0') {
					default_ns_id = ns[i].ns_id;
					break;
				}

				inst = g_hash_table_lookup (state->ns_prefixes, ns_ptr[0] + 6);
				if (inst == NULL) {
					inst = g_new0 (GsfXMLInNSInstance, 1);
					inst->tag    = g_strconcat (ns_ptr[0] + 6, ":", NULL);
					inst->taglen = strlen (inst->tag);
					inst->ref_count = 1;
					g_hash_table_insert (state->ns_prefixes, g_strdup (ns_ptr[0] + 6), inst);

					if (ns[i].ns_id >= state->ns_by_id->len)
						g_ptr_array_set_size  (state->ns_by_id, ns[i].ns_id+1);
					if (g_ptr_array_index (state->ns_by_id, ns[i].ns_id)) {
						g_warning ("Damn.  Someone just declared the same namespace '%s' with a different prefix '%s'",
							   ns[i].uri, inst->tag);
					} else
						g_ptr_array_index (state->ns_by_id, ns[i].ns_id) = inst;
				} else
					inst->ref_count++;
				break;
			}

			if (NULL == tmp) {
				g_warning ("Unknown namespace uri = '%s'", ns_ptr[1]);
			}
		}
	}

	node = (GsfXMLInNodeInternal const *) state->pub.node;
	if (lookup_child (state, default_ns_id, node->groups, name, attrs, NULL))
		return;

	/* useful for <Data><b><i><u></u></i></b></Data> where all of the markup can nest */
	ptr = state->pub.node_stack;
	for (; ptr != NULL && node->pub.share_children_with_parent; ptr = ptr->next) {
		node = ptr->data;
		if (lookup_child (state, default_ns_id, node->groups, name, attrs, NULL))
			return;
	}

	/* Check for extensions */
	for (ptr = node->extensions; ptr != NULL ; ptr = ptr->next) {
		GsfXMLInExtension *ext = ptr->data;
		if (lookup_child (state, default_ns_id,
			ext->doc->root_node->groups, name, attrs, ext))
			return;
	}

	if (state->pub.doc->unknown_handler != NULL) {
		gboolean handled;
		state->from_unknown_handler = TRUE;
		handled = (state->pub.doc->unknown_handler) (&state->pub, name, attrs);
		state->from_unknown_handler = FALSE;
		if (handled)
			return;
	}

	if (state->unknown_depth++ > 0)
		return;

	g_print ("Unexpected element '%s' in state : \n\t", name);
	ptr = state->pub.node_stack = g_slist_reverse (state->pub.node_stack);
	if (ptr != NULL)	/* skip toplevel catch all */
		ptr = ptr->next;
	for (;ptr != NULL && ptr != NULL; ptr = ptr->next) {
		node = ptr->data;
		if (node != NULL) {
/* FIXME FIXME FIXME if we really want this do we also want namespaces ? */
			g_print ("%s -> ", node_name (&node->pub));
		}
	}
	if (state->pub.node != NULL) {
		g_print ("%s\n", node_name (state->pub.node));
	}
	state->pub.node_stack = g_slist_reverse (state->pub.node_stack);
}

static void
gsf_xml_in_ext_free (GsfXMLInInternal *state, GsfXMLInExtension *ext)
{
	if (ext->dtor)
		(ext->dtor) (&state->pub, ext->state);
	g_free (ext);
}

static void
gsf_xml_in_end_element (GsfXMLInInternal *state,
			G_GNUC_UNUSED xmlChar const *name)
{
	GsfXMLInNodeInternal	*node;
	GsfXMLInExtension	*ext;
	GSList *ptr;

	if (state->unknown_depth > 0) {
		state->unknown_depth--;
		return;
	}

	g_return_if_fail (state->pub.node	!= NULL);
	g_return_if_fail (state->pub.node_stack != NULL);
	g_return_if_fail (state->ns_stack != NULL);

	node = (GsfXMLInNodeInternal *) state->pub.node;
	if (node->pub.end)
		node->pub.end (&state->pub, NULL);

	if (node->pub.has_content == GSF_XML_CONTENT) {
		GString *top;

		g_return_if_fail (state->contents_stack != NULL);
		top = state->contents_stack->data;
		state->contents_stack = g_slist_remove
			(state->contents_stack, top);

		if (top) {
			g_string_free (state->pub.content, TRUE);
			state->pub.content = top;
		} else {
			g_string_truncate (state->pub.content, 0);
		}
	}

	/* Free any potential extensions associated with the current node */
	for (ptr = node->extensions; ptr != NULL ; ptr = ptr->next)
		gsf_xml_in_ext_free (state, ptr->data);
	g_slist_free (node->extensions);
	node->extensions = NULL;

	/* pop the state stack */
	ext = state->extension_stack->data;
	state->extension_stack	= g_slist_remove (state->extension_stack, ext);
	state->pub.node		= state->pub.node_stack->data;
	state->pub.node_stack	= g_slist_remove (state->pub.node_stack, state->pub.node);
	state->default_ns_id	= GPOINTER_TO_INT (state->ns_stack->data);
	state->ns_stack		= g_slist_remove (state->ns_stack, GINT_TO_POINTER (state->default_ns_id));

	if (NULL != ext) {
		GsfXMLInDoc const *ext_doc = state->pub.doc;
		state->pub.doc = ext->doc;
		ext->doc = ext_doc;
		if (NULL != ext->state) {
			gpointer ext_state = state->pub.user_state;
			state->pub.user_state = ext->state;
			ext->state = ext_state;
		}
		if (ext->from_unknown)
			gsf_xml_in_ext_free (state, ext);
	}
}

static void
gsf_xml_in_characters (GsfXMLInInternal *state, xmlChar const *chars, int len)
{
	if (state->pub.node->has_content != GSF_XML_NO_CONTENT)
		g_string_append_len (state->pub.content, chars, len);
}

static xmlEntityPtr
gsf_xml_in_get_entity (G_GNUC_UNUSED GsfXMLInInternal *state, xmlChar const *name)
{
	return xmlGetPredefinedEntity (name);
}

static void
gsf_free_xmlinnsinstance (GsfXMLInNSInstance *inst)
{
	g_free (inst->tag);
	g_free (inst);
}

static void
gsf_xml_in_start_document (GsfXMLInInternal *state)
{
	/*
	 * This function will not be called when parsing an empty
	 * document.
	 */
	state->initialized	= TRUE;
	state->pub.node = &state->pub.doc->root_node->pub;
	state->unknown_depth	= 0;
	state->pub.node_stack	= NULL;
	state->extension_stack	= NULL;
	state->ns_stack		= NULL;
	state->default_ns_id	= -1;
	state->ns_by_id		= g_ptr_array_new ();
	state->ns_prefixes	= g_hash_table_new_full (
		g_str_hash, g_str_equal,
		g_free, (GDestroyNotify) gsf_free_xmlinnsinstance);
	state->contents_stack	= NULL;
	state->from_unknown_handler = FALSE;
}

static void
gsf_xml_in_end_document (GsfXMLInInternal *state)
{
	g_string_free (state->pub.content, TRUE);
	state->pub.content = NULL;

	if (state->initialized) {
		g_ptr_array_free (state->ns_by_id, TRUE);
		state->ns_by_id = NULL;

		g_hash_table_destroy (state->ns_prefixes);
		state->ns_prefixes = NULL;

		g_return_if_fail (state->pub.node == &state->pub.doc->root_node->pub);
		g_return_if_fail (state->unknown_depth == 0);
	}
}

static void
gsf_xml_in_warning (G_GNUC_UNUSED GsfXMLInInternal *state, char const *msg, ...)
{
	va_list args;

	va_start (args, msg);
	g_logv ("XML", G_LOG_LEVEL_WARNING, msg, args);
	va_end (args);
}

static void
gsf_xml_in_error (G_GNUC_UNUSED GsfXMLInInternal *state, char const *msg, ...)
{
	va_list args;

	va_start (args, msg);
	g_logv ("XML", G_LOG_LEVEL_CRITICAL, msg, args);
	va_end (args);
}

static void
gsf_xml_in_fatal_error (G_GNUC_UNUSED GsfXMLInInternal *state, char const *msg, ...)
{
	va_list args;

	va_start (args, msg);
	g_logv ("XML", G_LOG_LEVEL_ERROR, msg, args);
	va_end (args);
}

static xmlSAXHandler gsfXMLInParser = {
	NULL, /* internalSubset */
	NULL, /* isStandalone */
	NULL, /* hasInternalSubset */
	NULL, /* hasExternalSubset */
	NULL, /* resolveEntity */
	(getEntitySAXFunc)gsf_xml_in_get_entity, /* getEntity */
	NULL, /* entityDecl */
	NULL, /* notationDecl */
	NULL, /* attributeDecl */
	NULL, /* elementDecl */
	NULL, /* unparsedEntityDecl */
	NULL, /* setDocumentLocator */
	(startDocumentSAXFunc)gsf_xml_in_start_document, /* startDocument */
	(endDocumentSAXFunc)gsf_xml_in_end_document, /* endDocument */
	(startElementSAXFunc)gsf_xml_in_start_element, /* startElement */
	(endElementSAXFunc)gsf_xml_in_end_element, /* endElement */
	NULL, /* reference */
	(charactersSAXFunc)gsf_xml_in_characters, /* characters */
	NULL, /* ignorableWhitespace */
	NULL, /* processingInstruction */
	NULL, /* comment */
	(warningSAXFunc)gsf_xml_in_warning, /* warning */
	(errorSAXFunc)gsf_xml_in_error, /* error */
	(fatalErrorSAXFunc)gsf_xml_in_fatal_error, /* fatalError */
	NULL, /* getParameterEntity */
	NULL, /* cdataBlock */
	NULL, /* externalSubset */
	0
#if LIBXML_VERSION >= 20600
	,
	NULL, NULL, NULL
#if LIBXML_VERSION >= 20602
	,
	NULL /* serror */
#endif
#endif
};

static void
gsf_xml_in_node_internal_free (GsfXMLInNodeInternal *node)
{
	GSList *ptr;
	GsfXMLInNodeGroup *group;

	if (NULL != node->extensions) {
		g_warning ("leaking extensions");
	}

	for (ptr = node->groups; ptr != NULL ; ptr = ptr->next) {
		group = ptr->data;
		g_slist_free (group->elem);
		g_free (group);
	}
	g_slist_free (node->groups);
	node->groups = NULL;
	g_free (node);
}

/**
 * gsf_xml_in_doc_free :
 * @doc : #GsfXMLInDoc
 *
 * Free up resources
 **/
void
gsf_xml_in_doc_free (GsfXMLInDoc *doc)
{
	g_return_if_fail (doc != NULL);
	g_return_if_fail (doc->symbols != NULL);

	g_hash_table_destroy (doc->symbols);

	/* poison the well just in case */
	doc->symbols   = NULL;
	doc->root_node = NULL;
	g_free (doc);
}

/**
 * gsf_xml_in_doc_new :
 * @nodes : an array of node descriptors
 * @ns : an array of namespace identifiers
 *
 * Combine the nodes in the %NULL terminated array starting at @nodes with the
 * name spaces in the %NULL terminated array starting at @ns.  Prepare the
 * data structures necessary to validate a doument based on that description.
 *
 * Returns: %NULL on error
 **/
GsfXMLInDoc *
gsf_xml_in_doc_new (GsfXMLInNode const *nodes, GsfXMLInNS const *ns)
{
	GsfXMLInDoc  *doc;
	GsfXMLInNode const *e_node;
	GsfXMLInNodeInternal *tmp, *node;

	g_return_val_if_fail (nodes != NULL, NULL);

	doc = g_new0 (GsfXMLInDoc, 1);
	doc->root_node = NULL;
	doc->symbols   = g_hash_table_new_full (g_str_hash, g_str_equal,
		NULL, (GDestroyNotify) gsf_xml_in_node_internal_free);
	doc->ns        = ns;

	for (e_node = nodes; e_node->id != NULL ; e_node++ ) {
		node = g_hash_table_lookup (doc->symbols, e_node->id);
		if (node != NULL) {
			/* if its empty then this is just a recusion */
			if (e_node->start != NULL || e_node->end != NULL ||
			    e_node->has_content != GSF_XML_NO_CONTENT ||
			    e_node->user_data.v_int != 0) {
				g_warning ("ID '%s' has already been registered.\n"
					   "The additional decls should not specify start,end,content,data", e_node->id);
				continue;
			}
		} else {
			node = g_new0 (GsfXMLInNodeInternal, 1);
			node->pub = *e_node;
			/* WARNING VILE HACK :
			 * The api in 1.8.2 passed has_content as a boolean.
			 * Too many things still use that to change yet.  We
			 * edit the bool here to be GSF_CONTENT_NONE or
			 * GSF_XML_CONTENT and try to ignore SHARED_CONTENT */
			if (node->pub.has_content != 0 &&
			    node->pub.has_content != GSF_XML_SHARED_CONTENT)
				node->pub.has_content = GSF_XML_CONTENT;
			node->groups = NULL;
			g_hash_table_insert (doc->symbols,
				(gpointer)node->pub.id, node);
		}
		if (e_node == nodes) /* first valid node is the root */
			doc->root_node = node;

		/* NOTE : use e_node for parent_id rather than node
		 *        in case this is a shared symbol */
		tmp = g_hash_table_lookup (doc->symbols, e_node->parent_id);
		if (tmp != NULL) {
			GSList *ptr;
			GsfXMLInNodeGroup *group = NULL;
			int const ns_id = node->pub.ns_id;
			for (ptr = tmp->groups; ptr != NULL ; ptr = ptr->next) {
				group = ptr->data;
				if (group->ns_id == ns_id)
					break;
			}
			if (ptr == NULL) {
				group = g_new0 (GsfXMLInNodeGroup, 1);
				group->ns_id = ns_id;
				tmp->groups = g_slist_prepend (tmp->groups, group);
			}
			group->elem = g_slist_prepend (group->elem, node);
		} else if (strcmp (e_node->id, e_node->parent_id)) {
			g_warning ("Parent ID '%s' unknown", e_node->parent_id);
			continue;
		}
	}

	return doc;
}

/**
 * gsf_xml_in_doc_set_unknown_handler:
 * @doc : #GsfXMLInDoc
 * @handler : The function to call
 *
 * Call the function @handler when an unexpected child node is found
 **/
void
gsf_xml_in_doc_set_unknown_handler (GsfXMLInDoc *doc,
				    GsfXMLInUnknownFunc handler)
{
	g_return_if_fail (doc != NULL);
	doc->unknown_handler = handler;
}

/**
 * gsf_xml_in_push_state :
 * @xin : #GsfXMLIn
 * @doc : #GsfXMLInDoc
 * @new_state :
 * @dtor : #GsfXMLInExtDtor
 * @attrs : array of xmlChar const *
 *
 * Take the first node from @doc as the current node and call it's start handler.
 **/
void
gsf_xml_in_push_state (GsfXMLIn *xin, GsfXMLInDoc const *doc,
		       gpointer new_state, GsfXMLInExtDtor dtor,
		       xmlChar const **attrs)
{
	GsfXMLInInternal *state = (GsfXMLInInternal *)xin;
	GsfXMLInExtension *ext;
	
	g_return_if_fail (xin != NULL);
	g_return_if_fail (doc != NULL);
	g_return_if_fail (doc->root_node != NULL);

	ext = g_new (GsfXMLInExtension, 1);
	ext->doc	  = doc;
	ext->state	  = new_state;
	ext->dtor	  = dtor;
	if (!(ext->from_unknown = state->from_unknown_handler)) {
		GsfXMLInNodeInternal *node = (GsfXMLInNodeInternal *) xin->node;
		node->extensions = g_slist_prepend (node->extensions, ext);
	} else
		push_child (state, &doc->root_node->pub, -1, attrs, ext);
}

/**
 * gsf_xml_in_doc_parse :
 * @doc : #GsfXMLInDoc
 * @input : #GsfInput
 * @user_state :
 *
 * Read an xml document from @input and parse based on the the descriptor in
 * @doc
 *
 * Returns: %FALSE on error
 **/
gboolean
gsf_xml_in_doc_parse (GsfXMLInDoc *doc, GsfInput *input, gpointer user_state)
{
	xmlParserCtxt	*ctxt;
	GsfXMLInInternal state;
	gboolean res;

	g_return_val_if_fail (doc != NULL, FALSE);

	state.initialized = FALSE;
	ctxt = gsf_xml_parser_context_full (input, &gsfXMLInParser, &state);
	if (ctxt == NULL)
		return FALSE;

	state.pub.doc = doc;
	state.pub.user_state = user_state;
	state.pub.content = g_string_sized_new (128);
	state.input = input;
	xmlParseDocument (ctxt);
	res = ctxt->wellFormed;
	xmlFreeParserCtxt (ctxt);

	return res;
}

/**
 * gsf_xml_in_get_input :
 * @xin : #GsfXMLIn
 *
 * (New in 1.14.2)
 *
 * Returns: (but does not reference) the stream being parsed.
 **/
GsfInput *
gsf_xml_in_get_input (GsfXMLIn const *xin)
{
	GsfXMLInInternal const *state = (GsfXMLInInternal const *)xin;
	return state->input;
}

/**
 * gsf_xml_in_check_ns :
 * @xin : #GsfXMLIn
 * @str :
 * @ns_id :
 * 
 * According to @state is @str in the namespace @ns_id ?
 *
 * Returns: a pointer to @str after the namespace if successful,
 * 	otherwise NULL.
 **/
char const *
gsf_xml_in_check_ns (GsfXMLIn const *xin, char const *str, unsigned int ns_id)
{
	GsfXMLInInternal const *state = (GsfXMLInInternal const *)xin;
	GsfXMLInNSInstance *inst;

	/* If this is the default namespace there will not be an entry in the
	 * ns_by_id array */
	if (ns_id >= state->ns_by_id->len ||
	    NULL == (inst = g_ptr_array_index (state->ns_by_id, ns_id)) ||
	    0 != strncmp (str, inst->tag, inst->taglen)) {
		if (state->default_ns_id >= 0 &&
		    state->default_ns_id == (int)ns_id)
			return (NULL == strchr (str, ':')) ? str : NULL;
		return NULL;
	}

	return str + inst->taglen;
}

/**
 * gsf_xml_in_namecmp :
 * @xin   : The #GsfXMLIn we are reading from.
 * @str   : The potentially namespace qualified node name.
 * @ns_id : The name space id to check
 * @name  : The target node name
 *
 * Returns: %TRUE if @str == @ns_id:@name according to @state.
 **/
gboolean
gsf_xml_in_namecmp (GsfXMLIn const *xin, char const *str,
		    unsigned int ns_id, char const *name)
{
	GsfXMLInInternal const *state = (GsfXMLInInternal const *)xin;
	GsfXMLInNSInstance *inst;

	/* check for default namespace as a likely choice */
	if (state->default_ns_id >= 0 &&
	    state->default_ns_id == (int)ns_id &&
	    0 == strcmp (name, str))
		return TRUE;

	if (ns_id >= state->ns_by_id->len ||
	    NULL == (inst = g_ptr_array_index (state->ns_by_id, ns_id)) ||
	    0 != strncmp (str, inst->tag, inst->taglen))
		return FALSE;
	return 0 == strcmp (name, str + inst->taglen);
}

/****************************************************************************/

enum {
	PROP_0,
	PROP_PRETTY_PRINT
};

typedef enum {
	GSF_XML_OUT_NOCONTENT,
	GSF_XML_OUT_CHILD,
	GSF_XML_OUT_CONTENT
} GsfXMLOutState;

struct _GsfXMLOut {
	GObject	   base;

	GsfOutput	 *output;
	char		 *doc_type;
	GSList		 *stack;
	GsfXMLOutState	  state;
	unsigned   	  indent;
	gboolean	  needs_header;
	gboolean	  pretty_print;
};

typedef struct {
	GObjectClass  base;
} GsfXMLOutClass;

static void
gsf_xml_out_set_property (GObject      *object,
			  guint         property_id,
			  GValue const *value,
			  GParamSpec   *pspec)
{
	GsfXMLOut *xout = (GsfXMLOut *)object;

	switch (property_id) {
	case PROP_PRETTY_PRINT:
		xout->pretty_print = g_value_get_boolean (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
gsf_xml_out_get_property (GObject     *object,
			  guint        property_id,
			  GValue      *value,
			  GParamSpec  *pspec)
{
	GsfXMLOut const *xout = (GsfXMLOut const *)object;

	switch (property_id) {
	case PROP_PRETTY_PRINT:
		g_value_set_boolean (value, xout->pretty_print);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
gsf_xml_out_finalize (GObject *obj)
{
	GsfXMLOut *xout = GSF_XML_OUT (obj);

	g_free (xout->doc_type);

	parent_class->finalize (obj);
}

static void
gsf_xml_out_init (GObject *obj)
{
	GsfXMLOut *xout = GSF_XML_OUT (obj);
	xout->output = NULL;
	xout->stack  = NULL;
	xout->state  = GSF_XML_OUT_CHILD;
	xout->indent = 0;
	xout->needs_header = TRUE;
	xout->doc_type = NULL;
	xout->pretty_print = TRUE;
}

static void
gsf_xml_out_class_init (GObjectClass *gobject_class)
{
	parent_class = g_type_class_peek_parent (gobject_class);

	gobject_class->finalize	    = gsf_xml_out_finalize;
	gobject_class->get_property = gsf_xml_out_get_property;
	gobject_class->set_property = gsf_xml_out_set_property;

	g_object_class_install_property (gobject_class, PROP_PRETTY_PRINT,
		 g_param_spec_boolean ("pretty-print", "Pretty print",
			"Should the output auto-indent elements to make reading easier",
			TRUE, GSF_PARAM_STATIC | G_PARAM_READWRITE));
}

GSF_CLASS (GsfXMLOut, gsf_xml_out,
	   gsf_xml_out_class_init, gsf_xml_out_init,
	   G_TYPE_OBJECT)

/**
 * gsf_xml_out_new :
 * @output : #GsfOutput
 *
 * Create an XML output stream.
 *
 * Returns: #GsfXMLOut
 */
GsfXMLOut *
gsf_xml_out_new (GsfOutput *output)
{
	GsfXMLOut *xout = g_object_new (GSF_XML_OUT_TYPE, NULL);
	if (G_UNLIKELY (NULL == xout)) return NULL;
	if (!gsf_output_wrap (G_OBJECT (xout), output))
		return NULL;
	xout->output = output;
	return xout;
}

/**
 * gsf_xml_out_set_doc_type :
 * @xout : #GsfXMLOut
 * @type : the document type declaration
 *
 * Store some optional some &lt;!DOCTYPE .. &gt; content
 **/
void
gsf_xml_out_set_doc_type (GsfXMLOut *xout, char const *type)
{
	g_free (xout->doc_type);
	xout->doc_type = g_strdup (type);
}

static inline void
gsf_xml_out_indent (GsfXMLOut *xout)
{
	static char const spaces [] =
		"                                        "
		"                                        "
		"                                        "
		"                                        "
		"                                        "
		"                                        ";
	if (xout->pretty_print) {
		unsigned i;
		for (i = xout->indent ; i > (sizeof (spaces)/2) ; i -= sizeof (spaces)/2)
			gsf_output_write (xout->output, sizeof (spaces) - 1, spaces);
		gsf_output_write (xout->output, i*2, spaces);
	}
}

/**
 * gsf_xml_out_start_element :
 * @xout : #GsfXMLOut
 * @id  : Element name
 *
 * Output a start element @id, if necessary preceeded by an XML declaration.
 */
void
gsf_xml_out_start_element (GsfXMLOut *xout, char const *id)
{
	g_return_if_fail (id != NULL);
	g_return_if_fail (xout != NULL);
	g_return_if_fail (xout->state != GSF_XML_OUT_CONTENT);

	if (xout->needs_header) {
		static char const header0[] =
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
		gsf_output_write (xout->output, sizeof (header0) - 1, header0);
		if (xout->doc_type != NULL)
			gsf_output_puts (xout->output, xout->doc_type);
		xout->needs_header = FALSE;
	}
	if (xout->state == GSF_XML_OUT_NOCONTENT) {
		if (xout->pretty_print)
			gsf_output_write (xout->output, 2, ">\n");
		else
			gsf_output_write (xout->output, 1, ">");
	}

	gsf_xml_out_indent (xout);
	gsf_output_printf (xout->output, "<%s", id);

	xout->stack = g_slist_prepend (xout->stack, (gpointer)id);
	xout->indent++;
	xout->state = GSF_XML_OUT_NOCONTENT;
}

/**
 * gsf_xml_out_end_element :
 * @xout : #GsfXMLOut
 *
 * Closes/ends an XML element.
 *
 * Returns: the element that has been closed.
 */
char const *
gsf_xml_out_end_element (GsfXMLOut *xout)
{
	char const *id;

	g_return_val_if_fail (xout != NULL, NULL);
	g_return_val_if_fail (xout->stack != NULL, NULL);

	id = xout->stack->data;
	xout->stack = g_slist_remove (xout->stack, id);
	xout->indent--;
	switch (xout->state) {
	case GSF_XML_OUT_NOCONTENT :
		if (xout->pretty_print)
			gsf_output_write (xout->output, 3, "/>\n");
		else
			gsf_output_write (xout->output, 2, "/>");
		break;

	case GSF_XML_OUT_CHILD :
		gsf_xml_out_indent (xout);
	/* fall through */
	case GSF_XML_OUT_CONTENT :
		if (xout->pretty_print)
			gsf_output_printf (xout->output, "</%s>\n", id);
		else
			gsf_output_printf (xout->output, "</%s>", id);
	}
	xout->state = GSF_XML_OUT_CHILD;
	return id;
}

/**
 * gsf_xml_out_simple_element :
 * @xout : #GsfXMLOut
 * @id  : Element name
 * @content : Content of the element
 *
 * Convenience routine to output a simple @id element with content @content.
 **/
void
gsf_xml_out_simple_element (GsfXMLOut *xout, char const *id,
			    char const *content)
{
	gsf_xml_out_start_element (xout, id);
	if (content != NULL)
		gsf_xml_out_add_cstr (xout, NULL, content);
	gsf_xml_out_end_element (xout);
}

/**
 * gsf_xml_out_simple_int_element :
 * @xout : #GsfXMLOut
 * @id  : Element name
 * @val : Element value
 *
 * Convenience routine to output an element @id with integer value @val.
 **/
void
gsf_xml_out_simple_int_element (GsfXMLOut *xout, char const *id, int val)
{
	gsf_xml_out_start_element (xout, id);
	gsf_xml_out_add_int (xout, NULL, val);
	gsf_xml_out_end_element (xout);
}

/**
 * gsf_xml_out_simple_float_element :
 * @xout : #GsfXMLOut
 * @id  : Element name
 * @val : Element value
 * @precision : the number of significant digits to use, -1 meaning "enough".
 *
 * Convenience routine to output an element @id with float value @val using
 * @precision significant digits.
 **/
void
gsf_xml_out_simple_float_element (GsfXMLOut *xout, char const *id,
				  double val, int precision)
{
	gsf_xml_out_start_element (xout, id);
	gsf_xml_out_add_float (xout, NULL, val, precision);
	gsf_xml_out_end_element (xout);
}

static void
close_tag_if_neccessary (GsfXMLOut* xout)
{
	if (xout->state != GSF_XML_OUT_CONTENT) {
		xout->state = GSF_XML_OUT_CONTENT;
		gsf_output_write (xout->output, 1, ">");
	}
}

/**
 * gsf_xml_out_add_cstr_unchecked :
 * @xout : #GsfXMLOut
 * @id : optionally NULL for content
 * @val_utf8 : a utf8 encoded string to export
 *
 * dump @val_utf8 to an attribute named @id without checking to see if the
 * content needs escaping.  A useful performance enhancement when the
 * application knows that structure of the content well.  If @val_utf8 is NULL
 * do nothing (no warning, no output)
 **/
void
gsf_xml_out_add_cstr_unchecked (GsfXMLOut *xout, char const *id,
				char const *val_utf8)
{
	g_return_if_fail (xout != NULL);

	if (val_utf8 == NULL)
		return;

	if (id == NULL) {
		close_tag_if_neccessary (xout);
		gsf_output_write (xout->output, strlen (val_utf8), val_utf8);
	} else
		gsf_output_printf (xout->output, " %s=\"%s\"", id, val_utf8);
}

/**
 * gsf_xml_out_add_cstr :
 * @xout : #GsfXMLOut
 * @id : optionally NULL for content
 * @val_utf8 : a utf8 encoded string
 *
 * dump @val_utf8 to an attribute named @id or as the nodes content escaping
 * characters as necessary.  If @val_utf8 is NULL do nothing (no warning, no
 * output)
 **/
void
gsf_xml_out_add_cstr (GsfXMLOut *xout, char const *id,
		      char const *val_utf8)
{
	guint8 const *cur   = val_utf8;
	guint8 const *start = val_utf8;

	g_return_if_fail (xout != NULL);

	if (val_utf8 == NULL)
		return;

	if (id == NULL) {
		close_tag_if_neccessary (xout);
	} else
		gsf_output_printf (xout->output, " %s=\"", id);
	while (*cur != '\0') {
		if (*cur == '<') {
			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;
			gsf_output_write (xout->output, 4, "&lt;");
		} else if (*cur == '>') {
			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;
			gsf_output_write (xout->output, 4, "&gt;");
		} else if (*cur == '&') {
			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;
			gsf_output_write (xout->output, 5, "&amp;");
		} else if (*cur == '"') {
			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;
			gsf_output_write (xout->output, 6, "&quot;");
		} else if (*cur < 0x20 && id != NULL) {
			guint8 buf[8];
			sprintf (buf, "&#%d;", *cur);

			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;

			gsf_output_write (xout->output, strlen (buf), buf);
		} else if (((*cur >= 0x20) && (*cur != 0x7f)) ||
			   (*cur == '\n') || (*cur == '\r') || (*cur == '\t')) {
			cur++;
		} else {
			/*
			 * This is immensely pathetic, but XML 1.0 does not
			 * allow certain characters to be encoded.  XML 1.1
			 * does allow this, but libxml2 does not support it.
			 */
			g_warning ("Unknown char 0x%hhx in string", *cur);

			if (cur != start)
				gsf_output_write (xout->output, cur-start, start);
			start = ++cur;
		}
	}
	if (cur != start)
		gsf_output_write (xout->output, cur-start, start);
	if (id != NULL)
		gsf_output_write (xout->output, 1, "\"");
}

/**
 * gsf_xml_out_add_bool :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @val : a boolean
 *
 * dump boolean value @val to an attribute named @id or as the nodes content
 * Use '1' or '0' to simplify import
 **/
void
gsf_xml_out_add_bool (GsfXMLOut *xout, char const *id,
		      gboolean val)
{
	gsf_xml_out_add_cstr_unchecked (xout, id,
					val ? "1" : "0");
}

/**
 * gsf_xml_out_add_int :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @val : the value
 *
 * dump integer value @val to an attribute named @id or as the nodes content
 **/
void
gsf_xml_out_add_int (GsfXMLOut *xout, char const *id,
		     int val)
{
	char buf [4 * sizeof (int)];
	sprintf (buf, "%d", val);
	gsf_xml_out_add_cstr_unchecked (xout, id, buf);
}

/**
 * gsf_xml_out_add_uint :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @val : the value
 *
 * dump unsigned integer value @val to an attribute named @id or as the nodes
 * content
 **/
void
gsf_xml_out_add_uint (GsfXMLOut *xout, char const *id,
		      unsigned int val)
{
	char buf [4 * sizeof (unsigned int)];
	sprintf (buf, "%u", val);
	gsf_xml_out_add_cstr_unchecked (xout, id, buf);
}

/**
 * gsf_xml_out_add_float :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @val : the value
 * @precision : the number of significant digits to use, -1 meaning "enough".
 *
 * dump float value @val to an attribute named @id or as the nodes
 * content with precision @precision.  The number will be formattted
 * according to the "C" locale.
 **/
void
gsf_xml_out_add_float (GsfXMLOut *xout, char const *id,
		       double val, int precision)
{
	char format_str[4 * sizeof (int) + 10];
	char buf[G_ASCII_DTOSTR_BUF_SIZE + DBL_DIG];

	if (precision < 0 || precision > DBL_DIG)
		precision = DBL_DIG;

	sprintf (format_str, "%%.%dg", precision);
	g_ascii_formatd (buf, sizeof (buf), format_str, val);
	gsf_xml_out_add_cstr_unchecked (xout, id, buf);
}

/**
 * gsf_xml_out_add_color :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @r : Red value
 * @g : Green value
 * @b : Blue value
 *
 * dump Color @r.@g.@b to an attribute named @id or as the nodes content
 **/
void
gsf_xml_out_add_color (GsfXMLOut *xout, char const *id,
		       unsigned int r, unsigned int g, unsigned int b)
{
	char buf [3 * 4 * sizeof (unsigned int) + 1];
	sprintf (buf, "%X:%X:%X", r, g, b);
	gsf_xml_out_add_cstr_unchecked (xout, id, buf);
}

/**
 * gsf_xml_out_add_enum :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @etype : #GType
 * @val : enum element number
 *
 * Output the name of value @val of enumeration type @etype.
 **/
void
gsf_xml_out_add_enum (GsfXMLOut *xout, char const *id, GType etype, gint val)
{
	GEnumClass *eclass = G_ENUM_CLASS (g_type_class_peek (etype));
	GEnumValue *ev = g_enum_get_value (eclass, val);

	if (ev)
		gsf_xml_out_add_cstr_unchecked (xout, id, ev->value_name);
	else
		g_warning ("Invalid value %d for type %s",
			   val, g_type_name (etype));
}

/**
 * gsf_xml_out_add_gvalue :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @val : #GValue
 *
 * Output the value of @val as a string.  Does NOT store any type information
 * with the string, just thevalue.
 **/
void
gsf_xml_out_add_gvalue (GsfXMLOut *xout, char const *id, GValue const *val)
{
	GType t;
	g_return_if_fail (xout != NULL);
	g_return_if_fail (val != NULL);

	t = G_VALUE_TYPE (val);
	switch (t) {
	case G_TYPE_CHAR: {
		char c[2] = { 0, 0 };
		c[0] = g_value_get_char (val);
		/* FIXME: What if we are in 0x80-0xff? */
		gsf_xml_out_add_cstr (xout, id, c);
		break;
	}

	case G_TYPE_UCHAR: {
		unsigned char c[2] = { 0, 0 };
		c[0] = g_value_get_uchar (val);
		/* FIXME: What if we are in 0x80-0xff? */
		gsf_xml_out_add_cstr (xout, id, c);
		break;
	}

	case G_TYPE_BOOLEAN:
		gsf_xml_out_add_cstr (xout, id,
			g_value_get_boolean (val) ? "t" : "f");
		break;
	case G_TYPE_INT:
		gsf_xml_out_add_int (xout, id, g_value_get_int (val));
		break;
	case G_TYPE_UINT:
		gsf_xml_out_add_uint (xout, id, g_value_get_uint (val));
		break;
	case G_TYPE_LONG:
		gsf_xml_out_add_uint (xout, id, g_value_get_long (val));
		break;
	case G_TYPE_ULONG:
		gsf_xml_out_add_uint (xout, id, g_value_get_ulong (val));
		break; 
	case G_TYPE_ENUM:
		gsf_xml_out_add_cstr (xout, id,
			glade_string_from_enum (t, g_value_get_enum (val)));
		break;
	case G_TYPE_FLAGS:
		gsf_xml_out_add_cstr (xout, id,
			glade_string_from_flags (t, g_value_get_flags (val)));
		break;
	case G_TYPE_FLOAT:
		gsf_xml_out_add_float (xout, id, g_value_get_float (val), -1);
		break;
	case G_TYPE_DOUBLE:
		gsf_xml_out_add_float (xout, id, g_value_get_double (val), -1);
		break;
	case G_TYPE_STRING:
		gsf_xml_out_add_cstr (xout, id, g_value_get_string (val));
		break;

	default:
		if (GSF_TIMESTAMP_TYPE == t) {
			GsfTimestamp const *ts = g_value_get_boxed (val);
			char *str = gsf_timestamp_as_string (ts);
			gsf_xml_out_add_cstr (xout, id, str);
			g_free (str);
			break;
		}
	}

	/* FIXME FIXME FIXME Add some error checking */
}

/**
 * gsf_xml_out_add_base64 :
 * @xout : #GsfXMLOut
 * @id  : optionally NULL for content
 * @data : Data to be written
 * @len : Length of data
 *
 * dump @len bytes in @data into the content of node @id using base64
 **/
void
gsf_xml_out_add_base64 (GsfXMLOut *xout, char const *id,
			guint8 const *data, unsigned int len)
{
	/* We could optimize and stream right to the output,
	 * or even just keep the buffer around
	 * for now we start simple */
	guint8 *tmp = gsf_base64_encode_simple (data, len);
	if (tmp == NULL)
		return;
	if (id != NULL)
		g_warning ("Stream a binary blob into an attribute ??");
	gsf_xml_out_add_cstr_unchecked (xout, id, tmp);
	g_free (tmp);
}

GsfOutput *
gsf_xml_out_get_output (GsfXMLOut const *xout)
{
	g_return_val_if_fail (xout != NULL, NULL);
	return xout->output;
}



syntax highlighted by Code2HTML, v. 0.9.1