diff --git a/ChangeLog b/ChangeLog index 01d67911c..18f3d5933 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,33 @@ -2007-10-03 Emmanuele Bassi +2007-10-08 Emmanuele Bassi + + Initial implementation of the UI definition files. (#424) + + * clutter/json/Makefile.am: + * clutter/json/*.[ch]: In-tree copy of JSON-GLib, a GLib-based + JSON parser/generator library. We use it in-tree because we might + need to change the API. Ideally, we'd depend on it. + + * clutter/clutter.h: + * clutter/clutter-script-private.h: + * clutter/clutter-script.[ch]: ClutterScript, the scenegraph + generator class. It parses JSON streams in form of buffers and + files and builds the scene. + + * clutter/clutter-debug.h: + * clutter/clutter-main.c: Add a "script" debug flag + + * clutter/Makefile.am: Build glue. + + * tests/Makefile.am: + * tests/test-script.c: Add a test case for the ClutterScript. + + * configure.ac: Depend on GLib 2.14, so we can use the + g_hash_table_get_key() and g_hash_table_get_values() functions + for the time being; we can probably reimplement those, but we + are going to need 2.14 anyway if we are going to implement a + list model using GSequence. + +2007-10-08 Emmanuele Bassi * tests/test-behave.c: Use the right return type for the event callbacks. diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 63a80b833..b48b42480 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -1,8 +1,8 @@ NULL = -SUBDIRS = cogl pango $(clutterbackend) +SUBDIRS = cogl pango json $(clutterbackend) -DIST_SUBDIRS = pango glx eglx eglnative cogl sdl +DIST_SUBDIRS = pango glx eglx eglnative cogl sdl json target = $(clutterbackend) @@ -17,6 +17,7 @@ INCLUDES = \ -I$(top_srcdir)/clutter/pango \ -I$(top_srcdir)/clutter/cogl \ -I$(top_srcdir)/clutter/cogl/@CLUTTER_COGL@ \ + -I$(top_srcdir)/clutter/json \ -DPREFIX=\""$(prefix)"\" \ -DLIBDIR=\""$(libdir)"\" \ -DDATADIR=\""$(datadir)"\" \ @@ -65,6 +66,7 @@ source_h = \ $(srcdir)/clutter-main.h \ $(srcdir)/clutter-media.h \ $(srcdir)/clutter-rectangle.h \ + $(srcdir)/clutter-script.h \ $(srcdir)/clutter-stage.h \ $(srcdir)/clutter-texture.h \ $(srcdir)/clutter-timeline.h \ @@ -153,6 +155,7 @@ source_c = \ clutter-texture.c \ clutter-timeline.c \ clutter-score.c \ + clutter-script.c \ clutter-timeout-pool.c \ clutter-util.c \ clutter-vbox.c \ @@ -162,13 +165,16 @@ source_h_priv = \ clutter-keysyms-table.h \ clutter-debug.h \ clutter-private.h \ + clutter-script-private.h \ $(NULL) -libclutter_@CLUTTER_FLAVOUR@_@CLUTTER_API_VERSION@_la_LIBADD = \ - $(CLUTTER_LIBS) pango/libpangoclutter.la \ - @CLUTTER_FLAVOUR@/libclutter-@CLUTTER_FLAVOUR@.la \ - cogl/@CLUTTER_COGL@/libclutter-cogl.la +libclutter_@CLUTTER_FLAVOUR@_@CLUTTER_API_VERSION@_la_LIBADD = \ + $(CLUTTER_LIBS) \ + pango/libpangoclutter.la \ + @CLUTTER_FLAVOUR@/libclutter-@CLUTTER_FLAVOUR@.la \ + cogl/@CLUTTER_COGL@/libclutter-cogl.la \ + json/libclutter-json.la libclutter_@CLUTTER_FLAVOUR@_@CLUTTER_API_VERSION@_la_SOURCES = \ $(source_c) $(source_h) $(source_h_priv) diff --git a/clutter/clutter-debug.h b/clutter/clutter-debug.h index b9a2bd241..5dd24f11c 100644 --- a/clutter/clutter-debug.h +++ b/clutter/clutter-debug.h @@ -17,7 +17,8 @@ typedef enum { CLUTTER_DEBUG_BEHAVIOUR = 1 << 7, CLUTTER_DEBUG_PANGO = 1 << 8, CLUTTER_DEBUG_BACKEND = 1 << 9, - CLUTTER_DEBUG_SCHEDULER = 1 << 10 + CLUTTER_DEBUG_SCHEDULER = 1 << 10, + CLUTTER_DEBUG_SCRIPT = 1 << 11 } ClutterDebugFlag; #ifdef CLUTTER_ENABLE_DEBUG diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index e21b4e71a..a7d2a0814 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -78,6 +78,7 @@ static const GDebugKey clutter_debug_keys[] = { { "pango", CLUTTER_DEBUG_PANGO }, { "backend", CLUTTER_DEBUG_BACKEND }, { "scheduler", CLUTTER_DEBUG_SCHEDULER }, + { "script", CLUTTER_DEBUG_SCRIPT }, }; #endif /* CLUTTER_ENABLE_DEBUG */ diff --git a/clutter/clutter-script-private.h b/clutter/clutter-script-private.h new file mode 100644 index 000000000..f2e8558f0 --- /dev/null +++ b/clutter/clutter-script-private.h @@ -0,0 +1,31 @@ +#ifndef __CLUTTER_SCRIPT_PRIVATE_H__ +#define __CLUTTER_SCRIPT_PRIVATE_H__ + +#include +#include "clutter-script.h" + +G_BEGIN_DECLS + +typedef GType (* GTypeGetFunc) (void); + +typedef struct { + gchar *class_name; + gchar *id; + + GList *properties; + + GType gtype; + GObject *object; +} ObjectInfo; + +typedef struct { + gchar *property_name; + GValue value; +} PropertyInfo; + +GObject *clutter_script_construct_object (ClutterScript *script, + ObjectInfo *info); + +G_END_DECLS + +#endif /* __CLUTTER_SCRIPT_PRIVATE_H__ */ diff --git a/clutter/clutter-script.c b/clutter/clutter-script.c new file mode 100644 index 000000000..7b9dbcd11 --- /dev/null +++ b/clutter/clutter-script.c @@ -0,0 +1,765 @@ +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include "clutter-actor.h" +#include "clutter-stage.h" +#include "clutter-container.h" + +#include "clutter-scriptable.h" +#include "clutter-script.h" +#include "clutter-script-private.h" + +#include "clutter-private.h" +#include "clutter-debug.h" + +#include "json/json-parser.h" + +#define CLUTTER_SCRIPT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_SCRIPT, ClutterScriptPrivate)) + +struct _ClutterScriptPrivate +{ + GHashTable *objects; + + guint last_merge_id; + + JsonParser *parser; + ObjectInfo *current; + + gchar *filename; + guint is_filename : 1; +}; + +G_DEFINE_TYPE (ClutterScript, clutter_script, G_TYPE_OBJECT); + +/* tries to map a name in camel case into the corresponding get_type() + * function, e.g.: + * + * ClutterRectangle -> clutter_rectangle_get_type + * ClutterCloneTexture -> clutter_clone_texture_get_type + * + * taken from GTK+, gtkbuilder.c + */ +static GType +resolve_type_lazily (const gchar *name) +{ + static GModule *module = NULL; + GTypeGetFunc func; + GString *symbol_name = g_string_new (""); + char c, *symbol; + int i; + GType gtype = G_TYPE_INVALID; + + if (!module) + module = g_module_open (NULL, 0); + + for (i = 0; name[i] != '\0'; i++) + { + c = name[i]; + /* skip if uppercase, first or previous is uppercase */ + if ((c == g_ascii_toupper (c) && + i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) || + (i > 2 && name[i] == g_ascii_toupper (name[i]) && + name[i-1] == g_ascii_toupper (name[i-1]) && + name[i-2] == g_ascii_toupper (name[i-2]))) + g_string_append_c (symbol_name, '_'); + g_string_append_c (symbol_name, g_ascii_tolower (c)); + } + g_string_append (symbol_name, "_get_type"); + + symbol = g_string_free (symbol_name, FALSE); + + if (g_module_symbol (module, symbol, (gpointer)&func)) + gtype = func (); + + g_free (symbol); + + return gtype; +} + +static void +warn_missing_attribute (ClutterScript *script, + const gchar *id, + const gchar *attribute) +{ + ClutterScriptPrivate *priv = script->priv; + + if (G_LIKELY (id)) + { + g_warning ("%s: %d: object `%s' has no `%s' attribute", + priv->is_filename ? priv->filename : "", + json_parser_get_current_line (priv->parser), + id, + attribute); + } + else + { + g_warning ("%s: %d: object has no `%s' attribute", + priv->is_filename ? priv->filename : "", + json_parser_get_current_line (priv->parser), + attribute); + } +} + +static void +warn_invalid_value (ClutterScript *script, + const gchar *attribute, + JsonNode *node) +{ + ClutterScriptPrivate *priv = script->priv; + + if (G_LIKELY (node)) + { + g_warning ("%s: %d: invalid value of type `%s' for attribute `%s'", + priv->is_filename ? priv->filename : "", + json_parser_get_current_line (priv->parser), + json_node_type_name (node), + attribute); + } + else + { + g_warning ("%s: %d: invalid value for attribute `%s'", + priv->is_filename ? priv->filename : "", + json_parser_get_current_line (priv->parser), + attribute); + } +} + +static PropertyInfo * +parse_member_to_property (ClutterScript *script, + ObjectInfo *info, + const gchar *name, + JsonNode *node) +{ + PropertyInfo *retval; + GValue value = { 0, }; + + retval = g_slice_new (PropertyInfo); + retval->property_name = g_strdup (name); + + switch (JSON_NODE_TYPE (node)) + { + case JSON_NODE_VALUE: + json_node_get_value (node, &value); + g_value_init (&retval->value, G_VALUE_TYPE (&value)); + g_value_copy (&value, &retval->value); + g_value_unset (&value); + break; + + case JSON_NODE_OBJECT: + break; + + case JSON_NODE_ARRAY: + if (strcmp (name, "geometry") == 0) + { + JsonArray *array = json_node_get_array (node); + JsonNode *val; + gint i; + ClutterGeometry geom = { 0, }; + + /* this is quite evil indeed */ + for (i = 0; i < json_array_get_length (array); i++) + { + val = json_array_get_element (array, i); + switch (i) + { + case 0: geom.x = json_node_get_int (val); break; + case 1: geom.y = json_node_get_int (val); break; + case 2: geom.width = json_node_get_int (val); break; + case 3: geom.height = json_node_get_int (val); break; + } + } + + g_value_init (&retval->value, CLUTTER_TYPE_GEOMETRY); + g_value_set_boxed (&retval->value, &geom); + } + else if (strcmp (name, "children") == 0) + { + JsonArray *array = json_node_get_array (node); + JsonNode *val; + gint i, array_len; + GList *children; + + children = NULL; + array_len = json_array_get_length (array); + for (i = 0; i < array_len; i++) + { + JsonObject *object; + + val = json_array_get_element (array, i); + + switch (JSON_NODE_TYPE (val)) + { + case JSON_NODE_OBJECT: + object = json_node_get_object (val); + + if (json_object_has_member (object, "id") && + json_object_has_member (object, "type")) + { + JsonNode *id = json_object_get_member (object, "id"); + + children = g_list_prepend (children, + json_node_dup_string (id)); + } + break; + + case JSON_NODE_VALUE: + if (json_node_get_string (val)) + children = g_list_prepend (children, + json_node_dup_string (val)); + break; + + default: + warn_invalid_value (script, "children", val); + break; + } + } + + g_value_init (&retval->value, G_TYPE_POINTER); + g_value_set_pointer (&retval->value, children); + } + break; + + case JSON_NODE_NULL: + break; + } + + return retval; +} + +static void +json_object_end (JsonParser *parser, + JsonObject *object, + gpointer user_data) +{ + ClutterScript *script = user_data; + ClutterScriptPrivate *priv = script->priv; + ObjectInfo *oinfo; + JsonNode *val; + GList *members, *l; + + if (!json_object_has_member (object, "id")) + return; + + if (!json_object_has_member (object, "type")) + { + val = json_object_get_member (object, "id"); + + warn_missing_attribute (script, json_node_get_string (val), "type"); + return; + } + + oinfo = g_slice_new0 (ObjectInfo); + + val = json_object_get_member (object, "id"); + oinfo->id = json_node_dup_string (val); + + val = json_object_get_member (object, "type"); + oinfo->class_name = json_node_dup_string (val); + + members = json_object_get_members (object); + for (l = members; l; l = l->next) + { + const gchar *name = l->data; + + val = json_object_get_member (object, name); + + if (strcmp (name, "id") == 0 || strcmp (name, "type") == 0) + continue; + else + { + PropertyInfo *pinfo; + + pinfo = parse_member_to_property (script, oinfo, name, val); + oinfo->properties = g_list_prepend (oinfo->properties, pinfo); + + CLUTTER_NOTE (SCRIPT, "Added property `%s' (type:%s) for class `%s'", + pinfo->property_name, + g_type_name (G_VALUE_TYPE (&pinfo->value)), + oinfo->class_name); + } + } + g_list_free (members); + + CLUTTER_NOTE (SCRIPT, "Added object `%s' (type:%s) with %d properties", + oinfo->id, + oinfo->class_name, + g_list_length (oinfo->properties)); + + g_hash_table_replace (priv->objects, g_strdup (oinfo->id), oinfo); +} + +static gboolean +translate_property (const gchar *name, + const GValue *src, + GValue *dest) +{ + if (strcmp (name, "color") == 0) + { + ClutterColor color = { 0, }; + const gchar *color_str; + + if (G_VALUE_HOLDS (src, G_TYPE_STRING)) + color_str = g_value_get_string (src); + else + color_str = NULL; + + clutter_color_parse (color_str, &color); + + g_value_init (dest, CLUTTER_TYPE_COLOR); + g_value_set_boxed (dest, &color); + + return TRUE; + } + else if (strcmp (name, "pixbuf") == 0) + { + GdkPixbuf *pixbuf = NULL; + const gchar *path; + + if (G_VALUE_HOLDS (src, G_TYPE_STRING)) + path = g_value_get_string (src); + else + path = NULL; + + if (path && g_path_is_absolute (path)) + { + GError *error = NULL; + + pixbuf = gdk_pixbuf_new_from_file (path, &error); + if (error) + { + g_warning ("Unable to open pixbuf at path `%s': %s", + path, + error->message); + g_error_free (error); + } + } + + g_value_init (dest, GDK_TYPE_PIXBUF); + g_value_set_object (dest, pixbuf); + + return TRUE; + } + + return FALSE; +} + +static void +translate_properties (ClutterScript *script, + ObjectInfo *oinfo, + guint *n_params, + GParameter **params) +{ + GList *l; + GParamSpec *pspec; + GObjectClass *oclass; + GArray *parameters; + + oclass = g_type_class_ref (oinfo->gtype); + g_assert (oclass != NULL); + + parameters = g_array_new (FALSE, FALSE, sizeof (GParameter)); + + for (l = oinfo->properties; l; l = l->next) + { + PropertyInfo *pinfo = l->data; + GParameter param = { NULL }; + + if (strcmp (pinfo->property_name, "children") == 0) + continue; + + pspec = g_object_class_find_property (oclass, pinfo->property_name); + if (!pspec) + { + g_warning ("Unknown property `%s' for class `%s'", + pinfo->property_name, + g_type_name (oinfo->gtype)); + continue; + } + + param.name = pinfo->property_name; + if (!translate_property (param.name, &pinfo->value, ¶m.value)) + { + g_value_init (¶m.value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_value_copy (&pinfo->value, ¶m.value); + } + + g_array_append_val (parameters, param); + } + + *n_params = parameters->len; + *params = (GParameter *) g_array_free (parameters, FALSE); + + g_type_class_unref (oclass); +} + +static void +add_children (ClutterScript *script, + ClutterContainer *container, + GList *children) +{ + GObject *object; + GList *l; + + for (l = children; l != NULL; l = l->next) + { + const gchar *name = l->data; + + object = clutter_script_get_object (script, name); + if (!object) + { + ObjectInfo *oinfo; + + oinfo = g_hash_table_lookup (script->priv->objects, name); + if (oinfo) + object = clutter_script_construct_object (script, oinfo); + else + continue; + } + + CLUTTER_NOTE (SCRIPT, "Adding children `%s' to actor of type `%s'", + name, + g_type_name (G_OBJECT_TYPE (container))); + + clutter_container_add_actor (container, CLUTTER_ACTOR (object)); + } +} + +static GObject * +construct_stage (ClutterScript *script, + ObjectInfo *oinfo) +{ + GObjectClass *oclass = g_type_class_ref (CLUTTER_TYPE_STAGE); + GList *l; + + if (oinfo->object) + return oinfo->object; + + oinfo->object = G_OBJECT (clutter_stage_get_default ()); + + for (l = oinfo->properties; l; l = l->next) + { + PropertyInfo *pinfo = l->data; + const gchar *name = pinfo->property_name; + GParamSpec *pspec; + GValue value = { 0, }; + + /* "children" is a fake property: we use it so we can construct + * the list of children of a given container + */ + if (strcmp (name, "children") == 0) + { + GList *children = g_value_get_pointer (&pinfo->value); + + /* we know ClutterStage is a ClutterContainer */ + add_children (script, CLUTTER_CONTAINER (oinfo->object), children); + + /* unset, so we don't leak it later */ + g_list_foreach (children, (GFunc) g_free, NULL); + g_list_free (children); + g_value_set_pointer (&pinfo->value, NULL); + + continue; + } + + pspec = g_object_class_find_property (oclass, name); + if (!pspec) + { + g_warning ("Unknown property `%s' for class `ClutterStage'", + name); + continue; + } + + if (!translate_property (name, &pinfo->value, &value)) + { + g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec)); + g_value_copy (&pinfo->value, &value); + } + + g_object_set_property (oinfo->object, name, &value); + + g_value_unset (&value); + } + + g_type_class_unref (oclass); + + g_object_set_data_full (oinfo->object, "clutter-script-name", + g_strdup (oinfo->id), + g_free); + + return oinfo->object; +} + +GObject * +clutter_script_construct_object (ClutterScript *script, + ObjectInfo *oinfo) +{ + GType gtype; + guint n_params, i; + GParameter *params; + + if (oinfo->object) + return oinfo->object; + + gtype = resolve_type_lazily (oinfo->class_name); + if (gtype == G_TYPE_INVALID) + return NULL; + + /* the stage is a special case: it's a singleton, it cannot + * be created by the user and it's owned by the backend. hence, + * we cannot follow the usual pattern here + */ + if (g_type_is_a (gtype, CLUTTER_TYPE_STAGE)) + return construct_stage (script, oinfo); + + oinfo->gtype = gtype; + params = NULL; + translate_properties (script, oinfo, &n_params, ¶ms); + + CLUTTER_NOTE (SCRIPT, "Creating instance for type `%s' (params:%d)", + g_type_name (gtype), + n_params); + + oinfo->object = g_object_newv (gtype, n_params, params); + g_object_set_data_full (oinfo->object, "clutter-script-name", + g_strdup (oinfo->id), + g_free); + + for (i = 0; i < n_params; i++) + { + GParameter param = params[i]; + g_value_unset (¶m.value); + } + + g_free (params); + + return oinfo->object; +} + +static void +json_parse_end (JsonParser *parser, + gpointer user_data) +{ + ClutterScript *script = user_data; + ClutterScriptPrivate *priv = script->priv; + GList *objects, *l; + + objects = g_hash_table_get_values (priv->objects); + for (l = objects; l; l = l->next) + { + ObjectInfo *oinfo = l->data; + + oinfo->object = clutter_script_construct_object (script, oinfo); + } + g_list_free (objects); +} + +static void +object_info_free (gpointer data) +{ + if (G_LIKELY (data)) + { + ObjectInfo *oinfo = data; + GList *l; + + g_free (oinfo->class_name); + g_free (oinfo->id); + + for (l = oinfo->properties; l; l = l->next) + { + PropertyInfo *pinfo = l->data; + + g_free (pinfo->property_name); + g_value_unset (&pinfo->value); + } + g_list_free (oinfo->properties); + + if (oinfo->object) + g_object_unref (oinfo->object); + + g_slice_free (ObjectInfo, oinfo); + } +} + +static void +clutter_script_finalize (GObject *gobject) +{ + ClutterScriptPrivate *priv = CLUTTER_SCRIPT_GET_PRIVATE (gobject); + + g_object_unref (priv->parser); + g_hash_table_destroy (priv->objects); + g_free (priv->filename); + + G_OBJECT_CLASS (clutter_script_parent_class)->finalize (gobject); +} + +static void +clutter_script_class_init (ClutterScriptClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ClutterScriptPrivate)); + + gobject_class->finalize = clutter_script_finalize; +} + +static void +clutter_script_init (ClutterScript *script) +{ + ClutterScriptPrivate *priv; + + script->priv = priv = CLUTTER_SCRIPT_GET_PRIVATE (script); + + priv->parser = json_parser_new (); + g_signal_connect (priv->parser, + "object-end", G_CALLBACK (json_object_end), + script); + g_signal_connect (priv->parser, + "parse-end", G_CALLBACK (json_parse_end), + script); + + priv->is_filename = FALSE; + priv->last_merge_id = 0; + + priv->objects = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + object_info_free); +} + +ClutterScript * +clutter_script_new (void) +{ + return g_object_new (CLUTTER_TYPE_SCRIPT, NULL); +} + +guint +clutter_script_load_from_file (ClutterScript *script, + const gchar *filename, + GError **error) +{ + ClutterScriptPrivate *priv; + GError *internal_error; + + g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0); + g_return_val_if_fail (filename != NULL, 0); + + priv = script->priv; + + g_free (priv->filename); + priv->filename = g_strdup (filename); + priv->is_filename = TRUE; + + internal_error = NULL; + json_parser_load_from_file (priv->parser, filename, &internal_error); + if (internal_error) + { + g_propagate_error (error, internal_error); + return 0; + } + else + priv->last_merge_id += 1; + + return priv->last_merge_id; +} + +guint +clutter_script_load_from_data (ClutterScript *script, + const gchar *data, + gsize length, + GError **error) +{ + ClutterScriptPrivate *priv; + GError *internal_error; + + g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), 0); + g_return_val_if_fail (data != NULL, 0); + + priv = script->priv; + + g_free (priv->filename); + priv->filename = NULL; + priv->is_filename = FALSE; + + internal_error = NULL; + json_parser_load_from_data (priv->parser, data, length, &internal_error); + if (internal_error) + { + g_propagate_error (error, internal_error); + return 0; + } + else + priv->last_merge_id += 1; + + return priv->last_merge_id; +} + +GObject * +clutter_script_get_object (ClutterScript *script, + const gchar *name) +{ + ObjectInfo *oinfo; + + g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL); + g_return_val_if_fail (name != NULL, NULL); + + oinfo = g_hash_table_lookup (script->priv->objects, name); + if (!oinfo) + return NULL; + + return oinfo->object; +} + +static GList * +clutter_script_get_objects_valist (ClutterScript *script, + const gchar *first_name, + va_list args) +{ + GList *retval = NULL; + const gchar *name; + + name = first_name; + while (name) + { + retval = + g_list_prepend (retval, clutter_script_get_object (script, name)); + + name = va_arg (args, gchar*); + } + + return g_list_reverse (retval); +} + +GList * +clutter_script_get_objects (ClutterScript *script, + const gchar *first_name, + ...) +{ + GList *retval = NULL; + va_list var_args; + + g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), NULL); + g_return_val_if_fail (first_name != NULL, NULL); + + va_start (var_args, first_name); + retval = clutter_script_get_objects_valist (script, first_name, var_args); + va_end (var_args); + + return retval; +} + +gboolean +clutter_script_value_from_data (ClutterScript *script, + GType gtype, + const gchar *data, + GValue *value, + GError **error) +{ + return FALSE; +} diff --git a/clutter/clutter-script.h b/clutter/clutter-script.h new file mode 100644 index 000000000..f782b09af --- /dev/null +++ b/clutter/clutter-script.h @@ -0,0 +1,78 @@ +#ifndef __CLUTTER_SCRIPT_H__ +#define __CLUTTER_SCRIPT_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_SCRIPT (clutter_script_get_type ()) +#define CLUTTER_SCRIPT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_SCRIPT, ClutterScript)) +#define CLUTTER_IS_SCRIPT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_SCRIPT)) +#define CLUTTER_SCRIPT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_SCRIPT, ClutterScriptClass)) +#define CLUTTER_IS_SCRIPT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_SCRIPT)) +#define CLUTTER_SCRIPT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_SCRIPT, ClutterScript)) + +typedef struct _ClutterScript ClutterScript; +typedef struct _ClutterScriptPrivate ClutterScriptPrivate; +typedef struct _ClutterScriptClass ClutterScriptClass; + +/** + * ClutterScriptError: + * @CLUTTER_SCRIPT_ERROR_INVALID_VALUE: Invalid value + * + * #ClutterScript error enumeration. + * + * Since: 0.6 + */ +typedef enum { + CLUTTER_SCRIPT_ERROR_INVALID_TYPE_FUNCTION, + CLUTTER_SCRIPT_ERROR_INVALID_PROPERTY, + CLUTTER_SCRIPT_ERROR_INVALID_VALUE +} ClutterScriptError; + +#define CLUTTER_SCRIPT_ERROR (clutter_script_error_quark ()) +GQuark clutter_script_error_quark (void); + +struct _ClutterScript +{ + /*< private >*/ + GObject parent_instance; + + ClutterScriptPrivate *priv; +}; + +struct _ClutterScriptClass +{ + /*< private >*/ + GObjectClass parent_class; + + /* padding, for future expansion */ + void (*_clutter_reserved1) (void); + void (*_clutter_reserved2) (void); + void (*_clutter_reserved3) (void); + void (*_clutter_reserved4) (void); + void (*_clutter_reserved5) (void); + void (*_clutter_reserved6) (void); + void (*_clutter_reserved7) (void); + void (*_clutter_reserved8) (void); +}; + +GType clutter_script_get_type (void) G_GNUC_CONST; + +ClutterScript *clutter_script_new (void); +guint clutter_script_load_from_file (ClutterScript *script, + const gchar *filename, + GError **error); +guint clutter_script_load_from_data (ClutterScript *script, + const gchar *data, + gsize length, + GError **error); +GObject * clutter_script_get_object (ClutterScript *script, + const gchar *name); +GList * clutter_script_get_objects (ClutterScript *script, + const gchar *first_name, + ...) G_GNUC_NULL_TERMINATED; + +G_END_DECLS + +#endif /* __CLUTTER_SCRIPT_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index 8252be61f..e0d4a5c51 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -58,6 +58,7 @@ #include "clutter-timeout-pool.h" #include "clutter-timeline.h" #include "clutter-score.h" +#include "clutter-script.h" #include "clutter-types.h" #include "clutter-units.h" #include "clutter-util.h" diff --git a/clutter/json/Makefile.am b/clutter/json/Makefile.am new file mode 100644 index 000000000..6e78f0ca0 --- /dev/null +++ b/clutter/json/Makefile.am @@ -0,0 +1,26 @@ +source_c = \ + json-array.c \ + json-generator.c \ + json-marshal.c \ + json-node.c \ + json-object.c \ + json-parser.c + +source_h = \ + json-generator.h \ + json-glib.h \ + json-marshal.h \ + json-parser.h \ + json-types.h + +noinst_LTLIBRARIES = libclutter-json.la + +libclutter_json_la_SOURCES = $(source_c) $(source_h) + +INCLUDES = \ + -I$(top_srcdir) \ + -DG_DISABLE_DEPRECATED \ + $(GCC_FLAGS) \ + $(CLUTTER_CFLAGS) \ + $(CLUTTER_DEBUG_CFLAGS) + diff --git a/clutter/json/json-array.c b/clutter/json/json-array.c new file mode 100644 index 000000000..e9727e881 --- /dev/null +++ b/clutter/json/json-array.c @@ -0,0 +1,231 @@ +/* json-array.c - JSON array implementation + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#include "config.h" + +#include "json-types.h" + +/** + * SECTION:json-array + * @short_description: a JSON array representation + * + * #JsonArray is the representation of the array type inside JSON. It contains + * #JsonNodes, which may contain fundamental types, other arrays or + * objects. + * + * Since arrays can be expensive, they are reference counted. You can control + * the lifetime of a #JsonArray using json_array_ref() and json_array_unref(). + * + * To extract an element at a given index, use json_array_get_element(). + * To retrieve the entire array in list form, use json_array_get_elements(). + * To retrieve the length of the array, use json_array_get_length(). + */ + +struct _JsonArray +{ + GPtrArray *elements; + + volatile gint ref_count; +}; + +GType +json_array_get_type (void) +{ + static GType array_type = 0; + + if (G_UNLIKELY (!array_type)) + array_type = g_boxed_type_register_static ("JsonArray", + (GBoxedCopyFunc) json_array_ref, + (GBoxedFreeFunc) json_array_unref); + + return array_type; +} + +/** + * json_array_new: + * + * Creates a new #JsonArray. + * + * Return value: the newly created #JsonArray + */ +JsonArray * +json_array_new (void) +{ + JsonArray *array; + + array = g_slice_new (JsonArray); + + array->ref_count = 1; + array->elements = g_ptr_array_new (); + + return array; +} + +/** + * json_array_sized_new: + * @n_elements: number of slots to pre-allocate + * + * Creates a new #JsonArray with @n_elements slots already allocated. + * + * Return value: the newly created #JsonArray + */ +JsonArray * +json_array_sized_new (guint n_elements) +{ + JsonArray *array; + + array = g_slice_new (JsonArray); + + array->ref_count = 1; + array->elements = g_ptr_array_sized_new (n_elements); + + return array; +} + +/** + * json_array_ref: + * @array: a #JsonArray + * + * Increase by one the reference count of a #JsonArray. + * + * Return value: the passed #JsonArray, with the reference count + * increased by one. + */ +JsonArray * +json_array_ref (JsonArray *array) +{ + g_return_val_if_fail (array != NULL, NULL); + g_return_val_if_fail (array->ref_count > 0, NULL); + + g_atomic_int_exchange_and_add (&array->ref_count, 1); + + return array; +} + +/** + * json_array_unref: + * @array: a #JsonArray + * + * Decreases by one the reference count of a #JsonArray. If the + * reference count reaches zero, the array is destroyed and all + * its allocated resources are freed. + */ +void +json_array_unref (JsonArray *array) +{ + gint old_ref; + + g_return_if_fail (array != NULL); + g_return_if_fail (array->ref_count > 0); + + old_ref = g_atomic_int_get (&array->ref_count); + if (old_ref > 1) + g_atomic_int_compare_and_exchange (&array->ref_count, old_ref, old_ref - 1); + else + { + gint i; + + for (i = 0; i < array->elements->len; i++) + json_node_free (g_ptr_array_index (array->elements, i)); + + g_ptr_array_free (array->elements, TRUE); + array->elements = NULL; + + g_slice_free (JsonArray, array); + } +} + +/** + * json_array_get_elements: + * @array: a #JsonArray + * + * Gets the elements of a #JsonArray as a list of #JsonNodes. + * + * Return value: a #GList containing the elements of the array. The + * contents of the list are owned by the array and should never be + * modified or freed. Use g_list_free() on the returned list when + * done using it + */ +GList * +json_array_get_elements (JsonArray *array) +{ + GList *retval; + guint i; + + g_return_val_if_fail (array != NULL, NULL); + + retval = NULL; + for (i = 0; i < array->elements->len; i++) + retval = g_list_prepend (retval, + g_ptr_array_index (array->elements, i)); + + return g_list_reverse (retval); +} + +/** + * json_array_get_element: + * @array: a #JsonArray + * @index_: the index of the element to retrieve + * + * Retrieves the element at @index_ inside a #JsonArray. + * + * Return value: a pointer to the #JsonNode at the requested index + */ +JsonNode * +json_array_get_element (JsonArray *array, + guint index_) +{ + g_return_val_if_fail (array != NULL, NULL); + g_return_val_if_fail (index_ < array->elements->len, NULL); + + return g_ptr_array_index (array->elements, index_); +} + +/** + * json_array_get_length: + * @array: a #JsonArray + * + * Retrieves the length of a #JsonArray + * + * Return value: the length of the array + */ +guint +json_array_get_length (JsonArray *array) +{ + g_return_val_if_fail (array != NULL, 0); + + return array->elements->len; +} + +/** + * json_array_add_element: + * @array: a #JsonArray + * @node: a #JsonNode + * + * Appends @node inside @array. + */ +void +json_array_add_element (JsonArray *array, + JsonNode *node) +{ + g_return_if_fail (array != NULL); + g_return_if_fail (node != NULL); + + g_ptr_array_add (array->elements, node); +} diff --git a/clutter/json/json-generator.c b/clutter/json/json-generator.c new file mode 100644 index 000000000..1395ee249 --- /dev/null +++ b/clutter/json/json-generator.c @@ -0,0 +1,533 @@ +/* json-generator.c - JSON streams generator + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:json-generator + * @short_description: Generates JSON data streams + * + * #JsonGenerator provides an object for generating a JSON data stream and + * put it into a buffer or a file. + */ + +#include "config.h" + +#include +#include + +#include "json-marshal.h" +#include "json-generator.h" + +#define JSON_GENERATOR_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), JSON_TYPE_GENERATOR, JsonGeneratorPrivate)) + +struct _JsonGeneratorPrivate +{ + JsonNode *root; + + guint indent; + + guint pretty : 1; +}; + +enum +{ + PROP_0, + + PROP_PRETTY, + PROP_INDENT +}; + +static gchar *dump_value (JsonGenerator *generator, + gint level, + const gchar *name, + JsonNode *node); +static gchar *dump_array (JsonGenerator *generator, + gint level, + const gchar *name, + JsonArray *array, + gsize *length); +static gchar *dump_object (JsonGenerator *generator, + gint level, + const gchar *name, + JsonObject *object, + gsize *length); + +G_DEFINE_TYPE (JsonGenerator, json_generator, G_TYPE_OBJECT); + +static void +json_generator_finalize (GObject *gobject) +{ + JsonGeneratorPrivate *priv = JSON_GENERATOR_GET_PRIVATE (gobject); + + if (priv->root) + json_node_free (priv->root); + + G_OBJECT_CLASS (json_generator_parent_class)->finalize (gobject); +} + +static void +json_generator_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + JsonGeneratorPrivate *priv = JSON_GENERATOR_GET_PRIVATE (gobject); + + switch (prop_id) + { + case PROP_PRETTY: + priv->pretty = g_value_get_boolean (value); + break; + case PROP_INDENT: + priv->indent = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +json_generator_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + JsonGeneratorPrivate *priv = JSON_GENERATOR_GET_PRIVATE (gobject); + + switch (prop_id) + { + case PROP_PRETTY: + g_value_set_boolean (value, priv->pretty); + break; + case PROP_INDENT: + g_value_set_uint (value, priv->indent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +json_generator_class_init (JsonGeneratorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (JsonGeneratorPrivate)); + + gobject_class->set_property = json_generator_set_property; + gobject_class->get_property = json_generator_get_property; + gobject_class->finalize = json_generator_finalize; + + /** + * JsonGenerator:pretty: + * + * Whether the output should be "pretty-printed", with indentation and + * newlines. The indentation level can be controlled by using the + * JsonGenerator:indent property + */ + g_object_class_install_property (gobject_class, + PROP_PRETTY, + g_param_spec_boolean ("pretty", + "Pretty", + "Pretty-print the output", + FALSE, + G_PARAM_READWRITE)); + /** + * JsonGenerator:indent: + * + * Number of spaces to be used to indent when pretty printing. + */ + g_object_class_install_property (gobject_class, + PROP_INDENT, + g_param_spec_uint ("indent", + "Indent", + "Number of indentation spaces", + 0, G_MAXUINT, + 2, + G_PARAM_READWRITE)); +} + +static void +json_generator_init (JsonGenerator *generator) +{ + JsonGeneratorPrivate *priv; + + generator->priv = priv = JSON_GENERATOR_GET_PRIVATE (generator); + + priv->pretty = FALSE; + priv->indent = 2; +} + +static gchar * +dump_value (JsonGenerator *generator, + gint level, + const gchar *name, + JsonNode *node) +{ + gboolean pretty = generator->priv->pretty; + guint indent = generator->priv->indent; + GValue value = { 0, }; + GString *buffer; + gint i; + + buffer = g_string_new (""); + + if (pretty) + { + for (i = 0; i < (level * indent); i++) + g_string_append_c (buffer, ' '); + } + + if (name && name[0] != '\0') + g_string_append_printf (buffer, "\"%s\" : ", name); + + json_node_get_value (node, &value); + + switch (G_VALUE_TYPE (&value)) + { + case G_TYPE_INT: + g_string_append_printf (buffer, "%d", g_value_get_int (&value)); + break; + + case G_TYPE_STRING: + g_string_append_printf (buffer, "\"%s\"", g_value_get_string (&value)); + break; + + case G_TYPE_FLOAT: + g_string_append_printf (buffer, "%f", g_value_get_float (&value)); + break; + + case G_TYPE_BOOLEAN: + g_string_append_printf (buffer, "%s", + g_value_get_boolean (&value) ? "true" : "false"); + break; + + default: + break; + } + + g_value_unset (&value); + + return g_string_free (buffer, FALSE); +} + +static gchar * +dump_array (JsonGenerator *generator, + gint level, + const gchar *name, + JsonArray *array, + gsize *length) +{ + gint array_len = json_array_get_length (array); + gint i; + GString *buffer; + gboolean pretty = generator->priv->pretty; + guint indent = generator->priv->indent; + + buffer = g_string_new (""); + + if (pretty) + { + for (i = 0; i < (level * indent); i++) + g_string_append_c (buffer, ' '); + } + + if (name && name[0] != '\0') + g_string_append_printf (buffer, "\"%s\" : ", name); + + g_string_append_c (buffer, '['); + + if (pretty) + g_string_append_c (buffer, '\n'); + else + g_string_append_c (buffer, ' '); + + for (i = 0; i < array_len; i++) + { + JsonNode *cur = json_array_get_element (array, i); + guint sub_level = level + 1; + gint j; + gchar *value; + + switch (JSON_NODE_TYPE (cur)) + { + case JSON_NODE_NULL: + if (pretty) + { + for (j = 0; j < (sub_level * indent); j++) + g_string_append_c (buffer, ' '); + } + g_string_append (buffer, "null"); + break; + + case JSON_NODE_VALUE: + value = dump_value (generator, sub_level, NULL, cur); + g_string_append (buffer, value); + break; + + case JSON_NODE_ARRAY: + value = dump_array (generator, sub_level, NULL, json_node_get_array (cur), NULL); + g_string_append (buffer, value); + break; + + case JSON_NODE_OBJECT: + value = dump_object (generator, sub_level, NULL, json_node_get_object (cur), NULL); + g_string_append (buffer, value); + break; + } + + if ((i + 1) != array_len) + g_string_append_c (buffer, ','); + + if (pretty) + g_string_append_c (buffer, '\n'); + else + g_string_append_c (buffer, ' '); + } + + if (pretty) + { + for (i = 0; i < (level * indent); i++) + g_string_append_c (buffer, ' '); + } + + g_string_append_c (buffer, ']'); + + if (length) + *length = buffer->len; + + return g_string_free (buffer, FALSE); +} + +static gchar * +dump_object (JsonGenerator *generator, + gint level, + const gchar *name, + JsonObject *object, + gsize *length) +{ + GList *members, *l; + GString *buffer; + gboolean pretty = generator->priv->pretty; + guint indent = generator->priv->indent; + gint i; + + buffer = g_string_new (""); + + if (pretty) + { + for (i = 0; i < (level * indent); i++) + g_string_append_c (buffer, ' '); + } + + if (name && name[0] != '\0') + g_string_append_printf (buffer, "\"%s\" : ", name); + + g_string_append_c (buffer, '{'); + + if (pretty) + g_string_append_c (buffer, '\n'); + else + g_string_append_c (buffer, ' '); + + members = json_object_get_members (object); + + for (l = members; l != NULL; l = l->next) + { + const gchar *name = l->data; + JsonNode *cur = json_object_get_member (object, name); + guint sub_level = level + 1; + gint j; + gchar *value; + + switch (JSON_NODE_TYPE (cur)) + { + case JSON_NODE_NULL: + if (pretty) + { + for (j = 0; j < (sub_level * indent); j++) + g_string_append_c (buffer, ' '); + } + g_string_append_printf (buffer, "\"%s\" : null", name); + break; + + case JSON_NODE_VALUE: + value = dump_value (generator, sub_level, name, cur); + g_string_append (buffer, value); + break; + + case JSON_NODE_ARRAY: + value = dump_array (generator, sub_level, name, json_node_get_array (cur), NULL); + g_string_append (buffer, value); + break; + + case JSON_NODE_OBJECT: + value = dump_object (generator, sub_level, name, json_node_get_object (cur), NULL); + g_string_append (buffer, value); + break; + } + + if (l->next != NULL) + g_string_append_c (buffer, ','); + + if (pretty) + g_string_append_c (buffer, '\n'); + else + g_string_append_c (buffer, ' '); + } + + g_list_free (members); + + if (pretty) + { + for (i = 0; i < (level * indent); i++) + g_string_append_c (buffer, ' '); + } + + g_string_append_c (buffer, '}'); + + if (length) + *length = buffer->len; + + return g_string_free (buffer, FALSE); +} + +/** + * json_generator_new: + * + * Creates a new #JsonGenerator. You can use this object to generate a + * JSON data stream starting from a data object model composed by + * #JsonNodes. + * + * Return value: the newly created #JsonGenerator instance + */ +JsonGenerator * +json_generator_new (void) +{ + return g_object_new (JSON_TYPE_GENERATOR, NULL); +} + +/** + * json_generator_to_data: + * @generator: a #JsonGenerator + * @length: return location for the length of the returned buffer, or %NULL + * + * Generates a JSON data stream from @generator and returns it as a + * buffer. + * + * Return value: a newly allocated buffer holding a JSON data stream. Use + * g_free() to free the allocated resources. + */ +gchar * +json_generator_to_data (JsonGenerator *generator, + gsize *length) +{ + JsonNode *root; + gchar *retval = NULL; + + g_return_val_if_fail (JSON_IS_GENERATOR (generator), NULL); + + root = generator->priv->root; + if (!root) + { + if (length) + *length = 0; + + return NULL; + } + + switch (JSON_NODE_TYPE (root)) + { + case JSON_NODE_ARRAY: + retval = dump_array (generator, 0, NULL, json_node_get_array (root), length); + break; + + case JSON_NODE_OBJECT: + retval = dump_object (generator, 0, NULL, json_node_get_object (root), length); + break; + + case JSON_NODE_NULL: + retval = g_strdup ("null"); + if (length) + *length = 4; + break; + + case JSON_NODE_VALUE: + retval = NULL; + break; + } + + return retval; +} + +/** + * json_generator_to_file: + * @generator: a #JsonGenerator + * @filename: path to the target file + * @error: return location for a #GError, or %NULL + * + * Creates a JSON data stream and puts it inside @filename, overwriting the + * current file contents. This operation is atomic. + * + * Return value: %TRUE if saving was successful. + */ +gboolean +json_generator_to_file (JsonGenerator *generator, + const gchar *filename, + GError **error) +{ + gchar *buffer; + gsize len; + gboolean retval; + + g_return_val_if_fail (JSON_IS_GENERATOR (generator), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + buffer = json_generator_to_data (generator, &len); + retval = g_file_set_contents (filename, buffer, len, error); + g_free (buffer); + + return retval; +} + +/** + * json_generator_set_root: + * @generator: a #JsonGenerator + * @node: a #JsonNode + * + * Sets @node as the root of the JSON data stream to be serialized by + * the #JsonGenerator. + */ +void +json_generator_set_root (JsonGenerator *generator, + JsonNode *node) +{ + g_return_if_fail (JSON_IS_GENERATOR (generator)); + + if (generator->priv->root) + { + json_node_free (generator->priv->root); + generator->priv->root = NULL; + } + + if (node) + generator->priv->root = node; +} diff --git a/clutter/json/json-generator.h b/clutter/json/json-generator.h new file mode 100644 index 000000000..893325bee --- /dev/null +++ b/clutter/json/json-generator.h @@ -0,0 +1,83 @@ +/* json-generator.h - JSON streams generator + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#ifndef __JSON_GENERATOR_H__ +#define __JSON_GENERATOR_H__ + +#include +#include "json-types.h" + +G_BEGIN_DECLS + +#define JSON_TYPE_GENERATOR (json_generator_get_type ()) +#define JSON_GENERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JSON_TYPE_GENERATOR, JsonGenerator)) +#define JSON_IS_GENERATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), JSON_TYPE_GENERATOR)) +#define JSON_GENERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), JSON_TYPE_GENERATOR, JsonGeneratorClass)) +#define JSON_IS_GENERATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), JSON_TYPE_GENERATOR)) +#define JSON_GENERATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), JSON_TYPE_GENERATOR, JsonGeneratorClass)) + +typedef struct _JsonGenerator JsonGenerator; +typedef struct _JsonGeneratorPrivate JsonGeneratorPrivate; +typedef struct _JsonGeneratorClass JsonGeneratorClass; + +/** + * JsonGenerator: + * + * JSON data streams generator. The contents of the #JsonGenerator structure + * are private and should only be accessed via the provided API. + */ +struct _JsonGenerator +{ + /*< private >*/ + GObject parent_instance; + + JsonGeneratorPrivate *priv; +}; + +/** + * JsonGeneratorClass: + * + * #JsonGenerator class + */ +struct _JsonGeneratorClass +{ + /*< private >*/ + GObjectClass parent_class; + + /* padding, for future expansion */ + void (* _json_reserved1) (void); + void (* _json_reserved2) (void); + void (* _json_reserved3) (void); + void (* _json_reserved4) (void); +}; + +GType json_generator_get_type (void) G_GNUC_CONST; + +JsonGenerator *json_generator_new (void); +gchar * json_generator_to_data (JsonGenerator *generator, + gsize *length); +gboolean json_generator_to_file (JsonGenerator *generator, + const gchar *filename, + GError **error); +void json_generator_set_root (JsonGenerator *generator, + JsonNode *node); + +G_END_DECLS + +#endif /* __JSON_GENERATOR_H__ */ diff --git a/clutter/json/json-glib.h b/clutter/json/json-glib.h new file mode 100644 index 000000000..ac58b5179 --- /dev/null +++ b/clutter/json/json-glib.h @@ -0,0 +1,8 @@ +#ifndef __JSON_GLIB_H__ +#define __JSON_GLIB_H__ + +#include "json-types.h" +#include "json-generator.h" +#include "json-parser.h" + +#endif /* __JSON_GLIB_H__ */ diff --git a/clutter/json/json-marshal.c b/clutter/json/json-marshal.c new file mode 100644 index 000000000..9588d29cd --- /dev/null +++ b/clutter/json/json-marshal.c @@ -0,0 +1,130 @@ +#include "json-marshal.h" + +#include + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* VOID:VOID (./json-marshal.list:1) */ + +/* VOID:BOXED (./json-marshal.list:2) */ + +/* VOID:BOXED,STRING (./json-marshal.list:3) */ +void +_json_marshal_VOID__BOXED_STRING (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__BOXED_STRING) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_VOID__BOXED_STRING callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__BOXED_STRING) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_string (param_values + 2), + data2); +} + +/* VOID:BOXED,INT (./json-marshal.list:4) */ +void +_json_marshal_VOID__BOXED_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__BOXED_INT) (gpointer data1, + gpointer arg_1, + gint arg_2, + gpointer data2); + register GMarshalFunc_VOID__BOXED_INT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__BOXED_INT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); +} + +/* VOID:POINTER (./json-marshal.list:5) */ + diff --git a/clutter/json/json-marshal.h b/clutter/json/json-marshal.h new file mode 100644 index 000000000..f4f7658c8 --- /dev/null +++ b/clutter/json/json-marshal.h @@ -0,0 +1,37 @@ + +#ifndef ___json_marshal_MARSHAL_H__ +#define ___json_marshal_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* VOID:VOID (./json-marshal.list:1) */ +#define _json_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID + +/* VOID:BOXED (./json-marshal.list:2) */ +#define _json_marshal_VOID__BOXED g_cclosure_marshal_VOID__BOXED + +/* VOID:BOXED,STRING (./json-marshal.list:3) */ +extern void _json_marshal_VOID__BOXED_STRING (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:BOXED,INT (./json-marshal.list:4) */ +extern void _json_marshal_VOID__BOXED_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:POINTER (./json-marshal.list:5) */ +#define _json_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER + +G_END_DECLS + +#endif /* ___json_marshal_MARSHAL_H__ */ + diff --git a/clutter/json/json-node.c b/clutter/json/json-node.c new file mode 100644 index 000000000..68b66c9c5 --- /dev/null +++ b/clutter/json/json-node.c @@ -0,0 +1,610 @@ +/* json-node.c - JSON object model node + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#include "config.h" + +#include + +#include "json-types.h" + +/** + * SECTION:json-node + * @short_description: Node in a JSON object model + * + * A #JsonNode is a generic container of elements inside a JSON stream. + * It can contain fundamental types (integers, booleans, floating point + * numbers, strings) and complex types (arrays and objects). + * + * When parsing a JSON data stream you extract the root node and walk + * the node tree by retrieving the type of data contained inside the + * node with the %JSON_NODE_TYPE macro. If the node contains a fundamental + * type you can retrieve a copy of the GValue holding it with the + * json_node_get_value() function, and then use the GValue API to extract + * the data; if the node contains a complex type you can retrieve the + * #JsonObject or the #JsonArray using json_node_get_object() or + * json_node_get_array() respectively, and then retrieve the nodes + * they contain. + */ + +/** + * json_node_new: + * @type: a #JsonNodeType + * + * Creates a new #JsonNode of @type. + * + * Return value: the newly created #JsonNode + */ +JsonNode * +json_node_new (JsonNodeType type) +{ + JsonNode *data; + + g_return_val_if_fail (type >= JSON_NODE_OBJECT && type <= JSON_NODE_NULL, NULL); + + data = g_slice_new (JsonNode); + data->type = type; + + return data; +} + +/** + * json_node_copy: + * @node: a #JsonNode + * + * Copies @node. If the node contains complex data types then the reference + * count of the objects is increased. + * + * Return value: the copied #JsonNode + */ +JsonNode * +json_node_copy (JsonNode *node) +{ + JsonNode *copy; + + g_return_val_if_fail (node != NULL, NULL); + + copy = g_slice_new (JsonNode); + *copy = *node; + + switch (copy->type) + { + case JSON_NODE_OBJECT: + copy->data.object = json_object_ref (node->data.object); + break; + case JSON_NODE_ARRAY: + copy->data.array = json_array_ref (node->data.array); + break; + case JSON_NODE_VALUE: + g_value_init (&(copy->data.value), G_VALUE_TYPE (&(node->data.value))); + g_value_copy (&(node->data.value), &(copy->data.value)); + break; + case JSON_NODE_NULL: + break; + default: + g_assert_not_reached (); + } + + return copy; +} + +/** + * json_node_set_object: + * @node: a #JsonNode + * @object: a #JsonObject + * + * Sets @objects inside @node. The reference count of @object is increased. + */ +void +json_node_set_object (JsonNode *node, + JsonObject *object) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT); + + if (node->data.object) + json_object_unref (node->data.object); + + if (object) + node->data.object = json_object_ref (object); + else + node->data.object = NULL; +} + +/** + * json_node_take_object: + * @node: a #JsonNode + * @object: a #JsonObject + * + * Sets @object inside @node. The reference count of @object is not increased. + */ +void +json_node_take_object (JsonNode *node, + JsonObject *object) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT); + + if (node->data.object) + { + json_object_unref (node->data.object); + node->data.object = NULL; + } + + if (object) + node->data.object = object; +} + +/** + * json_node_get_object: + * @node: a #JsonNode + * + * Retrieves the #JsonObject stored inside a #JsonNode + * + * Return value: the #JsonObject + */ +JsonObject * +json_node_get_object (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT, NULL); + + return node->data.object; +} + +/** + * json_node_dup_object: + * @node: a #JsonNode + * + * Retrieves the #JsonObject inside @node. The reference count of + * the returned object is increased. + * + * Return value: the #JsonObject + */ +JsonObject * +json_node_dup_object (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT, NULL); + + if (node->data.object) + return json_object_ref (node->data.object); + + return NULL; +} + +/** + * json_node_set_array: + * @node: a #JsonNode + * @array: a #JsonArray + * + * Sets @array inside @node and increases the #JsonArray reference count + */ +void +json_node_set_array (JsonNode *node, + JsonArray *array) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY); + + if (node->data.array) + json_array_unref (node->data.array); + + if (array) + node->data.array = json_array_ref (array); + else + node->data.array = NULL; +} + +/** + * json_node_take_array: + * @node: a #JsonNode + * @array: a #JsonArray + * + * Sets @array into @node without increasing the #JsonArray reference count. + */ +void +json_node_take_array (JsonNode *node, + JsonArray *array) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY); + + if (node->data.array) + { + json_array_unref (node->data.array); + node->data.array = NULL; + } + + if (array) + node->data.array = array; +} + +/** + * json_node_get_array: + * @node: a #JsonNode + * + * Retrieves the #JsonArray stored inside a #JsonNode + * + * Return value: the #JsonArray + */ +JsonArray * +json_node_get_array (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY, NULL); + + return node->data.array; +} + +/** + * json_node_dup_array + * @node: a #JsonNode + * + * Retrieves the #JsonArray stored inside a #JsonNode and returns it + * with its reference count increased by one. + * + * Return value: the #JsonArray with its reference count increased. + */ +JsonArray * +json_node_dup_array (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY, NULL); + + if (node->data.array) + return json_array_ref (node->data.array); + + return NULL; +} + +/** + * json_node_get_value: + * @node: a #JsonNode + * @value: return location for an uninitialized value + * + * Retrieves a value from a #JsonNode and copies into @value. When done + * using it, call g_value_unset() on the #GValue. + */ +void +json_node_get_value (JsonNode *node, + GValue *value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) != 0) + { + g_value_init (value, G_VALUE_TYPE (&(node->data.value))); + g_value_copy (&(node->data.value), value); + } +} + +/** + * json_node_set_value: + * @node: a #JsonNode + * @value: the #GValue to set + * + * Sets @value inside @node. The passed #GValue is copied into the #JsonNode + */ +void +json_node_set_value (JsonNode *node, + const GValue *value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) != 0) + g_value_unset (&(node->data.value)); + + g_value_init (&(node->data.value), G_VALUE_TYPE (value)); + g_value_copy (value, &(node->data.value)); +} + +/** + * json_node_free: + * @node: a #JsonNode + * + * Frees the resources allocated by @node. + */ +void +json_node_free (JsonNode *node) +{ + if (G_LIKELY (node)) + { + switch (node->type) + { + case JSON_NODE_OBJECT: + json_object_unref (node->data.object); + break; + + case JSON_NODE_ARRAY: + json_array_unref (node->data.array); + break; + + case JSON_NODE_VALUE: + g_value_unset (&(node->data.value)); + break; + + case JSON_NODE_NULL: + break; + } + + g_slice_free (JsonNode, node); + } +} + +/** + * json_node_type_name: + * @node: a #JsonNode + * + * Retrieves the user readable name of the data type contained by @node. + * + * Return value: a string containing the name of the type. The returned string + * is owned by the node and should never be modified or freed + */ +G_CONST_RETURN gchar * +json_node_type_name (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, "(null)"); + + switch (node->type) + { + case JSON_NODE_OBJECT: + return "JsonObject"; + + case JSON_NODE_ARRAY: + return "JsonArray"; + + case JSON_NODE_NULL: + return "NULL"; + + case JSON_NODE_VALUE: + return g_type_name (G_VALUE_TYPE (&(node->data.value))); + } + + return "unknown"; +} + +/** + * json_node_get_parent: + * @node: a #JsonNode + * + * Retrieves the parent #JsonNode of @node. + * + * Return value: the parent node, or %NULL if @node is the root node + */ +JsonNode * +json_node_get_parent (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + + return node->parent; +} + +/** + * json_node_set_string: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * @value: a string value + * + * Sets @value as the string content of the @node, replacing any existing + * content. + */ +void +json_node_set_string (JsonNode *node, + const gchar *value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_STRING) + g_value_set_string (&(node->data.value), value); + else + { + GValue copy = { 0, }; + + g_value_init (©, G_TYPE_STRING); + g_value_set_string (©, value); + + json_node_set_value (node, ©); + + g_value_unset (©); + } +} + +/** + * json_node_get_string: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * + * Gets the string value stored inside a #JsonNode + * + * Return value: a string value. + */ +G_CONST_RETURN gchar * +json_node_get_string (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE, NULL); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_STRING) + return g_value_get_string (&(node->data.value)); + + return NULL; +} + +gchar * +json_node_dup_string (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, NULL); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE, NULL); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_STRING) + return g_strdup (g_value_get_string (&(node->data.value))); + + return NULL; +} + +/** + * json_node_set_int: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * @value: an integer value + * + * Sets @value as the integer content of the @node, replacing any existing + * content. + */ +void +json_node_set_int (JsonNode *node, + gint value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_INT) + g_value_set_int (&(node->data.value), value); + else + { + GValue copy = { 0, }; + + g_value_init (©, G_TYPE_INT); + g_value_set_int (©, value); + + json_node_set_value (node, ©); + + g_value_unset (©); + } +} + +/** + * json_node_get_int: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * + * Gets the integer value stored inside a #JsonNode + * + * Return value: an integer value. + */ +gint +json_node_get_int (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, 0); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE, 0); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_INT) + return g_value_get_int (&(node->data.value)); + + return 0; +} + +/** + * json_node_set_double: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * @value: a double value + * + * Sets @value as the double content of the @node, replacing any existing + * content. + */ +void +json_node_set_double (JsonNode *node, + gdouble value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_DOUBLE) + g_value_set_double (&(node->data.value), value); + else + { + GValue copy = { 0, }; + + g_value_init (©, G_TYPE_DOUBLE); + g_value_set_double (©, value); + + json_node_set_value (node, ©); + + g_value_unset (©); + } +} + +/** + * json_node_get_double: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * + * Gets the double value stored inside a #JsonNode + * + * Return value: a double value. + */ +gdouble +json_node_get_double (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, 0.0); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE, 0.0); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_DOUBLE) + return g_value_get_double (&(node->data.value)); + + return 0.0; +} + +/** + * json_node_set_boolean: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * @value: a boolean value + * + * Sets @value as the boolean content of the @node, replacing any existing + * content. + */ +void +json_node_set_boolean (JsonNode *node, + gboolean value) +{ + g_return_if_fail (node != NULL); + g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_BOOLEAN) + g_value_set_boolean (&(node->data.value), value); + else + { + GValue copy = { 0, }; + + g_value_init (©, G_TYPE_BOOLEAN); + g_value_set_boolean (©, value); + + json_node_set_value (node, ©); + + g_value_unset (©); + } +} + +/** + * json_node_get_boolean: + * @node: a #JsonNode of type %JSON_NODE_VALUE + * + * Gets the boolean value stored inside a #JsonNode + * + * Return value: a boolean value. + */ +gboolean +json_node_get_boolean (JsonNode *node) +{ + g_return_val_if_fail (node != NULL, FALSE); + g_return_val_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE, FALSE); + + if (G_VALUE_TYPE (&(node->data.value)) == G_TYPE_BOOLEAN) + return g_value_get_boolean (&(node->data.value)); + + return FALSE; +} diff --git a/clutter/json/json-object.c b/clutter/json/json-object.c new file mode 100644 index 000000000..bbea83d23 --- /dev/null +++ b/clutter/json/json-object.c @@ -0,0 +1,234 @@ +/* json-object.c - JSON object implementation + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#include "config.h" + +#include + +#include "json-types.h" + +/** + * SECTION:json-object + * @short_description: a JSON object representation + * + * #JsonArray is the representation of the object type inside JSON. It contains + * #JsonNodes, which may contain fundamental types, arrays or other + * objects. Each member of an object is accessed using its name. + * + * Since objects can be expensive, they are reference counted. You can control + * the lifetime of a #JsonObject using json_object_ref() and json_object_unref(). + * + * To extract a member with a given name, use json_object_get_member(). + * To retrieve the list of members, use json_object_get_members(). + * To retrieve the size of the object (that is, the number of members it has), use + * json_object_get_size(). + */ + +struct _JsonObject +{ + GHashTable *members; + + volatile gint ref_count; +}; + +GType +json_object_get_type (void) +{ + static GType object_type = 0; + + if (G_UNLIKELY (!object_type)) + object_type = g_boxed_type_register_static ("JsonObject", + (GBoxedCopyFunc) json_object_ref, + (GBoxedFreeFunc) json_object_unref); + + return object_type; +} + +/** + * json_object_new: + * + * Creates a new #JsonObject, an JSON object type representation. + * + * Return value: the newly created #JsonObject + */ +JsonObject * +json_object_new (void) +{ + JsonObject *object; + + object = g_slice_new (JsonObject); + + object->ref_count = 1; + object->members = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, + (GDestroyNotify) json_node_free); + + return object; +} + +/** + * json_object_ref: + * @object: a #JsonObject + * + * Increase by one the reference count of a #JsonObject. + * + * Return value: the passed #JsonObject, with the reference count + * increased by one. + */ +JsonObject * +json_object_ref (JsonObject *object) +{ + g_return_val_if_fail (object != NULL, NULL); + g_return_val_if_fail (object->ref_count > 0, NULL); + + g_atomic_int_exchange_and_add (&object->ref_count, 1); + + return object; +} + +/** + * json_object_unref: + * @object: a #JsonObject + * + * Decreases by one the reference count of a #JsonObject. If the + * reference count reaches zero, the object is destroyed and all + * its allocated resources are freed. + */ +void +json_object_unref (JsonObject *object) +{ + gint old_ref; + + g_return_if_fail (object != NULL); + g_return_if_fail (object->ref_count > 0); + + old_ref = g_atomic_int_get (&object->ref_count); + if (old_ref > 1) + g_atomic_int_compare_and_exchange (&object->ref_count, old_ref, old_ref - 1); + else + { + g_hash_table_destroy (object->members); + object->members = NULL; + + g_slice_free (JsonObject, object); + } +} + +/** + * json_object_add_member: + * @object: a #JsonObject + * @member_name: the name of the member + * @node: the value of the member + * + * Adds a member named @member_name and containing @node into a #JsonObject. + */ +void +json_object_add_member (JsonObject *object, + const gchar *member_name, + JsonNode *node) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (member_name != NULL); + g_return_if_fail (node != NULL); + + if (json_object_has_member (object, member_name)) + { + g_warning ("JsonObject already has a `%s' member of type `%s'", + member_name, + json_node_type_name (node)); + return; + } + + g_hash_table_replace (object->members, g_strdup (member_name), node); +} + +/** + * json_object_get_members: + * @object: a #JsonObject + * + * Retrieves all the names of the members of a #JsonObject. You can + * obtain the value for each member using json_object_get_member(). + * + * Return value: a #GList of member names. The content of the list + * is owned by the #JsonObject and should never be modified or + * freed. When you have finished using the returned list, use + * g_slist_free() to free the resources it has allocated. + */ +GList * +json_object_get_members (JsonObject *object) +{ + g_return_val_if_fail (object != NULL, NULL); + + return g_hash_table_get_keys (object->members); +} + +/** + * json_object_get_member: + * @object: a #JsonObject + * @member_name: the name of the JSON object member to access + * + * Retrieves the value of @member_name inside a #JsonObject. + * + * Return value: a pointer to the value for the requested object + * member, or %NULL + */ +JsonNode * +json_object_get_member (JsonObject *object, + const gchar *member_name) +{ + g_return_val_if_fail (object != NULL, NULL); + g_return_val_if_fail (member_name != NULL, NULL); + + return g_hash_table_lookup (object->members, member_name); +} + +/** + * json_object_has_member: + * @object: a #JsonObject + * @member_name: the name of a JSON object member + * + * Checks whether @object has a member named @member_name. + * + * Return value: %TRUE if the JSON object has the requested member + */ +gboolean +json_object_has_member (JsonObject *object, + const gchar *member_name) +{ + g_return_val_if_fail (object != NULL, FALSE); + g_return_val_if_fail (member_name != NULL, FALSE); + + return (g_hash_table_lookup (object->members, member_name) != NULL); +} + +/** + * json_object_get_size: + * @object: a #JsonObject + * + * Retrieves the size of a #JsonObject. + * + * Return value: the number of members the JSON object has + */ +guint +json_object_get_size (JsonObject *object) +{ + g_return_val_if_fail (object != NULL, 0); + + return g_hash_table_size (object->members); +} diff --git a/clutter/json/json-parser.c b/clutter/json/json-parser.c new file mode 100644 index 000000000..2f9e24c26 --- /dev/null +++ b/clutter/json/json-parser.c @@ -0,0 +1,998 @@ +/* json-parser.c - JSON streams parser + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +/** + * SECTION:json-parser + * @short_description: Parse JSON data streams + * + * #JsonParser provides an object for parsing a JSON data stream, either + * inside a file or inside a static buffer. + */ + +#include "config.h" + +#include + +#include "json-marshal.h" +#include "json-parser.h" + +GQuark +json_parser_error_quark (void) +{ + return g_quark_from_static_string ("json-parser-error"); +} + +#define JSON_PARSER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), JSON_TYPE_PARSER, JsonParserPrivate)) + +struct _JsonParserPrivate +{ + JsonNode *root; + JsonNode *current_node; + + GScanner *scanner; +}; + +static const GScannerConfig json_scanner_config = +{ + ( " \t\r\n" ) /* cset_skip_characters */, + ( + "_" + G_CSET_a_2_z + G_CSET_A_2_Z + ) /* cset_identifier_first */, + ( + G_CSET_DIGITS + "-_" + G_CSET_a_2_z + G_CSET_A_2_Z + ) /* cset_identifier_nth */, + ( "#\n" ) /* cpair_comment_single */, + TRUE /* case_sensitive */, + TRUE /* skip_comment_multi */, + TRUE /* skip_comment_single */, + FALSE /* scan_comment_multi */, + TRUE /* scan_identifier */, + TRUE /* scan_identifier_1char */, + FALSE /* scan_identifier_NULL */, + TRUE /* scan_symbols */, + TRUE /* scan_binary */, + TRUE /* scan_octal */, + TRUE /* scan_float */, + TRUE /* scan_hex */, + TRUE /* scan_hex_dollar */, + TRUE /* scan_string_sq */, + TRUE /* scan_string_dq */, + TRUE /* numbers_2_int */, + FALSE /* int_2_float */, + FALSE /* identifier_2_string */, + TRUE /* char_2_token */, + TRUE /* symbol_2_token */, + FALSE /* scope_0_fallback */, +}; + + +static const gchar symbol_names[] = + "true\0" + "false\0" + "null\0"; + +static const struct +{ + guint name_offset; + guint token; +} symbols[] = { + { 0, JSON_TOKEN_TRUE }, + { 5, JSON_TOKEN_FALSE }, + { 11, JSON_TOKEN_NULL } +}; + +static const guint n_symbols = G_N_ELEMENTS (symbols); + +enum +{ + PARSE_START, + OBJECT_START, + OBJECT_MEMBER, + OBJECT_END, + ARRAY_START, + ARRAY_ELEMENT, + ARRAY_END, + PARSE_END, + ERROR, + + LAST_SIGNAL +}; + +static guint parser_signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (JsonParser, json_parser, G_TYPE_OBJECT); + +static guint json_parse_array (JsonParser *parser, + GScanner *scanner, + gboolean nested); +static guint json_parse_object (JsonParser *parser, + GScanner *scanner, + gboolean nested); + +static void +json_parser_dispose (GObject *gobject) +{ + JsonParserPrivate *priv = JSON_PARSER_GET_PRIVATE (gobject); + + if (priv->root) + { + json_node_free (priv->root); + priv->root = NULL; + } + + G_OBJECT_CLASS (json_parser_parent_class)->dispose (gobject); +} + +static void +json_parser_class_init (JsonParserClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (JsonParserPrivate)); + + gobject_class->dispose = json_parser_dispose; + + /** + * JsonParser::parse-start: + * @parser: the #JsonParser that received the signal + * + * The ::parse-start signal is emitted when the parser began parsing + * a JSON data stream. + */ + parser_signals[PARSE_START] = + g_signal_new ("parse-start", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, parse_start), + NULL, NULL, + _json_marshal_VOID__VOID, + G_TYPE_NONE, 0); + /** + * JsonParser::parse-end: + * @parser: the #JsonParser that received the signal + * + * The ::parse-end signal is emitted when the parser successfully + * finished parsing a JSON data stream + */ + parser_signals[PARSE_END] = + g_signal_new ("parse-end", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, parse_end), + NULL, NULL, + _json_marshal_VOID__VOID, + G_TYPE_NONE, 0); + /** + * JsonParser::object-start: + * @parser: the #JsonParser that received the signal + * + * The ::object-start signal is emitted each time the #JsonParser + * starts parsing a #JsonObject. + */ + parser_signals[OBJECT_START] = + g_signal_new ("object-start", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, object_start), + NULL, NULL, + _json_marshal_VOID__VOID, + G_TYPE_NONE, 0); + /** + * JsonParser::object-member: + * @parser: the #JsonParser that received the signal + * @object: a #JsonObject + * @member_name: the name of the newly parsed member + * + * The ::object-member signal is emitted each time the #JsonParser + * has successfully parsed a single member of a #JsonObject. The + * object and member are passed to the signal handlers. + */ + parser_signals[OBJECT_MEMBER] = + g_signal_new ("object-member", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, object_member), + NULL, NULL, + _json_marshal_VOID__BOXED_STRING, + G_TYPE_NONE, 2, + JSON_TYPE_OBJECT, + G_TYPE_STRING); + /** + * JsonParser::object-end: + * @parser: the #JsonParser that received the signal + * @object: the parsed #JsonObject + * + * The ::object-end signal is emitted each time the #JsonParser + * has successfully parsed an entire #JsonObject. + */ + parser_signals[OBJECT_END] = + g_signal_new ("object-end", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, object_end), + NULL, NULL, + _json_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + JSON_TYPE_OBJECT); + /** + * JsonParser::array-start: + * @parser: the #JsonParser that received the signal + * + * The ::array-start signal is emitted each time the #JsonParser + * starts parsing a #JsonArray + */ + parser_signals[ARRAY_START] = + g_signal_new ("array-start", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, array_start), + NULL, NULL, + _json_marshal_VOID__VOID, + G_TYPE_NONE, 0); + /** + * JsonParser::array-element: + * @parser: the #JsonParser that received the signal + * @array: a #JsonArray + * @index_: the index of the newly parsed element + * + * The ::array-element signal is emitted each time the #JsonParser + * has successfully parsed a single element of a #JsonArray. The + * array and element index are passed to the signal handlers. + */ + parser_signals[ARRAY_ELEMENT] = + g_signal_new ("array-element", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, array_element), + NULL, NULL, + _json_marshal_VOID__BOXED_INT, + G_TYPE_NONE, 2, + JSON_TYPE_ARRAY, + G_TYPE_INT); + /** + * JsonParser::array-end: + * @parser: the #JsonParser that received the signal + * @array: the parsed #JsonArrary + * + * The ::array-end signal is emitted each time the #JsonParser + * has successfully parsed an entire #JsonArray + */ + parser_signals[ARRAY_END] = + g_signal_new ("array-end", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, array_end), + NULL, NULL, + _json_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + JSON_TYPE_ARRAY); + /** + * JsonParser::error: + * @parser: the parser instance that received the signal + * @error: a pointer to the #GError + * + * The ::error signal is emitted each time a #JsonParser encounters + * an error in a JSON stream. + */ + parser_signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (JsonParserClass, error), + NULL, NULL, + _json_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); +} + +static void +json_parser_init (JsonParser *parser) +{ + JsonParserPrivate *priv; + + parser->priv = priv = JSON_PARSER_GET_PRIVATE (parser); + + priv->root = NULL; + priv->current_node = NULL; +} + +static guint +json_parse_array (JsonParser *parser, + GScanner *scanner, + gboolean nested) +{ + JsonParserPrivate *priv = parser->priv; + JsonArray *array; + guint token; + + if (!nested) + { + /* the caller already swallowed the opening '[' */ + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_LEFT_BRACE) + return G_TOKEN_LEFT_BRACE; + } + + g_signal_emit (parser, parser_signals[ARRAY_START], 0); + + array = json_array_new (); + + token = g_scanner_get_next_token (scanner); + while (token != G_TOKEN_RIGHT_BRACE) + { + JsonNode *node = NULL; + GValue value = { 0, }; + + if (token == G_TOKEN_COMMA) + { + /* swallow the comma */ + token = g_scanner_get_next_token (scanner); + continue; + } + + /* nested object */ + if (token == G_TOKEN_LEFT_CURLY) + { + JsonNode *old_node = priv->current_node; + + priv->current_node = json_node_new (JSON_NODE_OBJECT); + + token = json_parse_object (parser, scanner, TRUE); + + node = priv->current_node; + priv->current_node = old_node; + + if (token != G_TOKEN_NONE) + { + json_node_free (node); + json_array_unref (array); + return token; + } + + json_array_add_element (array, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[ARRAY_ELEMENT], 0, + array, + json_array_get_length (array)); + + token = g_scanner_get_next_token (scanner); + if (token == G_TOKEN_RIGHT_BRACE) + break; + + continue; + } + + /* nested array */ + if (token == G_TOKEN_LEFT_BRACE) + { + JsonNode *old_node = priv->current_node; + + priv->current_node = json_node_new (JSON_NODE_ARRAY); + + token = json_parse_array (parser, scanner, TRUE); + + node = priv->current_node; + priv->current_node = old_node; + + if (token != G_TOKEN_NONE) + { + json_node_free (node); + json_array_unref (array); + return token; + } + + json_array_add_element (array, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[ARRAY_ELEMENT], 0, + array, + json_array_get_length (array)); + + token = g_scanner_get_next_token (scanner); + if (token == G_TOKEN_RIGHT_BRACE) + break; + + continue; + } + + switch (token) + { + case G_TOKEN_INT: + g_value_init (&value, G_TYPE_INT); + g_value_set_int (&value, scanner->value.v_int); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case G_TOKEN_FLOAT: + g_value_init (&value, G_TYPE_DOUBLE); + g_value_set_double (&value, scanner->value.v_float); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case G_TOKEN_STRING: + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, scanner->value.v_string); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, token == JSON_TOKEN_TRUE ? TRUE : FALSE); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case JSON_TOKEN_NULL: + node = json_node_new (JSON_NODE_NULL); + break; + + default: + return G_TOKEN_RIGHT_BRACE; + } + + if (node) + { + json_array_add_element (array, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[ARRAY_ELEMENT], 0, + array, + json_array_get_length (array)); + } + + token = g_scanner_get_next_token (scanner); + } + + json_node_take_array (priv->current_node, array); + + g_signal_emit (parser, parser_signals[ARRAY_END], 0, array); + + return G_TOKEN_NONE; +} + +static guint +json_parse_object (JsonParser *parser, + GScanner *scanner, + gboolean nested) +{ + JsonParserPrivate *priv = parser->priv; + JsonObject *object; + guint token; + + if (!nested) + { + /* the caller already swallowed the opening '{' */ + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_LEFT_CURLY) + return G_TOKEN_LEFT_CURLY; + } + + g_signal_emit (parser, parser_signals[OBJECT_START], 0); + + object = json_object_new (); + + token = g_scanner_get_next_token (scanner); + while (token != G_TOKEN_RIGHT_CURLY) + { + JsonNode *node = NULL; + gchar *name = NULL; + GValue value = { 0, }; + + if (token == G_TOKEN_COMMA) + { + /* swallow the comma */ + token = g_scanner_get_next_token (scanner); + continue; + } + + if (token == G_TOKEN_STRING) + { + name = g_strdup (scanner->value.v_string); + + token = g_scanner_get_next_token (scanner); + if (token != ':') + { + g_free (name); + json_object_unref (object); + return ':'; + } + else + { + /* swallow the colon */ + token = g_scanner_get_next_token (scanner); + } + } + + if (!name) + { + json_object_unref (object); + return G_TOKEN_STRING; + } + + if (token == G_TOKEN_LEFT_CURLY) + { + JsonNode *old_node = priv->current_node; + + priv->current_node = json_node_new (JSON_NODE_OBJECT); + + token = json_parse_object (parser, scanner, TRUE); + + node = priv->current_node; + priv->current_node = old_node; + + if (token != G_TOKEN_NONE) + { + g_free (name); + json_node_free (node); + json_object_unref (object); + return token; + } + + json_object_add_member (object, name, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[OBJECT_MEMBER], 0, + object, + name); + + g_free (name); + + token = g_scanner_get_next_token (scanner); + if (token == G_TOKEN_RIGHT_CURLY) + break; + + continue; + } + + if (token == G_TOKEN_LEFT_BRACE) + { + JsonNode *old_node = priv->current_node; + + priv->current_node = json_node_new (JSON_NODE_ARRAY); + + token = json_parse_array (parser, scanner, TRUE); + + node = priv->current_node; + priv->current_node = old_node; + + if (token != G_TOKEN_NONE) + { + g_free (name); + json_node_free (node); + json_object_unref (object); + return token; + } + + json_object_add_member (object, name, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[OBJECT_MEMBER], 0, + object, + name); + + g_free (name); + + token = g_scanner_get_next_token (scanner); + if (token == G_TOKEN_RIGHT_BRACE) + break; + + continue; + } + + switch (token) + { + case G_TOKEN_INT: + g_value_init (&value, G_TYPE_INT); + g_value_set_int (&value, scanner->value.v_int); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case G_TOKEN_FLOAT: + g_value_init (&value, G_TYPE_DOUBLE); + g_value_set_double (&value, scanner->value.v_float); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case G_TOKEN_STRING: + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, scanner->value.v_string); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, token == JSON_TOKEN_TRUE ? TRUE : FALSE); + + node = json_node_new (JSON_NODE_VALUE); + json_node_set_value (node, &value); + + g_value_unset (&value); + break; + + case JSON_TOKEN_NULL: + node = json_node_new (JSON_NODE_NULL); + break; + + default: + return G_TOKEN_RIGHT_BRACE; + } + + if (node) + { + json_object_add_member (object, name, node); + node->parent = priv->current_node; + + g_signal_emit (parser, parser_signals[OBJECT_MEMBER], 0, + object, + name); + } + + g_free (name); + + token = g_scanner_get_next_token (scanner); + } + + json_node_take_object (priv->current_node, object); + + g_signal_emit (parser, parser_signals[OBJECT_END], 0, object); + + return G_TOKEN_NONE; +} + +static guint +json_parse_statement (JsonParser *parser, + GScanner *scanner) +{ + JsonParserPrivate *priv = parser->priv; + guint token; + + token = g_scanner_peek_next_token (scanner); + switch (token) + { + case G_TOKEN_LEFT_CURLY: + priv->root = priv->current_node = json_node_new (JSON_NODE_OBJECT); + return json_parse_object (parser, scanner, FALSE); + + case G_TOKEN_LEFT_BRACE: + priv->root = priv->current_node = json_node_new (JSON_NODE_ARRAY); + return json_parse_array (parser, scanner, FALSE); + + case JSON_TOKEN_NULL: + priv->root = priv->current_node = json_node_new (JSON_NODE_NULL); + return G_TOKEN_NONE; + + default: + g_scanner_get_next_token (scanner); + return G_TOKEN_SYMBOL; + } +} + +static void +json_scanner_msg_handler (GScanner *scanner, + gchar *message, + gboolean error) +{ + JsonParser *parser = scanner->user_data; + + if (error) + { + GError *error = NULL; + + g_set_error (&error, JSON_PARSER_ERROR, + JSON_PARSER_ERROR_PARSE, + "Parse error on line %d: %s", + scanner->line, + message); + + g_signal_emit (parser, parser_signals[ERROR], 0, error); + + g_error_free (error); + } + else + g_warning ("Line %d: %s", scanner->line, message); +} + +static GScanner * +json_scanner_new (JsonParser *parser) +{ + GScanner *scanner; + + scanner = g_scanner_new (&json_scanner_config); + scanner->msg_handler = json_scanner_msg_handler; + scanner->user_data = parser; + + return scanner; +} + +/** + * json_parser_new: + * + * Creates a new #JsonParser instance. You can use the #JsonParser to + * load a JSON stream from either a file or a buffer and then walk the + * hierarchy using the data types API. + * + * Return value: the newly created #JsonParser. Use g_object_unref() + * to release all the memory it allocates. + */ +JsonParser * +json_parser_new (void) +{ + return g_object_new (JSON_TYPE_PARSER, NULL); +} + +/** + * json_parser_load_from_file: + * @parser: a #JsonParser + * @filename: the path for the file to parse + * @error: return location for a #GError, or %NULL + * + * Loads a JSON stream from the content of @filename and parses it. See + * json_parser_load_from_data(). + * + * Return value: %TRUE if the file was successfully loaded and parsed. + * In case of error, @error is set accordingly and %FALSE is returned + */ +gboolean +json_parser_load_from_file (JsonParser *parser, + const gchar *filename, + GError **error) +{ + GError *internal_error; + gchar *data; + gsize length; + gboolean retval = TRUE; + + g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + internal_error = NULL; + if (!g_file_get_contents (filename, &data, &length, &internal_error)) + { + g_propagate_error (error, internal_error); + return FALSE; + } + + if (!json_parser_load_from_data (parser, data, length, &internal_error)) + { + g_propagate_error (error, internal_error); + retval = FALSE; + } + + g_free (data); + + return retval; +} + +/** + * json_parser_load_from_data: + * @parser: a #JsonParser + * @data: the buffer to parse + * @length: the length of the buffer, or -1 + * @error: return location for a #GError, or %NULL + * + * Loads a JSON stream from a buffer and parses it. You can call this function + * multiple times with the same #JsonParser object, but the contents of the + * parser will be destroyed each time. + * + * Return value: %TRUE if the buffer was succesfully parser. In case + * of error, @error is set accordingly and %FALSE is returned + */ +gboolean +json_parser_load_from_data (JsonParser *parser, + const gchar *data, + gsize length, + GError **error) +{ + GScanner *scanner; + gboolean done; + gboolean retval = TRUE; + gint i; + + g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE); + g_return_val_if_fail (data != NULL, FALSE); + + if (length < 0) + length = strlen (data); + + if (parser->priv->root) + { + json_node_free (parser->priv->root); + parser->priv->root = NULL; + } + + scanner = json_scanner_new (parser); + g_scanner_input_text (scanner, data, strlen (data)); + + for (i = 0; i < n_symbols; i++) + { + g_scanner_scope_add_symbol (scanner, 0, + symbol_names + symbols[i].name_offset, + GINT_TO_POINTER (symbols[i].token)); + } + + parser->priv->scanner = scanner; + + g_signal_emit (parser, parser_signals[PARSE_START], 0); + + done = FALSE; + while (!done) + { + if (g_scanner_peek_next_token (scanner) == G_TOKEN_EOF) + done = TRUE; + else + { + guint expected_token; + + expected_token = json_parse_statement (parser, scanner); + if (expected_token != G_TOKEN_NONE) + { + const gchar *symbol_name; + gchar *msg; + + msg = NULL; + symbol_name = NULL; + if (scanner->scope_id == 0) + { + if (expected_token > JSON_TOKEN_INVALID && + expected_token < JSON_TOKEN_LAST) + { + for (i = 0; i < n_symbols; i++) + if (symbols[i].token == expected_token) + msg = (gchar *) symbol_names + symbols[i].name_offset; + + if (msg) + msg = g_strconcat ("e.g. `", msg, "'", NULL); + } + + if (scanner->token > JSON_TOKEN_INVALID && + scanner->token < JSON_TOKEN_LAST) + { + symbol_name = "???"; + + for (i = 0; i < n_symbols; i++) + if (symbols[i].token == scanner->token) + symbol_name = symbol_names + symbols[i].name_offset; + } + } + + /* this will emit the ::error signal via the custom + * message handler we install + */ + g_scanner_unexp_token (scanner, expected_token, + NULL, "keyword", + symbol_name, msg, + TRUE); + + /* we set a generic error here; the message from + * GScanner is relayed in the ::error signal + */ + g_set_error (error, JSON_PARSER_ERROR, + JSON_PARSER_ERROR_PARSE, + "Invalid token `%s' found: expecting %s", + symbol_name ? symbol_name : "???", + msg ? msg : "unknown"); + + retval = FALSE; + + g_free (msg); + done = TRUE; + } + } + } + + g_scanner_destroy (scanner); + parser->priv->scanner = NULL; + parser->priv->current_node = NULL; + + g_signal_emit (parser, parser_signals[PARSE_END], 0); + + return retval; +} + +/** + * json_parser_get_root: + * @parser: a #JsonParser + * + * Retrieves the top level node from the parsed JSON stream. + * + * Return value: the root #JsonNode . The returned node is owned by + * the #JsonParser and should never be modified or freed. + */ +JsonNode * +json_parser_get_root (JsonParser *parser) +{ + g_return_val_if_fail (JSON_IS_PARSER (parser), NULL); + + return parser->priv->root; +} + +/** + * json_parser_get_current_line: + * @parser: a #JsonParser + * + * Retrieves the line currently parsed, starting from 1. + * + * Return value: the currently parsed line. + */ +guint +json_parser_get_current_line (JsonParser *parser) +{ + g_return_val_if_fail (JSON_IS_PARSER (parser), 0); + + if (parser->priv->scanner) + return g_scanner_cur_line (parser->priv->scanner); + + return 0; +} + +/** + * json_parser_get_current_pos: + * @parser: a #JsonParser + * + * Retrieves the current position inside the current line, starting + * from 0. + * + * Return value: the position in the current line + */ +guint +json_parser_get_current_pos (JsonParser *parser) +{ + g_return_val_if_fail (JSON_IS_PARSER (parser), 0); + + if (parser->priv->scanner) + return g_scanner_cur_line (parser->priv->scanner); + + return 0; +} diff --git a/clutter/json/json-parser.h b/clutter/json/json-parser.h new file mode 100644 index 000000000..71a17cbfb --- /dev/null +++ b/clutter/json/json-parser.h @@ -0,0 +1,147 @@ +/* json-parser.h - JSON streams parser + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#ifndef __JSON_PARSER_H__ +#define __JSON_PARSER_H__ + +#include +#include "json-types.h" + +G_BEGIN_DECLS + +#define JSON_TYPE_PARSER (json_parser_get_type ()) +#define JSON_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JSON_TYPE_PARSER, JsonParser)) +#define JSON_IS_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), JSON_TYPE_PARSER)) +#define JSON_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), JSON_TYPE_PARSER, JsonParserClass)) +#define JSON_IS_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), JSON_TYPE_PARSER)) +#define JSON_PARSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), JSON_TYPE_PARSER, JsonParserClass)) + +#define JSON_PARSER_ERROR (json_parser_error_quark ()) + +typedef struct _JsonParser JsonParser; +typedef struct _JsonParserPrivate JsonParserPrivate; +typedef struct _JsonParserClass JsonParserClass; + +/** + * JsonParserError: + * @JSON_PARSER_ERROR_PARSE: parse error + * @JSON_PARSER_ERROR_UNKNOWN: unknown error + * + * Error enumeration for #JsonParser + */ +typedef enum { + JSON_PARSER_ERROR_PARSE, + + JSON_PARSER_ERROR_UNKNOWN +} JsonParserError; + +typedef enum { + JSON_TOKEN_INVALID = G_TOKEN_LAST, + JSON_TOKEN_TRUE, + JSON_TOKEN_FALSE, + JSON_TOKEN_NULL, + JSON_TOKEN_LAST +} JsonTokenType; + +/** + * JsonParser: + * + * JSON data streams parser. The contents of the #JsonParser structure are + * private and should only be accessed via the provided API. + */ +struct _JsonParser +{ + /*< private >*/ + GObject parent_instance; + + JsonParserPrivate *priv; +}; + +/** + * JsonParserClass: + * @parse_start: class handler for the JsonParser::parse-start signal + * @object_start: class handler for the JsonParser::object-start signal + * @object_member: class handler for the JsonParser::object-member signal + * @object_end: class handler for the JsonParser::object-end signal + * @array_start: class handler for the JsonParser::array-start signal + * @array_element: class handler for the JsonParser::array-element signal + * @array_end: class handler for the JsonParser::array-end signal + * @parse_end: class handler for the JsonParser::parse-end signal + * @error: class handler for the JsonParser::error signal + * + * #JsonParser class. + */ +struct _JsonParserClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + void (* parse_start) (JsonParser *parser); + + void (* object_start) (JsonParser *parser); + void (* object_member) (JsonParser *parser, + JsonObject *object, + const gchar *member_name); + void (* object_end) (JsonParser *parser, + JsonObject *object); + + void (* array_start) (JsonParser *parser); + void (* array_element) (JsonParser *parser, + JsonArray *array, + gint index_); + void (* array_end) (JsonParser *parser, + JsonArray *array); + + void (* parse_end) (JsonParser *parser); + + void (* error) (JsonParser *parser, + const GError *error); + + /*< private >*/ + /* padding for future expansion */ + void (* _json_reserved1) (void); + void (* _json_reserved2) (void); + void (* _json_reserved3) (void); + void (* _json_reserved4) (void); + void (* _json_reserved5) (void); + void (* _json_reserved6) (void); + void (* _json_reserved7) (void); + void (* _json_reserved8) (void); +}; + +GQuark json_parser_error_quark (void); +GType json_parser_get_type (void) G_GNUC_CONST; + +JsonParser *json_parser_new (void); +gboolean json_parser_load_from_file (JsonParser *parser, + const gchar *filename, + GError **error); +gboolean json_parser_load_from_data (JsonParser *parser, + const gchar *data, + gsize length, + GError **error); +JsonNode * json_parser_get_root (JsonParser *parser); + +guint json_parser_get_current_line (JsonParser *parser); +guint json_parser_get_current_pos (JsonParser *parser); + +G_END_DECLS + +#endif /* __JSON_PARSER_H__ */ diff --git a/clutter/json/json-types.h b/clutter/json/json-types.h new file mode 100644 index 000000000..a95f7fd14 --- /dev/null +++ b/clutter/json/json-types.h @@ -0,0 +1,156 @@ +/* json-types.h - JSON data types + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library 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 of the License, or (at your option) any later version. + * + * This library 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. + * + * Author: + * Emmanuele Bassi + */ + +#ifndef __JSON_TYPES_H__ +#define __JSON_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/** + * JSON_NODE_TYPE: + * @node: a #JsonNode + * + * Evaluates to the #JsonNodeType contained by @node + */ +#define JSON_NODE_TYPE(node) (((JsonNode *) (node))->type) +#define JSON_TYPE_OBJECT (json_object_get_type ()) +#define JSON_TYPE_ARRAY (json_array_get_type ()) + +/** + * JsonObject: + * + * A JSON object type. The contents of the #JsonObject structure are private + * and should only be accessed by the provided API + */ +typedef struct _JsonObject JsonObject; + +/** + * JsonArray: + * + * A JSON array type. The contents of the #JsonArray structure are private + * and should only be accessed by the provided API + */ +typedef struct _JsonArray JsonArray; + +typedef struct _JsonNode JsonNode; + +/** + * JsonNodeType: + * @JSON_NODE_OBJECT: The node contains a #JsonObject + * @JSON_NODE_ARRAY: The node contains a #JsonArray + * @JSON_NODE_VALUE: The node contains a #GValue + * @JSON_NODE_NULL: Special type, for nodes containing %NULL + * + * Indicates the content of a #JsonNode. + */ +typedef enum { + JSON_NODE_OBJECT, + JSON_NODE_ARRAY, + JSON_NODE_VALUE, + JSON_NODE_NULL +} JsonNodeType; + +/** + * JsonNode: + * + * A generic container of JSON data types. The contents of the #JsonNode + * structure are private and should only be accessed via the provided + * functions and never directly. + */ +struct _JsonNode +{ + /*< private >*/ + JsonNodeType type; + + union { + JsonObject *object; + JsonArray *array; + GValue value; + } data; + + JsonNode *parent; +}; + +JsonNode * json_node_new (JsonNodeType type); +JsonNode * json_node_copy (JsonNode *node); +void json_node_free (JsonNode *node); + +void json_node_set_object (JsonNode *node, + JsonObject *object); +void json_node_take_object (JsonNode *node, + JsonObject *object); +JsonObject * json_node_get_object (JsonNode *node); +JsonObject * json_node_dup_object (JsonNode *node); +void json_node_set_array (JsonNode *node, + JsonArray *array); +void json_node_take_array (JsonNode *node, + JsonArray *array); +JsonArray * json_node_get_array (JsonNode *node); +JsonArray * json_node_dup_array (JsonNode *node); +void json_node_set_value (JsonNode *node, + const GValue *value); +void json_node_get_value (JsonNode *node, + GValue *value); +void json_node_set_string (JsonNode *node, + const gchar *value); +G_CONST_RETURN gchar *json_node_get_string (JsonNode *node); +gchar * json_node_dup_string (JsonNode *node); +void json_node_set_int (JsonNode *node, + gint value); +gint json_node_get_int (JsonNode *node); +void json_node_set_double (JsonNode *node, + gdouble value); +gdouble json_node_get_double (JsonNode *node); +void json_node_set_boolean (JsonNode *node, + gboolean value); +gboolean json_node_get_boolean (JsonNode *node); +JsonNode * json_node_get_parent (JsonNode *node); +G_CONST_RETURN gchar *json_node_type_name (JsonNode *node); + +GType json_object_get_type (void) G_GNUC_CONST; +JsonObject * json_object_new (void); +JsonObject * json_object_ref (JsonObject *object); +void json_object_unref (JsonObject *object); +void json_object_add_member (JsonObject *object, + const gchar *member_name, + JsonNode *node); +GList * json_object_get_members (JsonObject *object); +JsonNode * json_object_get_member (JsonObject *object, + const gchar *member_name); +gboolean json_object_has_member (JsonObject *object, + const gchar *member_name); +guint json_object_get_size (JsonObject *object); + +GType json_array_get_type (void) G_GNUC_CONST; +JsonArray * json_array_new (void); +JsonArray * json_array_sized_new (guint n_elements); +JsonArray * json_array_ref (JsonArray *array); +void json_array_unref (JsonArray *array); +void json_array_add_element (JsonArray *array, + JsonNode *node); +GList * json_array_get_elements (JsonArray *array); +JsonNode * json_array_get_element (JsonArray *array, + guint index_); +guint json_array_get_length (JsonArray *array); + +G_END_DECLS + +#endif /* __JSON_TYPES_H__ */ diff --git a/configure.ac b/configure.ac index 3b11b27dd..0ded302eb 100644 --- a/configure.ac +++ b/configure.ac @@ -273,7 +273,7 @@ AC_SUBST([clutterbackendlib]) dnl ======================================================================== -pkg_modules="pangoft2 glib-2.0 >= 2.10 gobject-2.0 gthread-2.0 gdk-pixbuf-2.0 $BACKEND_PC_FILES" +pkg_modules="pangoft2 glib-2.0 >= 2.14 gobject-2.0 gthread-2.0 gdk-pixbuf-2.0 $BACKEND_PC_FILES" PKG_CHECK_MODULES(CLUTTER_DEPS, [$pkg_modules]) dnl ======================================================================== @@ -359,7 +359,6 @@ AC_SUBST(CLUTTER_LIBS) AC_CONFIG_FILES([ Makefile - clutter/pango/Makefile clutter/Makefile clutter/clutter-version.h clutter/glx/Makefile @@ -369,6 +368,8 @@ AC_CONFIG_FILES([ clutter/cogl/Makefile clutter/cogl/gl/Makefile clutter/cogl/gles/Makefile + clutter/json/Makefile + clutter/pango/Makefile tests/Makefile doc/Makefile doc/reference/Makefile diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index c7f81461a..7380da81a 100644 --- a/doc/reference/Makefile.am +++ b/doc/reference/Makefile.am @@ -60,8 +60,9 @@ IGNORE_HFILES=\ eglnative \ eglx \ glx \ - sdl \ - pango + json \ + pango \ + sdl EXTRA_HFILES=\ ../../clutter/glx/clutter-glx.h diff --git a/tests/Makefile.am b/tests/Makefile.am index 97a1750fe..86ed85014 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,7 +1,7 @@ noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \ test-actors test-behave test-text test-entry test-project \ test-boxes test-perspective test-rotate test-depth \ - test-threads test-timeline test-score + test-threads test-timeline test-score test-script INCLUDES = -I$(top_srcdir)/ LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la @@ -24,5 +24,6 @@ test_depth_SOURCES = test-depth.c test_threads_SOURCES = test-threads.c test_timeline_SOURCES = test-timeline.c test_score_SOURCES = test-score.c +test_script_SOURCES = test-script.c EXTRA_DIST = redhand.png diff --git a/tests/test-script.c b/tests/test-script.c new file mode 100644 index 000000000..2cc45d96f --- /dev/null +++ b/tests/test-script.c @@ -0,0 +1,81 @@ +#include +#include + +#include + +#include + +static const gchar *test_ui = +"{" +" \"Scene\" : {" +" \"id\" : \"main-stage\"," +" \"type\" : \"ClutterStage\"," +" \"color\" : \"white\"," +" \"width\" : 500," +" \"height\" : 200," +" \"children\" : [" +" {" +" \"id\" : \"red-button\"," +" \"type\" : \"ClutterRectangle\"," +" \"color\" : \"#ff0000ff\"," +" \"x\" : 50," +" \"y\" : 50," +" \"width\" : 100," +" \"height\" : 100," +" \"visible\" : true," +" }," +" {" +" \"id\" : \"green-button\"," +" \"type\" : \"ClutterRectangle\"," +" \"color\" : \"#00ff00ff\"," +" \"x\" : 200," +" \"y\" : 50," +" \"width\" : 100," +" \"height\" : 100," +" \"visible\" : true," +" }," +" {" +" \"id\" : \"blue-button\"," +" \"type\" : \"ClutterRectangle\"," +" \"color\" : \"#0000ffff\"," +" \"x\" : 350," +" \"y\" : 50," +" \"width\" : 100," +" \"height\" : 100," +" \"visible\" : true," +" }" +" ]" +" }" +"}"; + +int +main (int argc, char *argv[]) +{ + ClutterActor *stage; + ClutterActor *rect; + ClutterScript *script; + GError *error; + + clutter_init (&argc, &argv); + + script = clutter_script_new (); + error = NULL; + clutter_script_load_from_data (script, test_ui, -1, &error); + if (error) + { + g_print ("*** Error:\n" + "*** %s\n", error->message); + g_error_free (error); + g_object_unref (script); + return EXIT_FAILURE; + } + + stage = CLUTTER_ACTOR (clutter_script_get_object (script, "main-stage")); + clutter_actor_show (stage); + + clutter_main (); + + g_object_unref (script); + + return EXIT_SUCCESS; +}