/* 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 Lesser 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.h>

#include <glib/gi18n-lib.h>

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

#undef DEBUG_PUSH_POP

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 (NULL);

    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 = 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_schar (res, (signed char)(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 = gsf_timestamp_new ();
			gboolean ok = gsf_timestamp_load_from_string (ts, str);
			if (ok)
				gsf_timestamp_to_value (ts, res);
			gsf_timestamp_free (ts);
			if (ok)
				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 (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 (input);

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

	if (res) {
		res->replaceEntities = TRUE;
		res->options |= XML_PARSE_HUGE;
	} else
		g_object_unref (input);

	return res;
}

/**
 * gsf_xml_parser_context: (skip)
 * @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>
 *
 * NOTE: skipped since xmlParserCtxt is not exported to introspection.
 *
 * Returns: (nullable): A parser context
 **/
xmlParserCtxtPtr
gsf_xml_parser_context (GsfInput *input)
{
	return gsf_xml_parser_context_full (input, NULL, NULL);
}

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

	return res;
}

/**
 * gsf_xmlDocFormatDump:
 * @output: #GsfOutput
 * @cur: #xmlDocPtr
 * @encoding: (nullable): The encoding to use.
 * @format: %TRUE to reformat the output.
 *
 * Dumps the document @cur into @output.
 *
 * Returns: status from xmlSaveFormatFileTo.
 **/
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);
}

/**************************************************************************/
/* We parse and do some limited validation of the XML file, if this passes,
 * then we return TRUE
 */
typedef struct {
	GsfXMLProbeFunc	func;
	gboolean success;
} GsfXMLProbeState;

static void
gsf_xml_probe_error (GsfXMLProbeState *state, char const *msg, ...)
{
	(void)msg;
	state->func = NULL;
	state->success = FALSE;
}
static void
gsf_xml_probe_element (GsfXMLProbeState *state,
		       const xmlChar *name,
		       const xmlChar *prefix,
		       const xmlChar *URI,
		       int nb_namespaces,
		       const xmlChar **namespaces,
		       int nb_attributes,
		       int nb_defaulted,
		       const xmlChar **attributes)
{
	state->success =
		state->func &&
		state->func (name, prefix, URI, nb_namespaces, namespaces,
			     nb_attributes, nb_defaulted, attributes);
	state->func = NULL;
}

/**
 * gsf_xml_probe:
 * @input: #GsfInput
 * @func: (scope call): #GsfXMLProbeFunc
 *
 * Returns: TRUE on success.
 */
gboolean
gsf_xml_probe (GsfInput *input, GsfXMLProbeFunc func)
{
	static xmlSAXHandler gsf_xml_prober = {
		NULL, /* internalSubset */
		NULL, /* isStandalone */
		NULL, /* hasInternalSubset */
		NULL, /* hasExternalSubset */
		NULL, /* resolveEntity */
		NULL, /* getEntity */
		NULL, /* entityDecl */
		NULL, /* notationDecl */
		NULL, /* attributeDecl */
		NULL, /* elementDecl */
		NULL, /* unparsedEntityDecl */
		NULL, /* setDocumentLocator */
		NULL, /* startDocument */
		NULL, /* endDocument */
		NULL, /* startElement */
		NULL, /* endElement */
		NULL, /* reference */
		NULL, /* characters */
		NULL, /* ignorableWhitespace */
		NULL, /* processingInstruction */
		NULL, /* comment */
		NULL, /* xmlParserWarning */
		(errorSAXFunc) &gsf_xml_probe_error, /* error */
		(errorSAXFunc) &gsf_xml_probe_error, /* fatalError */
		NULL, /* getParameterEntity */
		NULL, /* cdataBlock; */
		NULL, /* externalSubset; */
		XML_SAX2_MAGIC,
		NULL,
		(startElementNsSAX2Func) &gsf_xml_probe_element, /* startElementNs */
		NULL, /* endElementNs */
		NULL  /* xmlStructuredErrorFunc */
	};
	GsfXMLProbeState probe_state = { func, FALSE };
	xmlParserCtxt *parse_context;
	char const *buf;

	if (gsf_input_seek (input, 0, G_SEEK_SET))
		return FALSE;

	g_object_ref (input);
	input = gsf_input_uncompress (input);
	gsf_input_seek (input, 0, G_SEEK_SET);

	buf = gsf_input_read (input, 4, NULL);
	if (NULL != buf ) {
		parse_context = xmlCreatePushParserCtxt (&gsf_xml_prober, &probe_state,
			(char *)buf, 4, gsf_input_name (input));
		if (NULL != parse_context) {
			while (NULL != probe_state.func &&
			       NULL != (buf = gsf_input_read (input, 1, NULL)))
				xmlParseChunk (parse_context, (char *)buf, 1, 0);
		}
		xmlFreeParserCtxt (parse_context);
	}
	g_object_unref (input);

	return probe_state.success;
}

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

