[json-glib/wip/json-path] Snapshot/WIP
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [json-glib/wip/json-path] Snapshot/WIP
- Date: Tue, 31 May 2011 11:04:52 +0000 (UTC)
commit e97b42dcac55d520210367e47c57c46390fc92a7
Author: Emmanuele Bassi <ebassi gnome org>
Date: Sat May 28 14:36:43 2011 +0100
Snapshot/WIP
json-glib/Makefile.am | 2 +
json-glib/json-glib.h | 1 +
json-glib/json-path.c | 424 +++++++++++++++++++++++++++++++++++++++++++
json-glib/json-path.h | 42 +++++
json-glib/tests/Makefile.am | 1 +
json-glib/tests/path-test.c | 136 ++++++++++++++
6 files changed, 606 insertions(+), 0 deletions(-)
---
diff --git a/json-glib/Makefile.am b/json-glib/Makefile.am
index 6a0f8b7..2a88ec3 100644
--- a/json-glib/Makefile.am
+++ b/json-glib/Makefile.am
@@ -34,6 +34,7 @@ source_h = \
$(top_srcdir)/json-glib/json-generator.h \
$(top_srcdir)/json-glib/json-gobject.h \
$(top_srcdir)/json-glib/json-parser.h \
+ $(top_srcdir)/json-glib/json-path.h \
$(top_srcdir)/json-glib/json-reader.h \
$(top_srcdir)/json-glib/json-types.h \
$(top_srcdir)/json-glib/json-gvariant.h \
@@ -56,6 +57,7 @@ source_c = \
$(srcdir)/json-node.c \
$(srcdir)/json-object.c \
$(srcdir)/json-parser.c \
+ $(srcdir)/json-path.c \
$(srcdir)/json-reader.c \
$(srcdir)/json-scanner.c \
$(srcdir)/json-serializable.c \
diff --git a/json-glib/json-glib.h b/json-glib/json-glib.h
index a14416b..257bcdf 100644
--- a/json-glib/json-glib.h
+++ b/json-glib/json-glib.h
@@ -31,6 +31,7 @@
#include <json-glib/json-builder.h>
#include <json-glib/json-generator.h>
#include <json-glib/json-parser.h>
+#include <json-glib/json-path.h>
#include <json-glib/json-reader.h>
#include <json-glib/json-version.h>
diff --git a/json-glib/json-path.c b/json-glib/json-path.c
new file mode 100644
index 0000000..2af459c
--- /dev/null
+++ b/json-glib/json-path.c
@@ -0,0 +1,424 @@
+#ifndef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "json-path.h"
+
+#include "json-debug.h"
+#include "json-types-private.h"
+
+typedef enum {
+ JSON_PATH_NODE_ROOT,
+ JSON_PATH_NODE_CHILD_MEMBER,
+ JSON_PATH_NODE_CHILD_ELEMENT,
+ JSON_PATH_NODE_RECURSIVE_DESCENT,
+ JSON_PATH_NODE_WILDCARD_MEMBER,
+ JSON_PATH_NODE_WILDCARD_ELEMENT,
+ JSON_PATH_NODE_WILDCARD,
+ JSON_PATH_NODE_SET,
+ JSON_PATH_NODE_SLICE
+} PathNodeType;
+
+typedef struct _PathNode PathNode;
+
+struct _JsonPath
+{
+ GObject parent_instance;
+
+ GList *nodes;
+
+ guint is_compiled : 1;
+};
+
+struct _JsonPathClass
+{
+ GObjectClass parent_class;
+};
+
+struct _PathNode
+{
+ PathNodeType node_type;
+
+ union {
+ int element_index;
+ char *member_name;
+ struct { int start, end, step; } slice;
+ } data;
+};
+
+G_DEFINE_TYPE (JsonPath, json_path, G_TYPE_OBJECT)
+
+static void
+path_node_free (gpointer data)
+{
+ if (data != NULL)
+ {
+ PathNode *node = data;
+
+ switch (node->node_type)
+ {
+ case JSON_PATH_NODE_CHILD_MEMBER:
+ g_free (node->data.member_name);
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (node);
+ }
+}
+
+static void
+json_path_finalize (GObject *gobject)
+{
+ JsonPath *self = JSON_PATH (gobject);
+
+ g_list_free_full (self->nodes, path_node_free);
+
+ G_OBJECT_CLASS (json_path_parent_class)->finalize (gobject);
+}
+
+static void
+json_path_class_init (JsonPathClass *klass)
+{
+ G_OBJECT_CLASS (klass)->finalize = json_path_finalize;
+}
+
+static void
+json_path_init (JsonPath *self)
+{
+}
+
+GQuark
+json_path_error_quark (void)
+{
+ return g_quark_from_static_string ("json-path-error");
+}
+
+JsonPath *
+json_path_new (void)
+{
+ return g_object_new (JSON_TYPE_PATH, NULL);
+}
+
+gboolean
+json_path_compile (JsonPath *path,
+ const char *expression,
+ GError **error)
+{
+ const char *p, *end_p;
+ PathNode *root = NULL;
+ GList *nodes, *l;
+
+ p = expression;
+
+ while (*p != '\0')
+ {
+ switch (*p)
+ {
+ case '$':
+ {
+ PathNode *node;
+
+ if (root != NULL)
+ {
+ g_print ("Multiple roots\n");
+ return FALSE;
+ }
+
+ if (!(*(p + 1) == '.' || *(p + 1) == '['))
+ {
+ g_print ("Root node followed by '%c'\n", *(p + 1));
+ return FALSE;
+ }
+
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_ROOT;
+
+ root = node;
+ nodes = g_list_prepend (NULL, root);
+ }
+ break;
+
+ case '.':
+ case '[':
+ {
+ PathNode *node = NULL;
+
+ if (*p == '.' && *(p + 1) == '.')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_RECURSIVE_DESCENT;
+ }
+ else if (*p == '.' && *(p + 1) == '*')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_WILDCARD_MEMBER;
+
+ p += 1;
+ }
+ else if (*p == '.')
+ {
+ end_p = p + 1;
+ while (!(*end_p == '.' || *end_p == '[' || *end_p == '\0'))
+ end_p += 1;
+
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_CHILD_MEMBER;
+ node->data.member_name = g_strndup (p + 1, end_p - p - 1);
+
+ p = end_p - 1;
+ }
+ else if (*p == '[' && *(p + 1) == '\'')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_CHILD_MEMBER;
+
+ end_p = strchr (p + 2, '\'');
+ node->data.member_name = g_strndup (p + 2, end_p - p - 2);
+
+ p = end_p + 1;
+ }
+ else if (*p == '[' && g_ascii_isdigit (*(p + 1)))
+ {
+ end_p = p;
+
+ while (*end_p != ']')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_CHILD_ELEMENT;
+ node->data.element_index = g_ascii_strtoll (end_p + 1, (gchar **) &end_p, 10);
+
+ nodes = g_list_prepend (nodes, node);
+ }
+
+ node = NULL;
+ p = end_p;
+ }
+ else if (*p == '[' && *(p + 1) == '*')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_WILDCARD_ELEMENT;
+
+ p += 1;
+ }
+ else
+ break;
+
+ if (node != NULL)
+ nodes = g_list_prepend (nodes, node);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ p += 1;
+ }
+
+ g_print ("expression: '%s' => ", expression);
+
+ nodes = g_list_reverse (nodes);
+
+ for (l = nodes; l != NULL; l = l->next)
+ {
+ PathNode *cur_node = l->data;
+
+ switch (cur_node->node_type)
+ {
+ case JSON_PATH_NODE_ROOT:
+ g_print ("<root");
+ break;
+
+ case JSON_PATH_NODE_CHILD_MEMBER:
+ g_print ("<member '%s'", cur_node->data.member_name);
+ break;
+
+ case JSON_PATH_NODE_CHILD_ELEMENT:
+ g_print ("<element '%d'", cur_node->data.element_index);
+ break;
+
+ case JSON_PATH_NODE_RECURSIVE_DESCENT:
+ g_print ("<recursive descent");
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_MEMBER:
+ g_print ("<wildcard member");
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_ELEMENT:
+ g_print ("<wildcard element");
+ break;
+
+ default:
+ g_print ("<unknown node");
+ break;
+ }
+
+ if (l->next != NULL)
+ g_print (">, ");
+ else
+ g_print (">\n");
+ }
+
+ if (path->nodes != NULL)
+ g_list_free_full (path->nodes, path_node_free);
+
+ path->nodes = nodes;
+ path->is_compiled = (path->nodes != NULL);
+
+ return path->nodes != NULL;
+}
+
+static void
+walk_path_node (GList *path,
+ JsonNode *root,
+ JsonArray *results,
+ GError **error)
+{
+ PathNode *node = path->data;
+
+ switch (node->node_type)
+ {
+ case JSON_PATH_NODE_ROOT:
+ walk_path_node (path->next, root, results, error);
+ break;
+
+ case JSON_PATH_NODE_CHILD_MEMBER:
+ if (JSON_NODE_HOLDS_OBJECT (root))
+ {
+ JsonObject *object = json_node_get_object (root);
+
+ if (json_object_has_member (object, node->data.member_name))
+ {
+ JsonNode *member = json_object_get_member (object, node->data.member_name);
+
+ if (path->next == NULL)
+ {
+ g_print (G_STRLOC ": end of path at member '%s'\n", node->data.member_name);
+ json_array_add_element (results, json_node_copy (member));
+ }
+ else
+ walk_path_node (path->next, member, results, error);
+ }
+ }
+ break;
+
+ case JSON_PATH_NODE_CHILD_ELEMENT:
+ if (JSON_NODE_HOLDS_ARRAY (root))
+ {
+ JsonArray *array = json_node_get_array (root);
+
+ if (json_array_get_length (array) >= node->data.element_index)
+ {
+ JsonNode *element = json_array_get_element (array, node->data.element_index);
+
+ if (path->next == NULL)
+ {
+ g_print (G_STRLOC ": end of path at element '%d'\n", node->data.element_index);
+ json_array_add_element (results, json_node_copy (element));
+ }
+ else
+ walk_path_node (path->next, element, results, error);
+ }
+ }
+ break;
+
+ case JSON_PATH_NODE_RECURSIVE_DESCENT:
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_MEMBER:
+ if (JSON_NODE_HOLDS_OBJECT (root))
+ {
+ JsonObject *object = json_node_get_object (root);
+ GList *members, *l;
+
+ members = json_object_get_members (object);
+ for (l = members; l != NULL; l = l->next)
+ {
+ JsonNode *member = json_object_get_member (object, l->data);
+
+ if (path->next != NULL)
+ walk_path_node (path->next, member, results, error);
+ else
+ json_array_add_element (results, json_node_copy (root));
+ }
+ g_list_free (members);
+ }
+ else
+ json_array_add_element (results, json_node_copy (root));
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_ELEMENT:
+ if (JSON_NODE_HOLDS_ARRAY (root))
+ {
+ JsonArray *array = json_node_get_array (root);
+ GList *elements, *l;
+
+ elements = json_array_get_elements (array);
+ for (l = elements; l != NULL; l = l->next)
+ {
+ JsonNode *element = l->data;
+
+ if (path->next != NULL)
+ walk_path_node (path->next, element, results, error);
+ else
+ json_array_add_element (results, json_node_copy (root));
+ }
+ g_list_free (elements);
+ }
+ else
+ json_array_add_element (results, json_node_copy (root));
+ break;
+
+ default:
+ break;
+ }
+}
+
+JsonNode *
+json_path_match (JsonPath *path,
+ JsonNode *root,
+ GError **error)
+{
+ JsonArray *results;
+ JsonNode *retval;
+
+ g_return_val_if_fail (JSON_IS_PATH (path), NULL);
+ g_return_val_if_fail (path->is_compiled, NULL);
+ g_return_val_if_fail (root != NULL, NULL);
+
+ results = json_array_new ();
+
+ walk_path_node (path->nodes, root, results, error);
+
+ retval = json_node_new (JSON_NODE_ARRAY);
+ json_node_take_array (retval, results);
+
+ return retval;
+}
+
+JsonNode *
+json_path_query (const char *expression,
+ JsonNode *root,
+ GError **error)
+{
+ JsonPath *path = json_path_new ();
+ JsonNode *retval;
+
+ if (!json_path_compile (path, expression, error))
+ {
+ g_object_unref (path);
+ return NULL;
+ }
+
+ retval = json_path_match (path, root, error);
+
+ g_object_unref (path);
+
+ return retval;
+}
diff --git a/json-glib/json-path.h b/json-glib/json-path.h
new file mode 100644
index 0000000..d4cd8fd
--- /dev/null
+++ b/json-glib/json-path.h
@@ -0,0 +1,42 @@
+#if !defined(__JSON_GLIB_INSIDE__) && !defined(JSON_COMPILATION)
+#error "Only <json-glib/json-glib.h> can be included directly."
+#endif
+
+#ifndef __JSON_PATH_H__
+#define __JSON_PATH_H__
+
+#include <json-glib/json-types.h>
+
+G_BEGIN_DECLS
+
+#define JSON_TYPE_PATH (json_path_get_type ())
+#define JSON_PATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JSON_TYPE_PATH, JsonPath))
+#define JSON_IS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), JSON_TYPE_PATH))
+
+typedef enum {
+ JSON_PATH_ERROR_INVALID_QUERY
+} JsonPathError;
+
+typedef struct _JsonPath JsonPath;
+
+typedef struct _JsonPathClass JsonPathClass;
+
+GType json_path_get_type (void) G_GNUC_CONST;
+GQuark json_path_error_quark (void);
+
+JsonPath * json_path_new (void);
+
+gboolean json_path_compile (JsonPath *path,
+ const char *expression,
+ GError **error);
+JsonNode * json_path_match (JsonPath *path,
+ JsonNode *root,
+ GError **error);
+
+JsonNode * json_path_query (const char *expression,
+ JsonNode *root,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __JSON_PATH_H__ */
diff --git a/json-glib/tests/Makefile.am b/json-glib/tests/Makefile.am
index fea656d..5e21412 100644
--- a/json-glib/tests/Makefile.am
+++ b/json-glib/tests/Makefile.am
@@ -26,6 +26,7 @@ TEST_PROGS += \
builder-test \
reader-test \
gvariant-test \
+ path-test \
boxed \
serialize-simple \
serialize-complex \
diff --git a/json-glib/tests/path-test.c b/json-glib/tests/path-test.c
new file mode 100644
index 0000000..e3bef36
--- /dev/null
+++ b/json-glib/tests/path-test.c
@@ -0,0 +1,136 @@
+#include <string.h>
+#include <glib.h>
+#include <json-glib/json-glib.h>
+
+static const char *test_json =
+"{ \"store\": {"
+" \"book\": [ "
+" { \"category\": \"reference\","
+" \"author\": \"Nigel Rees\","
+" \"title\": \"Sayings of the Century\","
+" \"price\": 8.95"
+" },"
+" { \"category\": \"fiction\","
+" \"author\": \"Evelyn Waugh\","
+" \"title\": \"Sword of Honour\","
+" \"price\": 12.99"
+" },"
+" { \"category\": \"fiction\","
+" \"author\": \"Herman Melville\","
+" \"title\": \"Moby Dick\","
+" \"isbn\": \"0-553-21311-3\","
+" \"price\": 8.99"
+" },"
+" { \"category\": \"fiction\","
+" \"author\": \"J. R. R. Tolkien\","
+" \"title\": \"The Lord of the Rings\","
+" \"isbn\": \"0-395-19395-8\","
+" \"price\": 22.99"
+" }"
+" ],"
+" \"bicycle\": {"
+" \"color\": \"red\","
+" \"price\": 19.95"
+" }"
+" }"
+"}";
+
+static const char *test_expressions[] = {
+ "$.store.book[0].title",
+ "$['store']['book'][0]['title']",
+ "$.store.book[*].author",
+ "$..author",
+ "$.store.*",
+ "$.store..price",
+ "$..book[2]",
+ "$..book[-1:]",
+ "$..book[0,1]",
+ "$..book[:2]",
+};
+
+static const char *test_results[] = {
+ "[\"Sayings of the Century\"]",
+ "[\"Sayings of the Century\"]",
+ "[\"Nigel Rees\",\"Evelyn Waugh\",\"Herman Melville\",\"J. R. R. Tolkien\"]",
+ "[\"Nigel Rees\",\"Evelyn Waugh\",\"Herman Melville\",\"J. R. R. Tolkien\"]",
+ NULL,
+ NULL,
+ "[{\"category\":\"fiction\",\"author\":\"Herman Melville\",\"title\":\"Moby Dick\",\"isbn\":\"0-553-21311-3\",\"price\":8.99}]",
+ NULL,
+ NULL,
+ NULL,
+};
+
+static void
+test_expression (void)
+{
+ JsonPath *path = json_path_new ();
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (test_expressions); i++)
+ {
+ const char *expr = test_expressions[i];
+
+ g_assert (json_path_compile (path, expr, NULL));
+ }
+
+ g_object_unref (path);
+}
+
+static void
+test_match (void)
+{
+ JsonParser *parser = json_parser_new ();
+ JsonGenerator *gen = json_generator_new ();
+ JsonPath *path = json_path_new ();
+ JsonNode *root;
+ int i;
+
+ json_parser_load_from_data (parser, test_json, -1, NULL);
+ root = json_parser_get_root (parser);
+
+ for (i = 0; i < G_N_ELEMENTS (test_expressions); i++)
+ {
+ const char *expr = test_expressions[i];
+ const char *res = test_results[i];
+ JsonNode *matches;
+ char *str;
+
+ if (res == NULL || *res == '\0')
+ continue;
+
+ g_assert (json_path_compile (path, expr, NULL));
+
+ matches = json_path_match (path, root, NULL);
+ g_assert (JSON_NODE_HOLDS_ARRAY (matches));
+
+ json_generator_set_root (gen, matches);
+ str = json_generator_to_data (gen, NULL);
+
+ if (g_test_verbose ())
+ g_print ("* expr[%02d]: '%s' =>\n- result: %s\n- expected: %s\n", i, expr, str, res);
+
+ g_assert_cmpstr (str, ==, res);
+
+ g_free (str);
+ json_node_free (matches);
+ }
+
+ g_object_unref (parser);
+ g_object_unref (path);
+ g_object_unref (gen);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+ g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=");
+
+ g_test_add_func ("/path/expressions", test_expression);
+ g_test_add_func ("/path/match", test_match);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]