Blame gdata/gdata-parsable.c

Packit 4b6dd7
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
Packit 4b6dd7
/*
Packit 4b6dd7
 * GData Client
Packit 4b6dd7
 * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
Packit 4b6dd7
 *
Packit 4b6dd7
 * GData Client is free software; you can redistribute it and/or
Packit 4b6dd7
 * modify it under the terms of the GNU Lesser General Public
Packit 4b6dd7
 * License as published by the Free Software Foundation; either
Packit 4b6dd7
 * version 2.1 of the License, or (at your option) any later version.
Packit 4b6dd7
 *
Packit 4b6dd7
 * GData Client is distributed in the hope that it will be useful,
Packit 4b6dd7
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 4b6dd7
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 4b6dd7
 * Lesser General Public License for more details.
Packit 4b6dd7
 *
Packit 4b6dd7
 * You should have received a copy of the GNU Lesser General Public
Packit 4b6dd7
 * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
Packit 4b6dd7
 */
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * SECTION:gdata-parsable
Packit 4b6dd7
 * @short_description: GData parsable object
Packit 4b6dd7
 * @stability: Stable
Packit 4b6dd7
 * @include: gdata/gdata-parsable.h
Packit 4b6dd7
 *
Packit 4b6dd7
 * #GDataParsable is an abstract class allowing easy implementation of an extensible parser. It is primarily extended by #GDataFeed and #GDataEntry,
Packit 4b6dd7
 * both of which require XML parsing which can be extended by subclassing.
Packit 4b6dd7
 *
Packit 4b6dd7
 * 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.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.3.0
Packit 4b6dd7
 */