/**
 * GsfXMLInNS:
 * @uri: URI
 * @ns_id:
 **/

/**
 * GsfXMLIn:
 * @user_state: user data
 * @content: the current node content
 * @doc: Current document being parsed #GsfXMLInDoc
 * @node: current node (not on the stack)
 **/

/**
 * GsfXMLInNode:
 * @id: identifier unique in the entire tree
 * @ns_id: namespace identifier
 * @name: node name
 * @parent_id: parent node identifier
 * @start: callback for the node opening
 * @end: callback for node end
 * @has_content: whether the node has content
 * @check_children_for_ns: whether to check namespace for children
 * @share_children_with_parent: whether to share children with parent.
 **/

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;
	unsigned ref_count;
};
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;
	GHashTable	 *ns_unknowns;
	GSList	 	 *contents_stack;
	gboolean          initialized;
	gint	  	  unknown_depth; /* handle recursive unknown tags */
	gboolean	  from_unknown_handler;
	gboolean          debug_parsing;
	gboolean          silent_unknowns;

	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 (state->debug_parsing)
		g_printerr ("push: %-*s%s\n",
			    (int)g_slist_length (state->pub.node_stack), "",
			    node->name);

	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;
		/* Is the node explicitly namespaced ? */
		if (group->ns_id >= 0 &&
		    group->ns_id < (int)state->ns_by_id->len &&
		    NULL != (inst = g_ptr_array_index (state->ns_by_id, group->ns_id)) &&
		    0 == strncmp (name, inst->tag, inst->taglen))
			tmp = name + inst->taglen;
		else if (group->ns_id < 0 ||			/* target is unqualified */
			 group->ns_id == default_ns_id) {	/* target is in default ns */
#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;
		} else
			continue;

		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_dump_state (GsfXMLInInternal *state)
{
	GSList *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) {
		GsfXMLInNodeInternal const *node = ptr->data;
		if (node != NULL)
			g_printerr ("%s -> ", node_name (&node->pub));
	}
	if (state->pub.node != NULL)
		g_printerr ("%s\n", node_name (state->pub.node));
	state->pub.node_stack = g_slist_reverse (state->pub.node_stack);
}

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;
	gboolean complain = !state->silent_unknowns;

	/* 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) {
				char *s = g_strdup (ns_ptr[0] + 6);
				g_hash_table_replace (state->ns_unknowns, s, s);
				if (gsf_debug_flag ("xml-ns"))
					g_warning ("Unknown namespace uri = '%s'", ns_ptr[1]);
			}
		}
	}

	node = (GsfXMLInNodeInternal const *) state->pub.node;

	if (state->unknown_depth == 0) {
		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;

	tmp = strchr (name, ':');
	if (tmp) {
		char *nsstr = g_strndup (name, tmp - (const char *)name);
		if (g_hash_table_lookup (state->ns_unknowns, nsstr))
			complain = FALSE;
		g_free (nsstr);
	}

	if (!complain)
		return;

	g_printerr ("Unexpected element '%s' in state : \n\t", name);
	gsf_xml_in_dump_state (state);
}

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;

	if (state->debug_parsing)
		g_printerr (" pop: %-*s%s\n",
			    (int)g_slist_length (state->pub.node_stack) - 1, "",
			    node->pub.name);

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

	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->initialized)
		return;

	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->ns_unknowns	= g_hash_table_new_full (
		g_str_hash, g_str_equal,
		g_free, NULL);
	state->contents_stack	= NULL;
	state->from_unknown_handler = FALSE;
	state->debug_parsing = gsf_debug_flag ("xml-parsing");
	state->silent_unknowns = 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_hash_table_destroy (state->ns_unknowns);
		state->ns_unknowns = NULL;

		g_slist_free (state->extension_stack);
		state->extension_stack = NULL;

		g_slist_free (state->pub.node_stack);
		state->pub.node_stack = NULL;

		g_slist_free (state->ns_stack);
		state->ns_stack = NULL;

		state->initialized = FALSE;

		if (state->pub.node != &state->pub.doc->root_node->pub) {
			g_warning ("Document likely damaged.");
		}
		if (state->unknown_depth > 0) {
			g_warning ("Document likely damaged.");
		}
	}
}

static void
gsf_xml_in_warning (G_GNUC_UNUSED GsfXMLInInternal *state, char const *msg, ...) G_GNUC_PRINTF (2, 3);
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, ...) G_GNUC_PRINTF (2, 3);
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, ...) G_GNUC_PRINTF (2, 3);
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: A #GsfXMLInDoc
 *
 * Free up resources
 **/
