Blob Blame History Raw
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * GData Client
 * Copyright (C) Philip Withnall 2009ā€“2010 <philip@tecnocode.co.uk>
 *
 * GData Client is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * GData Client 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 GData Client.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:gdata-parsable
 * @short_description: GData parsable object
 * @stability: Stable
 * @include: gdata/gdata-parsable.h
 *
 * #GDataParsable is an abstract class allowing easy implementation of an extensible parser. It is primarily extended by #GDataFeed and #GDataEntry,
 * both of which require XML parsing which can be extended by subclassing.
 *
 * It allows methods to be defined for handling the root XML node, each of its child nodes, and a method to be called after parsing is complete.
 *
 * Since: 0.3.0
 */

#include <config.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#include <libxml/parser.h>
#include <json-glib/json-glib.h>

#include "gdata-parsable.h"
#include "gdata-private.h"
#include "gdata-parser.h"

GQuark
gdata_parser_error_quark (void)
{
	return g_quark_from_static_string ("gdata-parser-error-quark");
}

static void gdata_parsable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static void gdata_parsable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static void gdata_parsable_finalize (GObject *object);
static gboolean real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
static gboolean real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
static const gchar *get_content_type (void);

struct _GDataParsablePrivate {
	/* XML stuff. */
	GString *extra_xml;
	GHashTable *extra_namespaces;

	/* JSON stuff. */
	GHashTable/*<gchar*, owned JsonNode*>*/ *extra_json;

	gboolean constructed_from_xml;
};

enum {
	PROP_CONSTRUCTED_FROM_XML = 1,
};

G_DEFINE_ABSTRACT_TYPE (GDataParsable, gdata_parsable, G_TYPE_OBJECT)

static void
gdata_parsable_class_init (GDataParsableClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (GDataParsablePrivate));

	gobject_class->get_property = gdata_parsable_get_property;
	gobject_class->set_property = gdata_parsable_set_property;
	gobject_class->finalize = gdata_parsable_finalize;
	klass->parse_xml = real_parse_xml;
	klass->parse_json = real_parse_json;
	klass->get_content_type = get_content_type;

	/**
	 * GDataParsable:constructed-from-xml:
	 *
	 * Specifies whether the object was constructed by parsing XML or manually.
	 *
	 * Since: 0.7.0
	 */
	g_object_class_install_property (gobject_class, PROP_CONSTRUCTED_FROM_XML,
	                                 g_param_spec_boolean ("constructed-from-xml",
	                                                       "Constructed from XML?",
	                                                       "Specifies whether the object was constructed by parsing XML or manually.",
	                                                       FALSE,
	                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gdata_parsable_init (GDataParsable *self)
{
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PARSABLE, GDataParsablePrivate);

	self->priv->extra_xml = g_string_new ("");
	self->priv->extra_namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

	self->priv->extra_json = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) json_node_free);

	self->priv->constructed_from_xml = FALSE;
}