Packit 4b6dd7
Packit 4b6dd7
#include <config.h>
Packit 4b6dd7
#include <glib.h>
Packit 4b6dd7
#include <glib/gi18n-lib.h>
Packit 4b6dd7
#include <string.h>
Packit 4b6dd7
#include <libxml/parser.h>
Packit 4b6dd7
#include <json-glib/json-glib.h>
Packit 4b6dd7
Packit 4b6dd7
#include "gdata-parsable.h"
Packit 4b6dd7
#include "gdata-private.h"
Packit 4b6dd7
#include "gdata-parser.h"
Packit 4b6dd7
Packit 4b6dd7
GQuark
Packit 4b6dd7
gdata_parser_error_quark (void)
Packit 4b6dd7
{
Packit 4b6dd7
	return g_quark_from_static_string ("gdata-parser-error-quark");
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void gdata_parsable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
Packit 4b6dd7
static void gdata_parsable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
Packit 4b6dd7
static void gdata_parsable_finalize (GObject *object);
Packit 4b6dd7
static gboolean real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
Packit 4b6dd7
static gboolean real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
Packit 4b6dd7
static const gchar *get_content_type (void);
Packit 4b6dd7
Packit 4b6dd7
struct _GDataParsablePrivate {
Packit 4b6dd7
	/* XML stuff. */
Packit 4b6dd7
	GString *extra_xml;
Packit 4b6dd7
	GHashTable *extra_namespaces;
Packit 4b6dd7
Packit 4b6dd7
	/* JSON stuff. */
Packit 4b6dd7
	GHashTable/*<gchar*, owned JsonNode*>*/ *extra_json;
Packit 4b6dd7
Packit 4b6dd7
	gboolean constructed_from_xml;
Packit 4b6dd7
};
Packit 4b6dd7
Packit 4b6dd7
enum {
Packit 4b6dd7
	PROP_CONSTRUCTED_FROM_XML = 1,
Packit 4b6dd7
};
Packit 4b6dd7
Packit 4b6dd7
G_DEFINE_ABSTRACT_TYPE (GDataParsable, gdata_parsable, G_TYPE_OBJECT)
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_parsable_class_init (GDataParsableClass *klass)
Packit 4b6dd7
{
Packit 4b6dd7
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Packit 4b6dd7
Packit 4b6dd7
	g_type_class_add_private (klass, sizeof (GDataParsablePrivate));
Packit 4b6dd7
Packit 4b6dd7
	gobject_class->get_property = gdata_parsable_get_property;
Packit 4b6dd7
	gobject_class->set_property = gdata_parsable_set_property;
Packit 4b6dd7
	gobject_class->finalize = gdata_parsable_finalize;
Packit 4b6dd7
	klass->parse_xml = real_parse_xml;
Packit 4b6dd7
	klass->parse_json = real_parse_json;
Packit 4b6dd7
	klass->get_content_type = get_content_type;
Packit 4b6dd7
Packit 4b6dd7
	/**
Packit 4b6dd7
	 * GDataParsable:constructed-from-xml:
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Specifies whether the object was constructed by parsing XML or manually.
Packit 4b6dd7
	 *
Packit 4b6dd7
	 * Since: 0.7.0
Packit 4b6dd7
	 */
Packit 4b6dd7
	g_object_class_install_property (gobject_class, PROP_CONSTRUCTED_FROM_XML,
Packit 4b6dd7
	                                 g_param_spec_boolean ("constructed-from-xml",
Packit 4b6dd7
	                                                       "Constructed from XML?",
Packit 4b6dd7
	                                                       "Specifies whether the object was constructed by parsing XML or manually.",
Packit 4b6dd7
	                                                       FALSE,
Packit 4b6dd7
	                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_parsable_init (GDataParsable *self)
Packit 4b6dd7
{
Packit 4b6dd7
	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PARSABLE, GDataParsablePrivate);
Packit 4b6dd7
Packit 4b6dd7
	self->priv->extra_xml = g_string_new ("");
Packit 4b6dd7
	self->priv->extra_namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
Packit 4b6dd7
Packit 4b6dd7
	self->priv->extra_json = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) json_node_free);
Packit 4b6dd7
Packit 4b6dd7
	self->priv->constructed_from_xml = FALSE;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_parsable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	switch (property_id) {
Packit 4b6dd7
		case PROP_CONSTRUCTED_FROM_XML:
Packit 4b6dd7
			g_value_set_boolean (value, priv->constructed_from_xml);
Packit 4b6dd7
			break;
Packit 4b6dd7
		default:
Packit 4b6dd7
			/* We don't have any other property... */
Packit 4b6dd7
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
Packit 4b6dd7
			break;
Packit 4b6dd7
	}
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_parsable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	switch (property_id) {
Packit 4b6dd7
		case PROP_CONSTRUCTED_FROM_XML:
Packit 4b6dd7
			priv->constructed_from_xml = g_value_get_boolean (value);
Packit 4b6dd7
			break;
Packit 4b6dd7
		default:
Packit 4b6dd7
			/* We don't have any other property... */
Packit 4b6dd7
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
Packit 4b6dd7
			break;
Packit 4b6dd7
	}
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
gdata_parsable_finalize (GObject *object)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsablePrivate *priv = GDATA_PARSABLE (object)->priv;
Packit 4b6dd7
Packit 4b6dd7
	g_string_free (priv->extra_xml, TRUE);
Packit 4b6dd7
	g_hash_table_destroy (priv->extra_namespaces);
Packit 4b6dd7
Packit 4b6dd7
	g_hash_table_destroy (priv->extra_json);
Packit 4b6dd7
Packit 4b6dd7
	/* Chain up to the parent class */
Packit 4b6dd7
	G_OBJECT_CLASS (gdata_parsable_parent_class)->finalize (object);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static gboolean
Packit 4b6dd7
real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	xmlBuffer *buffer;
Packit 4b6dd7
	xmlNs **namespaces, **namespace;
Packit 4b6dd7
Packit 4b6dd7
	/* Unhandled XML */
Packit 4b6dd7
	buffer = xmlBufferCreate ();
Packit 4b6dd7
	xmlNodeDump (buffer, doc, node, 0, 0);
Packit 4b6dd7
	g_string_append (parsable->priv->extra_xml, (gchar*) xmlBufferContent (buffer));
Packit 4b6dd7
	g_debug ("Unhandled XML in %s: %s", G_OBJECT_TYPE_NAME (parsable), (gchar*) xmlBufferContent (buffer));
Packit 4b6dd7
	xmlBufferFree (buffer);
Packit 4b6dd7
Packit 4b6dd7
	/* Get the namespaces */
Packit 4b6dd7
	namespaces = xmlGetNsList (doc, node);
Packit 4b6dd7
	if (namespaces == NULL)
Packit 4b6dd7
		return TRUE;
Packit 4b6dd7
Packit 4b6dd7
	for (namespace = namespaces; *namespace != NULL; namespace++) {
Packit 4b6dd7
		if ((*namespace)->prefix != NULL) {
Packit 4b6dd7
			/* NOTE: These two g_strdup()s leak, but it's probably acceptable, given that it saves us
Packit 4b6dd7
			 * g_strdup()ing every other namespace we put in @extra_namespaces. */
Packit 4b6dd7
			g_hash_table_insert (parsable->priv->extra_namespaces,
Packit 4b6dd7
			                     g_strdup ((gchar*) ((*namespace)->prefix)),
Packit 4b6dd7
			                     g_strdup ((gchar*) ((*namespace)->href)));
Packit 4b6dd7
		}
Packit 4b6dd7
	}
Packit 4b6dd7
	xmlFree (namespaces);
Packit 4b6dd7
Packit 4b6dd7
	return TRUE;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/* Extract the member node. This would be a lot easier if JsonReader had an API to return
Packit 4b6dd7
 * the current node (regardless of whether it's a value, object or array). FIXME: bgo#707100. */
Packit 4b6dd7
static JsonNode * /* transfer full */
Packit 4b6dd7
_json_reader_dup_current_node (JsonReader *reader)
Packit 4b6dd7
{
Packit 4b6dd7
	JsonNode *value;
Packit 4b6dd7
Packit 4b6dd7
	if (json_reader_is_value (reader) == TRUE) {
Packit 4b6dd7
		/* Value nodes are easy. Well, ignoring the complication of nulls. */
Packit 4b6dd7
		if (json_reader_get_null_value (reader) == TRUE) {
Packit 4b6dd7
			value = json_node_new (JSON_NODE_NULL);
Packit 4b6dd7
		} else {
Packit 4b6dd7
			value = json_node_copy (json_reader_get_value (reader));
Packit 4b6dd7
		}
Packit 4b6dd7
	} else if (json_reader_is_object (reader) == TRUE) {
Packit 4b6dd7
		/* Object nodes require deep copies. */
Packit 4b6dd7
		gint i;
Packit 4b6dd7
		gchar **members;
Packit 4b6dd7
		JsonObject *obj;
Packit 4b6dd7
Packit 4b6dd7
		obj = json_object_new ();
Packit 4b6dd7
Packit 4b6dd7
		for (i = 0, members = json_reader_list_members (reader); members[i] != NULL; i++) {
Packit 4b6dd7
			json_reader_read_member (reader, members[i]);
Packit 4b6dd7
			json_object_set_member (obj, members[i], _json_reader_dup_current_node (reader));
Packit 4b6dd7
			json_reader_end_member (reader);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		g_strfreev (members);
Packit 4b6dd7
Packit 4b6dd7
		value = json_node_new (JSON_NODE_OBJECT);
Packit 4b6dd7
		json_node_take_object (value, obj);
Packit 4b6dd7
	} else if (json_reader_is_array (reader) == TRUE) {
Packit 4b6dd7
		/* Array nodes require deep copies. */
Packit 4b6dd7
		gint i, elements;
Packit 4b6dd7
		JsonArray *arr;
Packit 4b6dd7
Packit 4b6dd7
		arr = json_array_new ();
Packit 4b6dd7
Packit 4b6dd7
		for (i = 0, elements = json_reader_count_elements (reader); i < elements; i++) {
Packit 4b6dd7
			json_reader_read_element (reader, i);
Packit 4b6dd7
			json_array_add_element (arr, _json_reader_dup_current_node (reader));
Packit 4b6dd7
			json_reader_end_element (reader);
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		value = json_node_new (JSON_NODE_ARRAY);
Packit 4b6dd7
		json_node_take_array (value, arr);
Packit 4b6dd7
	} else {
Packit 4b6dd7
		/* Uh-oh. */
Packit 4b6dd7
		g_assert_not_reached ();
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	return value;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static gboolean
Packit 4b6dd7
real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	gchar *json, *member_name;
Packit 4b6dd7
	JsonGenerator *generator;
Packit 4b6dd7
	JsonNode *value;
Packit 4b6dd7
Packit 4b6dd7
	/* Unhandled JSON member. Save it and its value to ->extra_xml so that it's not lost if we
Packit 4b6dd7
	 * re-upload this Parsable to the server. */
Packit 4b6dd7
	member_name = g_strdup (json_reader_get_member_name (reader));
Packit 4b6dd7
	g_assert (member_name != NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Extract a copy of the current node. */
Packit 4b6dd7
	value = _json_reader_dup_current_node (reader);
Packit 4b6dd7
	g_assert (value != NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Serialise the value for debugging. */
Packit 4b6dd7
	generator = json_generator_new ();
Packit 4b6dd7
	json_generator_set_root (generator, value);
Packit 4b6dd7
Packit 4b6dd7
	json = json_generator_to_data (generator, NULL);
Packit 4b6dd7
	g_debug ("Unhandled JSON member ‘%s’ in %s: %s", member_name, G_OBJECT_TYPE_NAME (parsable), json);
Packit 4b6dd7
	g_free (json);
Packit 4b6dd7
Packit 4b6dd7
	g_object_unref (generator);
Packit 4b6dd7
Packit 4b6dd7
	/* Save the value. Transfer ownership of the member_name and value. */
Packit 4b6dd7
	g_hash_table_replace (parsable->priv->extra_json, (gpointer) member_name, (gpointer) value);
Packit 4b6dd7
Packit 4b6dd7
	return TRUE;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static const gchar *
Packit 4b6dd7
get_content_type (void) {
Packit 4b6dd7
	return "application/atom+xml";
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_parsable_new_from_xml:
Packit 4b6dd7
 * @parsable_type: the type of the class represented by the XML
Packit 4b6dd7
 * @xml: the XML for just the parsable object, with full namespace declarations
Packit 4b6dd7
 * @length: the length of @xml, or -1
Packit 4b6dd7
 * @error: a #GError, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @xml.
Packit 4b6dd7
 *
Packit 4b6dd7
 * An object of the given @parsable_type is created, and its <function>pre_parse_xml</function>, <function>parse_xml</function> and
Packit 4b6dd7
 * <function>post_parse_xml</function> class functions called on the XML tree obtained from @xml. <function>pre_parse_xml</function> and
Packit 4b6dd7
 * <function>post_parse_xml</function> are called once each on the root node of the tree, while <function>parse_xml</function> is called for
Packit 4b6dd7
 * each of the child nodes of the root node.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If @length is -1, @xml will be assumed to be null-terminated.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.4.0
Packit 4b6dd7
 */
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
gdata_parsable_new_from_xml (GType parsable_type, const gchar *xml, gint length, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (xml != NULL && *xml != '\0', NULL);
Packit 4b6dd7
	g_return_val_if_fail (length >= -1, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	return _gdata_parsable_new_from_xml (parsable_type, xml, length, NULL, error);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
_gdata_parsable_new_from_xml (GType parsable_type, const gchar *xml, gint length, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	xmlDoc *doc;
Packit 4b6dd7
	xmlNode *node;
Packit 4b6dd7
	GDataParsable *parsable;
Packit 4b6dd7
	static gboolean libxml_initialised = FALSE;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (xml != NULL && *xml != '\0', NULL);
Packit 4b6dd7
	g_return_val_if_fail (length >= -1, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Set up libxml. We do this here to avoid introducing a libgdata setup function, which would be unnecessary hassle. This is the only place
Packit 4b6dd7
	 * that libxml can be initialised in the library. */
Packit 4b6dd7
	if (libxml_initialised == FALSE) {
Packit 4b6dd7
		/* 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
Packit 4b6dd7
		 * libxml, which cuts down on strdup() calls dramatically. */
Packit 4b6dd7
		xmlMemSetup ((xmlFreeFunc) g_free, (xmlMallocFunc) g_malloc, (xmlReallocFunc) g_realloc, (xmlStrdupFunc) g_strdup);
Packit 4b6dd7
		libxml_initialised = TRUE;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	if (length == -1)
Packit 4b6dd7
		length = strlen (xml);
Packit 4b6dd7
Packit 4b6dd7
	/* Parse the XML */
Packit 4b6dd7
	doc = xmlReadMemory (xml, length, "/dev/null", NULL, 0);
Packit 4b6dd7
	if (doc == NULL) {
Packit 4b6dd7
		xmlError *xml_error = xmlGetLastError ();
Packit 4b6dd7
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
Packit 4b6dd7
		             /* Translators: the parameter is an error message */
Packit 4b6dd7
		             _("Error parsing XML: %s"),
Packit 4b6dd7
		             (xml_error != NULL) ? xml_error->message : NULL);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Get the root element */
Packit 4b6dd7
	node = xmlDocGetRootElement (doc);
Packit 4b6dd7
	if (node == NULL) {
Packit 4b6dd7
		/* XML document's empty */
Packit 4b6dd7
		xmlFreeDoc (doc);
Packit 4b6dd7
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
Packit 4b6dd7
		             _("Error parsing XML: %s"),
Packit 4b6dd7
		             /* Translators: this is a dummy error message to be substituted into "Error parsing XML: %s". */
Packit 4b6dd7
		             _("Empty document."));
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	parsable = _gdata_parsable_new_from_xml_node (parsable_type, doc, node, user_data, error);
Packit 4b6dd7
	xmlFreeDoc (doc);
Packit 4b6dd7
Packit 4b6dd7
	return parsable;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
_gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsable *parsable;
Packit 4b6dd7
	GDataParsableClass *klass;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (doc != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (node != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);
Packit 4b6dd7
Packit 4b6dd7
	klass = GDATA_PARSABLE_GET_CLASS (parsable);
Packit 4b6dd7
	if (klass->parse_xml == NULL) {
Packit 4b6dd7
		g_object_unref (parsable);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_assert (klass->element_name != NULL);
Packit 4b6dd7
	/* TODO: See gdata-documents-entry.c:260 for an example of where the code below doesn't work */
Packit 4b6dd7
	/*if (xmlStrcmp (node->name, (xmlChar*) klass->element_name) != 0 ||
Packit 4b6dd7
	    (node->ns != NULL && xmlStrcmp (node->ns->prefix, (xmlChar*) klass->element_namespace) != 0)) {
Packit 4b6dd7
		* No <entry> element (required) *
Packit 4b6dd7
		gdata_parser_error_required_element_missing (klass->element_name, "root", error);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}*/
Packit 4b6dd7
Packit 4b6dd7
	/* Call the pre-parse function first */
Packit 4b6dd7
	if (klass->pre_parse_xml != NULL &&
Packit 4b6dd7
	    klass->pre_parse_xml (parsable, doc, node, user_data, error) == FALSE) {
Packit 4b6dd7
		g_object_unref (parsable);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Parse each child element */
Packit 4b6dd7
	node = node->children;
Packit 4b6dd7
	while (node != NULL) {
Packit 4b6dd7
		if (klass->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
Packit 4b6dd7
			g_object_unref (parsable);
Packit 4b6dd7
			return NULL;
Packit 4b6dd7
		}
Packit 4b6dd7
		node = node->next;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Call the post-parse function */
Packit 4b6dd7
	if (klass->post_parse_xml != NULL &&
Packit 4b6dd7
	    klass->post_parse_xml (parsable, user_data, error) == FALSE) {
Packit 4b6dd7
		g_object_unref (parsable);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	return parsable;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_parsable_new_from_json:
Packit 4b6dd7
 * @parsable_type: the type of the class represented by the JSON
Packit 4b6dd7
 * @json: the JSON for just the parsable object
Packit 4b6dd7
 * @length: the length of @json, or -1
Packit 4b6dd7
 * @error: a #GError, or %NULL
Packit 4b6dd7
 *
Packit 4b6dd7
 * Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @json.
Packit 4b6dd7
 *
Packit 4b6dd7
 * An object of the given @parsable_type is created, and its <function>parse_json</function> and
Packit 4b6dd7
 * <function>post_parse_json</function> class functions called on the JSON node obtained from @json.
Packit 4b6dd7
 * <function>post_parse_json</function> is called once on the root node, while <function>parse_json</function> is called for
Packit 4b6dd7
 * each of the node's members.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If @length is -1, @json will be assumed to be nul-terminated.
Packit 4b6dd7
 *
Packit 4b6dd7
 * If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.15.0
Packit 4b6dd7
 */
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (json != NULL && *json != '\0', NULL);
Packit 4b6dd7
	g_return_val_if_fail (length >= -1, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	return _gdata_parsable_new_from_json (parsable_type, json, length, NULL, error);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
_gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	JsonParser *parser;
Packit 4b6dd7
	JsonReader *reader;
Packit 4b6dd7
	GDataParsable *parsable;
Packit 4b6dd7
	GError *child_error = NULL;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (json != NULL && *json != '\0', NULL);
Packit 4b6dd7
	g_return_val_if_fail (length >= -1, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	if (length == -1)
Packit 4b6dd7
		length = strlen (json);
Packit 4b6dd7
Packit 4b6dd7
	parser = json_parser_new ();
Packit 4b6dd7
	if (!json_parser_load_from_data (parser, json, length, &child_error)) {
Packit 4b6dd7
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
Packit 4b6dd7
		             /* Translators: the parameter is an error message */
Packit 4b6dd7
		             _("Error parsing JSON: %s"), child_error->message);
Packit 4b6dd7
		g_error_free (child_error);
Packit 4b6dd7
		g_object_unref (parser);
Packit 4b6dd7
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	reader = json_reader_new (json_parser_get_root (parser));
Packit 4b6dd7
	parsable = _gdata_parsable_new_from_json_node (parsable_type, reader, user_data, error);
Packit 4b6dd7
Packit 4b6dd7
	g_object_unref (reader);
Packit 4b6dd7
	g_object_unref (parser);
Packit 4b6dd7
Packit 4b6dd7
	return parsable;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
GDataParsable *
Packit 4b6dd7
_gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, gpointer user_data, GError **error)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsable *parsable;
Packit 4b6dd7
	GDataParsableClass *klass;
Packit 4b6dd7
	gint i;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
Packit 4b6dd7
	g_return_val_if_fail (reader != NULL, NULL);
Packit 4b6dd7
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Indicator property which allows distinguishing between locally created and server based objects
Packit 4b6dd7
	 * as it is used for non-XML tasks, and adding another one for JSON would be a bit pointless. */
Packit 4b6dd7
	parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);
Packit 4b6dd7
Packit 4b6dd7
	klass = GDATA_PARSABLE_GET_CLASS (parsable);
Packit 4b6dd7
	g_assert (klass->parse_json != NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Check that the outermost node is an object. */
Packit 4b6dd7
	if (json_reader_is_object (reader) == FALSE) {
Packit 4b6dd7
		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
Packit 4b6dd7
		             /* Translators: the parameter is an error message */
Packit 4b6dd7
		             _("Error parsing JSON: %s"),
Packit 4b6dd7
		             _("Outermost JSON node is not an object."));
Packit 4b6dd7
		g_object_unref (parsable);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Parse each child member. This assumes the outermost node is an object. */
Packit 4b6dd7
	for (i = 0; i < json_reader_count_members (reader); i++) {
Packit 4b6dd7
		g_return_val_if_fail (json_reader_read_element (reader, i), NULL);
Packit 4b6dd7
Packit 4b6dd7
		if (klass->parse_json (parsable, reader, user_data, error) == FALSE) {
Packit 4b6dd7
			json_reader_end_element (reader);
Packit 4b6dd7
			g_object_unref (parsable);
Packit 4b6dd7
			return NULL;
Packit 4b6dd7
		}
Packit 4b6dd7
Packit 4b6dd7
		json_reader_end_element (reader);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Call the post-parse function */
Packit 4b6dd7
	if (klass->post_parse_json != NULL &&
Packit 4b6dd7
	    klass->post_parse_json (parsable, user_data, error) == FALSE) {
Packit 4b6dd7
		g_object_unref (parsable);
Packit 4b6dd7
		return NULL;
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	return parsable;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_parsable_get_content_type:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 *
Packit 4b6dd7
 * Returns the content type upon which the #GDataParsable is built. For example, `application/atom+xml` or `application/json`.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the parsable's content type
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.17.7
Packit 4b6dd7
 */
Packit 4b6dd7
const gchar *
Packit 4b6dd7
gdata_parsable_get_content_type (GDataParsable *self)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsableClass *klass;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
Packit 4b6dd7
Packit 4b6dd7
	klass = GDATA_PARSABLE_GET_CLASS (self);
Packit 4b6dd7
	g_assert (klass->get_content_type != NULL);
Packit 4b6dd7
Packit 4b6dd7
	return klass->get_content_type ();
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static void
Packit 4b6dd7
build_namespaces_cb (gchar *prefix, gchar *href, GString *output)
Packit 4b6dd7
{
Packit 4b6dd7
	g_string_append_printf (output, " xmlns:%s='%s'", prefix, href);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
static gboolean
Packit 4b6dd7
filter_namespaces_cb (gchar *prefix, gchar *href, GHashTable *canonical_namespaces)
Packit 4b6dd7
{
Packit 4b6dd7
	if (g_hash_table_lookup (canonical_namespaces, prefix) != NULL)
Packit 4b6dd7
		return TRUE;
Packit 4b6dd7
	return FALSE;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_parsable_get_xml:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 *
Packit 4b6dd7
 * Builds an XML representation of the #GDataParsable in its current state, such that it could be inserted on the server. The XML is guaranteed
Packit 4b6dd7
 * to have all its namespaces declared properly in a self-contained fashion, and is valid for stand-alone use.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the object's XML; free with g_free()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.4.0
Packit 4b6dd7
 */
Packit 4b6dd7
gchar *
Packit 4b6dd7
gdata_parsable_get_xml (GDataParsable *self)
Packit 4b6dd7
{
Packit 4b6dd7
	GString *xml_string;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
Packit 4b6dd7
Packit 4b6dd7
	xml_string = g_string_sized_new (1000);
Packit 4b6dd7
	g_string_append (xml_string, "");
Packit 4b6dd7
	_gdata_parsable_get_xml (self, xml_string, TRUE);
Packit 4b6dd7
	return g_string_free (xml_string, FALSE);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/*
Packit 4b6dd7
 * _gdata_parsable_get_xml:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 * @xml_string: a #GString to build the XML in
Packit 4b6dd7
 * @declare_namespaces: %TRUE if all the namespaces used in the outputted XML should be declared in the opening tag of the root element,
Packit 4b6dd7
 * %FALSE otherwise
Packit 4b6dd7
 *
Packit 4b6dd7
 * Builds an XML representation of the #GDataParsable in its current state, such that it could be inserted on the server. If @declare_namespaces is
Packit 4b6dd7
 * %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
Packit 4b6dd7
 * @declare_namespaces is %FALSE, none of the used namespaces are declared, and the XML is suitable for insertion into a larger XML tree.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the object's XML; free with g_free()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.4.0
Packit 4b6dd7
 */
Packit 4b6dd7
void
Packit 4b6dd7
_gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean declare_namespaces)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsableClass *klass;
Packit 4b6dd7
	guint length;
Packit 4b6dd7
	GHashTable *namespaces = NULL; /* shut up, gcc */
Packit 4b6dd7
Packit 4b6dd7
	g_return_if_fail (GDATA_IS_PARSABLE (self));
Packit 4b6dd7
	g_return_if_fail (xml_string != NULL);
Packit 4b6dd7
Packit 4b6dd7
	klass = GDATA_PARSABLE_GET_CLASS (self);
Packit 4b6dd7
	g_assert (klass->element_name != NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Get the namespaces the class uses */
Packit 4b6dd7
	if (declare_namespaces == TRUE && klass->get_namespaces != NULL) {
Packit 4b6dd7
		namespaces = g_hash_table_new (g_str_hash, g_str_equal);
Packit 4b6dd7
		klass->get_namespaces (self, namespaces);
Packit 4b6dd7
Packit 4b6dd7
		/* Remove any duplicate extra namespaces */
Packit 4b6dd7
		g_hash_table_foreach_remove (self->priv->extra_namespaces, (GHRFunc) filter_namespaces_cb, namespaces);
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	/* Build up the namespace list */
Packit 4b6dd7
	if (klass->element_namespace != NULL)
Packit 4b6dd7
		g_string_append_printf (xml_string, "<%s:%s", klass->element_namespace, klass->element_name);
Packit 4b6dd7
	else
Packit 4b6dd7
		g_string_append_printf (xml_string, "<%s", klass->element_name);
Packit 4b6dd7
Packit 4b6dd7
	/* We only include the normal namespaces if we're not at the top level of XML building */
Packit 4b6dd7
	if (declare_namespaces == TRUE) {
Packit 4b6dd7
		g_string_append (xml_string, " xmlns='http://www.w3.org/2005/Atom'");
Packit 4b6dd7
		if (namespaces != NULL) {
Packit 4b6dd7
			g_hash_table_foreach (namespaces, (GHFunc) build_namespaces_cb, xml_string);
Packit 4b6dd7
			g_hash_table_destroy (namespaces);
Packit 4b6dd7
		}
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	g_hash_table_foreach (self->priv->extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);
Packit 4b6dd7
Packit 4b6dd7
	/* Add anything the class thinks is suitable */
Packit 4b6dd7
	if (klass->pre_get_xml != NULL)
Packit 4b6dd7
		klass->pre_get_xml (self, xml_string);
Packit 4b6dd7
	g_string_append_c (xml_string, '>');
Packit 4b6dd7
Packit 4b6dd7
	/* Store the length before we close the opening tag, so we can determine whether to self-close later on */
Packit 4b6dd7
	length = xml_string->len;
Packit 4b6dd7
Packit 4b6dd7
	/* Add the rest of the XML */
Packit 4b6dd7
	if (klass->get_xml != NULL)
Packit 4b6dd7
		klass->get_xml (self, xml_string);
Packit 4b6dd7
Packit 4b6dd7
	/* Any extra XML? */
Packit 4b6dd7
	if (self->priv->extra_xml != NULL && self->priv->extra_xml->str != NULL)
Packit 4b6dd7
		g_string_append (xml_string, self->priv->extra_xml->str);
Packit 4b6dd7
Packit 4b6dd7
	/* Close the element; either by self-closing the opening tag, or by writing out a closing tag */
Packit 4b6dd7
	if (xml_string->len == length)
Packit 4b6dd7
		g_string_overwrite (xml_string, length - 1, "/>");
Packit 4b6dd7
	else if (klass->element_namespace != NULL)
Packit 4b6dd7
		g_string_append_printf (xml_string, "</%s:%s>", klass->element_namespace, klass->element_name);
Packit 4b6dd7
	else
Packit 4b6dd7
		g_string_append_printf (xml_string, "</%s>", klass->element_name);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/**
Packit 4b6dd7
 * gdata_parsable_get_json:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 *
Packit 4b6dd7
 * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server. The JSON
Packit 4b6dd7
 * is valid for stand-alone use.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: the object's JSON; free with g_free()
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.15.0
Packit 4b6dd7
 */
Packit 4b6dd7
gchar *
Packit 4b6dd7
gdata_parsable_get_json (GDataParsable *self)
Packit 4b6dd7
{
Packit 4b6dd7
	JsonGenerator *generator;
Packit 4b6dd7
	JsonBuilder *builder;
Packit 4b6dd7
	JsonNode *root;
Packit 4b6dd7
	gchar *output;
Packit 4b6dd7
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
Packit 4b6dd7
Packit 4b6dd7
	/* Build the JSON tree. */
Packit 4b6dd7
	builder = json_builder_new ();
Packit 4b6dd7
	_gdata_parsable_get_json (self, builder);
Packit 4b6dd7
	root = json_builder_get_root (builder);
Packit 4b6dd7
	g_object_unref (builder);
Packit 4b6dd7
Packit 4b6dd7
	/* Serialise it to a string. */
Packit 4b6dd7
	generator = json_generator_new ();
Packit 4b6dd7
	json_generator_set_root (generator, root);
Packit 4b6dd7
	output = json_generator_to_data (generator, NULL);
Packit 4b6dd7
	g_object_unref (generator);
Packit 4b6dd7
Packit 4b6dd7
	json_node_free (root);
Packit 4b6dd7
Packit 4b6dd7
	return output;
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/*
Packit 4b6dd7
 * _gdata_parsable_get_json:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 * @builder: a #JsonBuilder to build the JSON in
Packit 4b6dd7
 *
Packit 4b6dd7
 * Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.15.0
Packit 4b6dd7
 */
Packit 4b6dd7
void
Packit 4b6dd7
_gdata_parsable_get_json (GDataParsable *self, JsonBuilder *builder)
Packit 4b6dd7
{
Packit 4b6dd7
	GDataParsableClass *klass;
Packit 4b6dd7
	GHashTableIter iter;
Packit 4b6dd7
	gchar *member_name;
Packit 4b6dd7
	JsonNode *value;
Packit 4b6dd7
Packit 4b6dd7
	g_return_if_fail (GDATA_IS_PARSABLE (self));
Packit 4b6dd7
	g_return_if_fail (JSON_IS_BUILDER (builder));
Packit 4b6dd7
Packit 4b6dd7
	klass = GDATA_PARSABLE_GET_CLASS (self);
Packit 4b6dd7
Packit 4b6dd7
	json_builder_begin_object (builder);
Packit 4b6dd7
Packit 4b6dd7
	/* Add the JSON. */
Packit 4b6dd7
	if (klass->get_json != NULL)
Packit 4b6dd7
		klass->get_json (self, builder);
Packit 4b6dd7
Packit 4b6dd7
	/* Any extra JSON which we couldn't parse before? */
Packit 4b6dd7
	g_hash_table_iter_init (&iter, self->priv->extra_json);
Packit 4b6dd7
	while (g_hash_table_iter_next (&iter, (gpointer *) &member_name, (gpointer *) &value) == TRUE) {
Packit 4b6dd7
		json_builder_set_member_name (builder, member_name);
Packit 4b6dd7
		json_builder_add_value (builder, json_node_copy (value)); /* transfers ownership */
Packit 4b6dd7
	}
Packit 4b6dd7
Packit 4b6dd7
	json_builder_end_object (builder);
Packit 4b6dd7
}
Packit 4b6dd7
Packit 4b6dd7
/*
Packit 4b6dd7
 * _gdata_parsable_is_constructed_from_xml:
Packit 4b6dd7
 * @self: a #GDataParsable
Packit 4b6dd7
 *
Packit 4b6dd7
 * Returns the value of #GDataParsable:constructed-from-xml.
Packit 4b6dd7
 *
Packit 4b6dd7
 * Return value: %TRUE if the #GDataParsable was constructed from XML, %FALSE otherwise
Packit 4b6dd7
 *
Packit 4b6dd7
 * Since: 0.7.0
Packit 4b6dd7
 */
Packit 4b6dd7
gboolean
Packit 4b6dd7
_gdata_parsable_is_constructed_from_xml (GDataParsable *self)
Packit 4b6dd7
{
Packit 4b6dd7
	g_return_val_if_fail (GDATA_IS_PARSABLE (self), FALSE);
Packit 4b6dd7
	return self->priv->constructed_from_xml;
Packit 4b6dd7
}