void
gsf_xml_in_doc_free (GsfXMLInDoc *doc)
{
	doc->ref_count--;
	if (doc->ref_count == 0) {
		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: (array zero-terminated=1): an array of node descriptors
 * @ns: (array zero-terminated=1): 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: (nullable): a #GsfXMLInDoc
 **/
GsfXMLInDoc *
gsf_xml_in_doc_new (GsfXMLInNode const *nodes, GsfXMLInNS const *ns)
{
	GsfXMLInDoc  *doc;

	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;

	gsf_xml_in_doc_add_nodes (doc, nodes);

	if (NULL == doc->root_node) {
		gsf_xml_in_doc_free (doc);
		g_return_val_if_fail (NULL != doc->root_node, NULL);
	}
	doc->ref_count = 1;

	return doc;
}

static GsfXMLInDoc *
gsf_xml_in_doc_ref (GsfXMLInDoc *doc) {
	doc->ref_count++;
	return doc;
}

GType
gsf_xml_in_doc_get_type (void)
{
    static GType type = 0;

    if (type == 0)
	type = g_boxed_type_register_static
	    ("GsfXMLInDoc",
	     (GBoxedCopyFunc) gsf_xml_in_doc_ref,
	     (GBoxedFreeFunc) gsf_xml_in_doc_free);

    return type;
}

/**
 * gsf_xml_in_doc_add_nodes:
 * @doc: #GsfXMLInDoc
 * @nodes: (array zero-terminated=1): %NULL terminated array of #GsfXMLInNode
 *
 * Adds additional nodes to the structure of @doc
 **/
void
gsf_xml_in_doc_add_nodes (GsfXMLInDoc *doc,
			  GsfXMLInNode const *nodes)
{
	GsfXMLInNode const *e_node;

	g_return_if_fail (doc != NULL);
	g_return_if_fail (nodes != NULL);

	for (e_node = nodes; e_node->id != NULL ; e_node++ ) {
		GsfXMLInNodeInternal *tmp, *node =
			g_hash_table_lookup (doc->symbols, e_node->id);
		if (node != NULL) {
			/*
			 * We use the repeat of a node name to attach an
			 * entire subtree in another place too.  The second
			 * node should either be empty (old method) or
			 * use GSF_XML_2ND (new, as-of 1.14.33).
			 */
			if (e_node->has_content == GSF_XML_2ND) {
				/* Nothing, but e_node contents ignored. */
			} else 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.",
					e_node->id);
				continue;
			}

			if (strcmp (e_node->parent_id, node->pub.parent_id) == 0)
				g_warning ("Duplicate node %s (under %s)", e_node->id, e_node->parent_id);
		} else {
			if (e_node->has_content == GSF_XML_2ND) {
				g_warning ("ID '%s' is declared 2nd, but is missing.",
					e_node->id);
				/* Hence e_node contents ignored. */
				continue;
			}			

			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 (NULL == doc->root_node && 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;
		}
	}
}

/**
 * gsf_xml_in_doc_set_unknown_handler:
 * @doc: #GsfXMLInDoc
 * @handler: (scope call): 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: arbitrary content for the parser
 * @dtor: (scope call): #GsfXMLInExtDtor
 * @attrs: (array) (element-type utf8): array of xmlChar const *
 *
 * Take the first node from @doc as the current node and call its 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: arbitrary content stored in the parser
 *
 * 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);

	if (state.pub.content)
		g_string_free (state.pub.content, TRUE);

	return res;
}

static GsfXMLInNS *
gsf_xml_in_ns_copy (GsfXMLInNS *ns) {
	GsfXMLInNS *res = g_new (GsfXMLInNS, 1);
	res->uri = ns->uri;
	res->ns_id = ns->ns_id;
	return res;
}

GType
gsf_xml_in_ns_get_type (void)
{
    static GType type = 0;

    if (type == 0)
	type = g_boxed_type_register_static
	    ("GsfXMLInNS",
	     (GBoxedCopyFunc) gsf_xml_in_ns_copy,
	     (GBoxedFreeFunc) g_free);

    return type;
}

/**
 * gsf_xml_in_get_input:
 * @xin: #GsfXMLIn
 *
 * (New in 1.14.2)
 *
 * Returns: (transfer none): (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_set_silent_unknowns:
 * @xin: #GsfXMLIn
 * @silent: whether to be silent about unknown tags
 *
 * (New in 1.14.33)
 *
 * This provides a means to silently ignore unknown tags in contexts where
 * they are expected.
 **/
void
gsf_xml_in_set_silent_unknowns (GsfXMLIn *xin, gboolean silent)
{
	GsfXMLInInternal *state = (GsfXMLInInternal *)xin;
	state->silent_unknowns = silent;
}


/**
 * gsf_xml_in_check_ns:
 * @xin: #GsfXMLIn
 * @str: string to check
 * @ns_id: the namespace id
 *
 * According to @state is @str in the namespace @ns_id ?
 *
 * Returns: (transfer none) (nullable): 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
 *
 * Checks to see if @str is the same as @ns_id::@name with either an explicit
 * namespace or the current default namespace.
 *
 * 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,
	PROP_SINK
};

typedef enum {
	GSF_XML_OUT_NOCONTENT,
	GSF_XML_OUT_CHILD,
	GSF_XML_OUT_CHILD_PRETTY,
	GSF_XML_OUT_CONTENT
} GsfXMLOutState;

typedef struct _GsfXMLOutPrivate {
	char		 *doc_type;
	GSList		 *stack;
	GsfXMLOutState	  state;
	unsigned   	  indent;
	gboolean	  needs_header;
	gboolean	  pretty_print;
} GsfXMLOutPrivate;

G_DEFINE_TYPE_WITH_CODE (GsfXMLOut, gsf_xml_out, G_TYPE_OBJECT,
			 G_ADD_PRIVATE (GsfXMLOut))

static void
gsf_xml_out_set_output (GsfXMLOut *xout, GsfOutput *output)
{
	if (gsf_output_wrap (G_OBJECT (xout), output))
		xout->output = output;
}

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

	switch (property_id) {
	case PROP_PRETTY_PRINT:
		priv->pretty_print = g_value_get_boolean (value);
		break;
	case PROP_SINK:
		gsf_xml_out_set_output (xout, g_value_get_object (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;
	GsfXMLOutPrivate const *priv = xout->priv;

	switch (property_id) {
	case PROP_PRETTY_PRINT:
		g_value_set_boolean (value, priv->pretty_print);
		break;
	case PROP_SINK:
		g_value_set_object (value, xout->output);
		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->priv->doc_type);
	parent_class->finalize (obj);
}

static void
gsf_xml_out_init (GsfXMLOut *xout)
{
	GsfXMLOutPrivate *priv = gsf_xml_out_get_instance_private (xout);

	xout->output = NULL;
	xout->priv = priv;
	priv->stack  = NULL;
	priv->state  = GSF_XML_OUT_CHILD;
	priv->indent = 0;
	priv->needs_header = TRUE;
	priv->doc_type = NULL;
	priv->pretty_print = TRUE;
}

static void
gsf_xml_out_class_init (GsfXMLOutClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	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, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));

	g_object_class_install_property
		(gobject_class, PROP_SINK,
		 g_param_spec_object ("sink",
				      _("Sink"),
				      _("The destination for writes"),
				      GSF_OUTPUT_TYPE,
				      G_PARAM_STATIC_STRINGS |
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT_ONLY));
}

/**
 * gsf_xml_out_new:
 * @output: #GsfOutput
 *
 * Create an XML output stream.
 *
 * Returns: #GsfXMLOut
 */
GsfXMLOut *
gsf_xml_out_new (GsfOutput *output)
{
	g_return_val_if_fail (GSF_IS_OUTPUT (output), NULL);

	return g_object_new (GSF_XML_OUT_TYPE,
			     "sink", output,
			     NULL);
}

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

/**
 * gsf_xml_out_get_pretty_print:
 * @xout: #GsfXMLOut
 *
 * Returns: the current state of the pretty-print flag.  Note, that
 * gsf_xml_out_set_pretty_print will return the same value.
 **/
gboolean
gsf_xml_out_get_pretty_print (GsfXMLOut *xout)
{
	g_return_val_if_fail (GSF_IS_XML_OUT (xout), TRUE);
	return xout->priv->pretty_print;
}

/**
 * gsf_xml_out_set_pretty_print:
 * @xout: #GsfXMLOut
 * @pp: new state of pretty-print flag.
 *
 * Returns: the previous state of the pretty-print flag.
 **/
gboolean
gsf_xml_out_set_pretty_print (GsfXMLOut *xout, gboolean pp)
{
	gboolean res;

	g_return_val_if_fail (GSF_IS_XML_OUT (xout), TRUE);

	pp = !!pp;

	res = xout->priv->pretty_print;
	if (pp != res) {
		xout->priv->pretty_print = pp;
		g_object_notify (G_OBJECT (xout), "pretty-print");
	}
	return res;
}

static inline void
gsf_xml_out_indent (GsfXMLOut *xout)
{
	GsfXMLOutPrivate *priv = xout->priv;
	static char const spaces [] =
		"                                        "
		"                                        "
		"                                        "
		"                                        "
		"                                        "
		"                                        ";
	if (priv->pretty_print) {
		unsigned i;
		for (i = priv->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)
{
	GsfXMLOutPrivate *priv;

	g_return_if_fail (id != NULL);
	g_return_if_fail (xout != NULL);
	priv = xout->priv;

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

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

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

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

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

	case GSF_XML_OUT_CHILD_PRETTY:
		gsf_xml_out_indent (xout);
	/* fall through */
	case GSF_XML_OUT_CHILD:
	case GSF_XML_OUT_CONTENT:
		if (priv->pretty_print)
			gsf_output_printf (xout->output, "</%s>\n", id);
		else
			gsf_output_printf (xout->output, "</%s>", id);
	}
	priv->state = priv->pretty_print ? GSF_XML_OUT_CHILD_PRETTY : 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)
{
	GsfXMLOutPrivate *priv = xout->priv;
	if (priv->state == GSF_XML_OUT_NOCONTENT) {
		priv->state = GSF_XML_OUT_CONTENT;
		gsf_output_write (xout->output, 1, ">");
	}
}

/**
 * gsf_xml_out_add_cstr_unchecked:
 * @xout: #GsfXMLOut
 * @id: (nullable): tag id, or %NULL for node content
 * @val_utf8: (nullable): 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: (nullable): tag id, or %NULL for node content
 * @val_utf8: (nullable): 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 == '\n' || *cur == '\r' || *cur == '\t') &&
			   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%02x 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: (nullable): tag id, or %NULL for node 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: (nullable): tag id, or %NULL for node 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: (nullable): tag id, or %NULL for node 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: (nullable): tag id, or %NULL for node 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 buf[G_ASCII_DTOSTR_BUF_SIZE + DBL_DIG + 17];

	if (precision < 0 || precision > 17) {
		g_ascii_dtostr (buf, sizeof (buf), val);
	} else {
		char format_str[4 * sizeof (int) + 10];
		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: (nullable): tag id, or %NULL for node 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: (nullable): tag id, or %NULL for node 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_ref (etype));
	GEnumValue *ev = g_enum_get_value (eclass, val);
	g_type_class_unref (eclass);

	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: (nullable): tag id, or %NULL for node 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] = (char)g_value_get_schar (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: (nullable): tag id, or %NULL for node content
 * @data: (array length=len): 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);
}

/**
 * gsf_xml_out_get_output:
 * @xout: #GsfXMLOut
 *
 * Get the #GsfOutput we are writing to..
 *
 * Returns: (transfer none) (nullable): #GsfInput
 **/
GsfOutput *
gsf_xml_out_get_output (GsfXMLOut const *xout)
{
	g_return_val_if_fail (xout != NULL, NULL);
	return xout->output;
}