static void
gdata_parsable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;

	switch (property_id) {
		case PROP_CONSTRUCTED_FROM_XML:
			g_value_set_boolean (value, priv->constructed_from_xml);
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
gdata_parsable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;

	switch (property_id) {
		case PROP_CONSTRUCTED_FROM_XML:
			priv->constructed_from_xml = g_value_get_boolean (value);
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
gdata_parsable_finalize (GObject *object)
{
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;

	g_string_free (priv->extra_xml, TRUE);
	g_hash_table_destroy (priv->extra_namespaces);

	g_hash_table_destroy (priv->extra_json);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (gdata_parsable_parent_class)->finalize (object);
}

static gboolean
real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
{
	xmlBuffer *buffer;
	xmlNs **namespaces, **namespace;

	/* Unhandled XML */
	buffer = xmlBufferCreate ();
	xmlNodeDump (buffer, doc, node, 0, 0);
	g_string_append (parsable->priv->extra_xml, (gchar*) xmlBufferContent (buffer));
	g_debug ("Unhandled XML in %s: %s", G_OBJECT_TYPE_NAME (parsable), (gchar*) xmlBufferContent (buffer));
	xmlBufferFree (buffer);

	/* Get the namespaces */
	namespaces = xmlGetNsList (doc, node);
	if (namespaces == NULL)
		return TRUE;

	for (namespace = namespaces; *namespace != NULL; namespace++) {
		if ((*namespace)->prefix != NULL) {
			/* NOTE: These two g_strdup()s leak, but it's probably acceptable, given that it saves us
			 * g_strdup()ing every other namespace we put in @extra_namespaces. */
			g_hash_table_insert (parsable->priv->extra_namespaces,
			                     g_strdup ((gchar*) ((*namespace)->prefix)),
			                     g_strdup ((gchar*) ((*namespace)->href)));
		}
	}
	xmlFree (namespaces);

	return TRUE;
}

/* Extract the member node. This would be a lot easier if JsonReader had an API to return
 * the current node (regardless of whether it's a value, object or array). FIXME: bgo#707100. */
static JsonNode * /* transfer full */
_json_reader_dup_current_node (JsonReader *reader)
{
	JsonNode *value;

	if (json_reader_is_value (reader) == TRUE) {
		/* Value nodes are easy. Well, ignoring the complication of nulls. */
		if (json_reader_get_null_value (reader) == TRUE) {
			value = json_node_new (JSON_NODE_NULL);
		} else {
			value = json_node_copy (json_reader_get_value (reader));
		}
	} else if (json_reader_is_object (reader) == TRUE) {
		/* Object nodes require deep copies. */
		gint i;
		gchar **members;
		JsonObject *obj;

		obj = json_object_new ();

		for (i = 0, members = json_reader_list_members (reader); members[i] != NULL; i++) {
			json_reader_read_member (reader, members[i]);
			json_object_set_member (obj, members[i], _json_reader_dup_current_node (reader));
			json_reader_end_member (reader);
		}

		g_strfreev (members);

		value = json_node_new (JSON_NODE_OBJECT);
		json_node_take_object (value, obj);
	} else if (json_reader_is_array (reader) == TRUE) {
		/* Array nodes require deep copies. */
		gint i, elements;
		JsonArray *arr;

		arr = json_array_new ();

		for (i = 0, elements = json_reader_count_elements (reader); i < elements; i++) {
			json_reader_read_element (reader, i);
			json_array_add_element (arr, _json_reader_dup_current_node (reader));
			json_reader_end_element (reader);
		}

		value = json_node_new (JSON_NODE_ARRAY);
		json_node_take_array (value, arr);
	} else {
		/* Uh-oh. */
		g_assert_not_reached ();
	}

	return value;
}

static gboolean
real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
{
	gchar *json, *member_name;
	JsonGenerator *generator;
	JsonNode *value;

	/* Unhandled JSON member. Save it and its value to ->extra_xml so that it's not lost if we
	 * re-upload this Parsable to the server. */
	member_name = g_strdup (json_reader_get_member_name (reader));
	g_assert (member_name != NULL);

	/* Extract a copy of the current node. */
	value = _json_reader_dup_current_node (reader);
	g_assert (value != NULL);

	/* Serialise the value for debugging. */
	generator = json_generator_new ();
	json_generator_set_root (generator, value);

	json = json_generator_to_data (generator, NULL);
	g_debug ("Unhandled JSON member ā€˜%sā€™ in %s: %s", member_name, G_OBJECT_TYPE_NAME (parsable), json);
	g_free (json);

	g_object_unref (generator);

	/* Save the value. Transfer ownership of the member_name and value. */
	g_hash_table_replace (parsable->priv->extra_json, (gpointer) member_name, (gpointer) value);

	return TRUE;
}

static const gchar *
get_content_type (void) {
	return "application/atom+xml";
}

/**
 * gdata_parsable_new_from_xml:
 * @parsable_type: the type of the class represented by the XML
 * @xml: the XML for just the parsable object, with full namespace declarations
 * @length: the length of @xml, or -1
 * @error: a #GError, or %NULL
 *
 * Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @xml.
 *
 * An object of the given @parsable_type is created, and its <function>pre_parse_xml</function>, <function>parse_xml</function> and
 * <function>post_parse_xml</function> class functions called on the XML tree obtained from @xml. <function>pre_parse_xml</function> and
 * <function>post_parse_xml</function> are called once each on the root node of the tree, while <function>parse_xml</function> is called for
 * each of the child nodes of the root node.
 *
 * If @length is -1, @xml will be assumed to be null-terminated.
 *
 * If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
 *
 * Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
 *
 * Since: 0.4.0
 */
GDataParsable *
gdata_parsable_new_from_xml (GType parsable_type, const gchar *xml, gint length, GError **error)
{
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (xml != NULL && *xml != '\0', NULL);
	g_return_val_if_fail (length >= -1, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	return _gdata_parsable_new_from_xml (parsable_type, xml, length, NULL, error);
}

GDataParsable *
_gdata_parsable_new_from_xml (GType parsable_type, const gchar *xml, gint length, gpointer user_data, GError **error)
{
	xmlDoc *doc;
	xmlNode *node;
	GDataParsable *parsable;
	static gboolean libxml_initialised = FALSE;

	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (xml != NULL && *xml != '\0', NULL);
	g_return_val_if_fail (length >= -1, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	/* Set up libxml. We do this here to avoid introducing a libgdata setup function, which would be unnecessary hassle. This is the only place
	 * that libxml can be initialised in the library. */
	if (libxml_initialised == FALSE) {
		/* Change the libxml memory allocation functions to be GLib's. This means we don't have to re-allocate all the strings we get from
		 * libxml, which cuts down on strdup() calls dramatically. */
		xmlMemSetup ((xmlFreeFunc) g_free, (xmlMallocFunc) g_malloc, (xmlReallocFunc) g_realloc, (xmlStrdupFunc) g_strdup);
		libxml_initialised = TRUE;
	}

	if (length == -1)
		length = strlen (xml);

	/* Parse the XML */
	doc = xmlReadMemory (xml, length, "/dev/null", NULL, 0);
	if (doc == NULL) {
		xmlError *xml_error = xmlGetLastError ();
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
		             /* Translators: the parameter is an error message */
		             _("Error parsing XML: %s"),
		             (xml_error != NULL) ? xml_error->message : NULL);
		return NULL;
	}

	/* Get the root element */
	node = xmlDocGetRootElement (doc);
	if (node == NULL) {
		/* XML document's empty */
		xmlFreeDoc (doc);
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
		             _("Error parsing XML: %s"),
		             /* Translators: this is a dummy error message to be substituted into "Error parsing XML: %s". */
		             _("Empty document."));
		return NULL;
	}

	parsable = _gdata_parsable_new_from_xml_node (parsable_type, doc, node, user_data, error);
	xmlFreeDoc (doc);

	return parsable;
}

GDataParsable *
_gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
{
	GDataParsable *parsable;
	GDataParsableClass *klass;

	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (doc != NULL, NULL);
	g_return_val_if_fail (node != NULL, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);

	klass = GDATA_PARSABLE_GET_CLASS (parsable);
	if (klass->parse_xml == NULL) {
		g_object_unref (parsable);
		return NULL;
	}

	g_assert (klass->element_name != NULL);
	/* TODO: See gdata-documents-entry.c:260 for an example of where the code below doesn't work */
	/*if (xmlStrcmp (node->name, (xmlChar*) klass->element_name) != 0 ||
	    (node->ns != NULL && xmlStrcmp (node->ns->prefix, (xmlChar*) klass->element_namespace) != 0)) {
		* No <entry> element (required) *
		gdata_parser_error_required_element_missing (klass->element_name, "root", error);
		return NULL;
	}*/

	/* Call the pre-parse function first */
	if (klass->pre_parse_xml != NULL &&
	    klass->pre_parse_xml (parsable, doc, node, user_data, error) == FALSE) {
		g_object_unref (parsable);
		return NULL;
	}

	/* Parse each child element */
	node = node->children;
	while (node != NULL) {
		if (klass->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
			g_object_unref (parsable);
			return NULL;
		}
		node = node->next;
	}

	/* Call the post-parse function */
	if (klass->post_parse_xml != NULL &&
	    klass->post_parse_xml (parsable, user_data, error) == FALSE) {
		g_object_unref (parsable);
		return NULL;
	}

	return parsable;
}

/**
 * gdata_parsable_new_from_json:
 * @parsable_type: the type of the class represented by the JSON
 * @json: the JSON for just the parsable object
 * @length: the length of @json, or -1
 * @error: a #GError, or %NULL
 *
 * Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @json.
 *
 * An object of the given @parsable_type is created, and its <function>parse_json</function> and
 * <function>post_parse_json</function> class functions called on the JSON node obtained from @json.
 * <function>post_parse_json</function> is called once on the root node, while <function>parse_json</function> is called for
 * each of the node's members.
 *
 * If @length is -1, @json will be assumed to be nul-terminated.
 *
 * If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
 *
 * Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
 *
 * Since: 0.15.0
 */
GDataParsable *
gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, GError **error)
{
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (json != NULL && *json != '\0', NULL);
	g_return_val_if_fail (length >= -1, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	return _gdata_parsable_new_from_json (parsable_type, json, length, NULL, error);
}

GDataParsable *
_gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, gpointer user_data, GError **error)
{
	JsonParser *parser;
	JsonReader *reader;
	GDataParsable *parsable;
	GError *child_error = NULL;

	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (json != NULL && *json != '\0', NULL);
	g_return_val_if_fail (length >= -1, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (length == -1)
		length = strlen (json);

	parser = json_parser_new ();
	if (!json_parser_load_from_data (parser, json, length, &child_error)) {
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
		             /* Translators: the parameter is an error message */
		             _("Error parsing JSON: %s"), child_error->message);
		g_error_free (child_error);
		g_object_unref (parser);

		return NULL;
	}

	reader = json_reader_new (json_parser_get_root (parser));
	parsable = _gdata_parsable_new_from_json_node (parsable_type, reader, user_data, error);

	g_object_unref (reader);
	g_object_unref (parser);

	return parsable;
}

GDataParsable *
_gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, gpointer user_data, GError **error)
{
	GDataParsable *parsable;
	GDataParsableClass *klass;
	gint i;

	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
	g_return_val_if_fail (reader != NULL, NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	/* Indicator property which allows distinguishing between locally created and server based objects
	 * as it is used for non-XML tasks, and adding another one for JSON would be a bit pointless. */
	parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);

	klass = GDATA_PARSABLE_GET_CLASS (parsable);
	g_assert (klass->parse_json != NULL);

	/* Check that the outermost node is an object. */
	if (json_reader_is_object (reader) == FALSE) {
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
		             /* Translators: the parameter is an error message */
		             _("Error parsing JSON: %s"),
		             _("Outermost JSON node is not an object."));
		g_object_unref (parsable);
		return NULL;
	}

	/* Parse each child member. This assumes the outermost node is an object. */
	for (i = 0; i < json_reader_count_members (reader); i++) {
		g_return_val_if_fail (json_reader_read_element (reader, i), NULL);

		if (klass->parse_json (parsable, reader, user_data, error) == FALSE) {
			json_reader_end_element (reader);
			g_object_unref (parsable);
			return NULL;
		}

		json_reader_end_element (reader);
	}

	/* Call the post-parse function */
	if (klass->post_parse_json != NULL &&
	    klass->post_parse_json (parsable, user_data, error) == FALSE) {
		g_object_unref (parsable);
		return NULL;
	}

	return parsable;
}

/**
 * gdata_parsable_get_content_type:
 * @self: a #GDataParsable
 *
 * Returns the content type upon which the #GDataParsable is built. For example, `application/atom+xml` or `application/json`.
 *
 * Return value: the parsable's content type
 *
 * Since: 0.17.7
 */
const gchar *
gdata_parsable_get_content_type (GDataParsable *self)
{
	GDataParsableClass *klass;

	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);

	klass = GDATA_PARSABLE_GET_CLASS (self);
	g_assert (klass->get_content_type != NULL);

	return klass->get_content_type ();
}

static void
build_namespaces_cb (gchar *prefix, gchar *href, GString *output)
{
	g_string_append_printf (output, " xmlns:%s='%s'", prefix, href);
}

static gboolean
filter_namespaces_cb (gchar *prefix, gchar *href, GHashTable *canonical_namespaces)
{
	if (g_hash_table_lookup (canonical_namespaces, prefix) != NULL)
		return TRUE;
	return FALSE;
}

/**
 * gdata_parsable_get_xml:
 * @self: a #GDataParsable
 *
 * Builds an XML representation of the #GDataParsable in its current state, such that it could be inserted on the server. The XML is guaranteed
 * to have all its namespaces declared properly in a self-contained fashion, and is valid for stand-alone use.
 *
 * Return value: the object's XML; free with g_free()
 *
 * Since: 0.4.0
 */
gchar *
gdata_parsable_get_xml (GDataParsable *self)
{
	GString *xml_string;

	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);

	xml_string = g_string_sized_new (1000);
	g_string_append (xml_string, "<?xml version='1.0' encoding='UTF-8'?>");
	_gdata_parsable_get_xml (self, xml_string, TRUE);
	return g_string_free (xml_string, FALSE);
}

/*
 * _gdata_parsable_get_xml:
 * @self: a #GDataParsable
 * @xml_string: a #GString to build the XML in
 * @declare_namespaces: %TRUE if all the namespaces used in the outputted XML should be declared in the opening tag of the root element,
 * %FALSE otherwise
 *
 * Builds an XML representation of the #GDataParsable in its current state, such that it could be inserted on the server. If @declare_namespaces is
 * %TRUE, the XML is guaranteed to have all its namespaces declared properly in a self-contained fashion, and is valid for stand-alone use. If
 * @declare_namespaces is %FALSE, none of the used namespaces are declared, and the XML is suitable for insertion into a larger XML tree.
 *
 * Return value: the object's XML; free with g_free()
 *
 * Since: 0.4.0
 */
void
_gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean declare_namespaces)
{
	GDataParsableClass *klass;
	guint length;
	GHashTable *namespaces = NULL; /* shut up, gcc */

	g_return_if_fail (GDATA_IS_PARSABLE (self));
	g_return_if_fail (xml_string != NULL);

	klass = GDATA_PARSABLE_GET_CLASS (self);
	g_assert (klass->element_name != NULL);

	/* Get the namespaces the class uses */
	if (declare_namespaces == TRUE && klass->get_namespaces != NULL) {
		namespaces = g_hash_table_new (g_str_hash, g_str_equal);
		klass->get_namespaces (self, namespaces);

		/* Remove any duplicate extra namespaces */
		g_hash_table_foreach_remove (self->priv->extra_namespaces, (GHRFunc) filter_namespaces_cb, namespaces);
	}

	/* Build up the namespace list */
	if (klass->element_namespace != NULL)
		g_string_append_printf (xml_string, "<%s:%s", klass->element_namespace, klass->element_name);
	else
		g_string_append_printf (xml_string, "<%s", klass->element_name);

	/* We only include the normal namespaces if we're not at the top level of XML building */
	if (declare_namespaces == TRUE) {
		g_string_append (xml_string, " xmlns='http://www.w3.org/2005/Atom'");
		if (namespaces != NULL) {
			g_hash_table_foreach (namespaces, (GHFunc) build_namespaces_cb, xml_string);
			g_hash_table_destroy (namespaces);
		}
	}

	g_hash_table_foreach (self->priv->extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);

	/* Add anything the class thinks is suitable */
	if (klass->pre_get_xml != NULL)
		klass->pre_get_xml (self, xml_string);
	g_string_append_c (xml_string, '>');

	/* Store the length before we close the opening tag, so we can determine whether to self-close later on */
	length = xml_string->len;

	/* Add the rest of the XML */
	if (klass->get_xml != NULL)
		klass->get_xml (self, xml_string);

	/* Any extra XML? */
	if (self->priv->extra_xml != NULL && self->priv->extra_xml->str != NULL)
		g_string_append (xml_string, self->priv->extra_xml->str);

	/* Close the element; either by self-closing the opening tag, or by writing out a closing tag */
	if (xml_string->len == length)
		g_string_overwrite (xml_string, length - 1, "/>");
	else if (klass->element_namespace != NULL)
		g_string_append_printf (xml_string, "</%s:%s>", klass->element_namespace, klass->element_name);
	else
		g_string_append_printf (xml_string, "</%s>", klass->element_name);
}

/**
 * gdata_parsable_get_json:
 * @self: a #GDataParsable
 *
 * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server. The JSON
 * is valid for stand-alone use.
 *
 * Return value: the object's JSON; free with g_free()
 *
 * Since: 0.15.0
 */
gchar *
gdata_parsable_get_json (GDataParsable *self)
{
	JsonGenerator *generator;
	JsonBuilder *builder;
	JsonNode *root;
	gchar *output;

	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);

	/* Build the JSON tree. */
	builder = json_builder_new ();
	_gdata_parsable_get_json (self, builder);
	root = json_builder_get_root (builder);
	g_object_unref (builder);

	/* Serialise it to a string. */
	generator = json_generator_new ();
	json_generator_set_root (generator, root);
	output = json_generator_to_data (generator, NULL);
	g_object_unref (generator);

	json_node_free (root);

	return output;
}

/*
 * _gdata_parsable_get_json:
 * @self: a #GDataParsable
 * @builder: a #JsonBuilder to build the JSON in
 *
 * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server.
 *
 * Since: 0.15.0
 */
void
_gdata_parsable_get_json (GDataParsable *self, JsonBuilder *builder)
{
	GDataParsableClass *klass;
	GHashTableIter iter;
	gchar *member_name;
	JsonNode *value;

	g_return_if_fail (GDATA_IS_PARSABLE (self));
	g_return_if_fail (JSON_IS_BUILDER (builder));

	klass = GDATA_PARSABLE_GET_CLASS (self);

	json_builder_begin_object (builder);

	/* Add the JSON. */
	if (klass->get_json != NULL)
		klass->get_json (self, builder);

	/* Any extra JSON which we couldn't parse before? */
	g_hash_table_iter_init (&iter, self->priv->extra_json);
	while (g_hash_table_iter_next (&iter, (gpointer *) &member_name, (gpointer *) &value) == TRUE) {
		json_builder_set_member_name (builder, member_name);
		json_builder_add_value (builder, json_node_copy (value)); /* transfers ownership */
	}

	json_builder_end_object (builder);
}

/*
 * _gdata_parsable_is_constructed_from_xml:
 * @self: a #GDataParsable
 *
 * Returns the value of #GDataParsable:constructed-from-xml.
 *
 * Return value: %TRUE if the #GDataParsable was constructed from XML, %FALSE otherwise
 *
 * Since: 0.7.0
 */
gboolean
_gdata_parsable_is_constructed_from_xml (GDataParsable *self)
{
	g_return_val_if_fail (GDATA_IS_PARSABLE (self), FALSE);
	return self->priv->constructed_from_xml;
}