[json-glib: 1/2] Add initial JSONPath implementation
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [json-glib: 1/2] Add initial JSONPath implementation
- Date: Tue, 31 May 2011 22:17:00 +0000 (UTC)
commit 4ea8cd43986d5888fb8e809a198d6b0331b12480
Author: Emmanuele Bassi <ebassi gnome org>
Date: Sat May 28 14:36:43 2011 +0100
Add initial JSONPath implementation
JSONPath is a JSON query syntax similar to what XPath does for XML;
using JSONPath it's possible to address a specific node (or set of
nodes) inside a JSON document.
The JsonPath class is a simple implementation of part of the JSONPath
proposal, as formalised by Stefan Gössner here:
http://goessner.net/articles/JsonPath/
The covered operators are:
â?¢ root, or '$';
â?¢ child, both using the dot-notation and the bracket notation;
â?¢ recursive descent, or '..';
â?¢ subscript, or '[]';
â?¢ set, or '[,]';
â?¢ slice, or '[start:end:step]'.
The only missing operators are the filter, or '?()' and the script,
or '()', because implementing a JavaScript interpreter inside JSON-GLib
is not one of my greatest aspirations. It should be possible, though,
to parse and evaluate simple arithmetic conditions, in the future.
The JsonPath methods are pretty straightforward: a JsonPath instance
should be created and used to compile an expression; the compilation
might result in a syntax error or not. Then, the JsonPath instance can
be used to match any JSON tree. Like the other JSONPath implementations,
JsonPath returns a JSON array of matching nodes.
A simple, one-off static method called json_path_query() is also
provided; the method wraps the JsonPath creation, the expression
compilation, and the matching, as well as disposing the JsonPath
instance once done.
For the time being, only positive testing is provided; negative testing
for the expression compilation will follow.
json-glib/Makefile.am | 2 +
json-glib/json-debug.c | 3 +-
json-glib/json-debug.h | 3 +-
json-glib/json-glib.h | 1 +
json-glib/json-path.c | 856 +++++++++++++++++++++++++++++++++++++++++++
json-glib/json-path.h | 97 +++++
json-glib/tests/Makefile.am | 1 +
json-glib/tests/path-test.c | 143 +++++++
8 files changed, 1104 insertions(+), 2 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-debug.c b/json-glib/json-debug.c
index dd725c4..c0dc2e9 100644
--- a/json-glib/json-debug.c
+++ b/json-glib/json-debug.c
@@ -10,7 +10,8 @@ static gboolean json_debug_flags_set = FALSE;
#ifdef JSON_ENABLE_DEBUG
static const GDebugKey json_debug_keys[] = {
{ "parser", JSON_DEBUG_PARSER },
- { "gobject", JSON_DEBUG_GOBJECT }
+ { "gobject", JSON_DEBUG_GOBJECT },
+ { "path", JSON_DEBUG_PATH }
};
#endif /* JSON_ENABLE_DEBUG */
diff --git a/json-glib/json-debug.h b/json-glib/json-debug.h
index 20f22c6..695917f 100644
--- a/json-glib/json-debug.h
+++ b/json-glib/json-debug.h
@@ -7,7 +7,8 @@ G_BEGIN_DECLS
typedef enum {
JSON_DEBUG_PARSER = 1 << 0,
- JSON_DEBUG_GOBJECT = 1 << 1
+ JSON_DEBUG_GOBJECT = 1 << 1,
+ JSON_DEBUG_PATH = 1 << 2
} JsonDebugFlags;
#ifdef JSON_ENABLE_DEBUG
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..5d00710
--- /dev/null
+++ b/json-glib/json-path.c
@@ -0,0 +1,856 @@
+/* json-path.h - JSONPath implementation
+ *
+ * This file is part of JSON-GLib
+ * Copyright © 2011 Intel Corp.
+ *
+ * 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.1 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ * Emmanuele Bassi <ebassi linux intel com>
+ */
+
+/**
+ * SECTION:json-path
+ * @Title: JsonPath
+ * @Short_Desc: JSONPath implementation
+ *
+ * JSONPath is FIXME
+ *
+ * #JsonPath is available since JSON-GLib 0.14
+ */
+
+#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_ELEMENT_SET,
+ JSON_PATH_NODE_ELEMENT_SLICE
+} PathNodeType;
+
+typedef struct _PathNode PathNode;
+
+struct _JsonPath
+{
+ GObject parent_instance;
+
+ /* the compiled path */
+ GList *nodes;
+
+ guint is_compiled : 1;
+};
+
+struct _JsonPathClass
+{
+ GObjectClass parent_class;
+};
+
+struct _PathNode
+{
+ PathNodeType node_type;
+
+ union {
+ /* JSON_PATH_NODE_CHILD_ELEMENT */
+ int element_index;
+
+ /* JSON_PATH_NODE_CHILD_MEMBER */
+ char *member_name;
+
+ /* JSON_PATH_NODE_ELEMENT_SET */
+ struct { int n_indices; int *indices; } set;
+
+ /* JSON_PATH_NODE_ELEMENT_SLICE */
+ 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;
+
+ case JSON_PATH_NODE_ELEMENT_SET:
+ g_free (node->data.set.indices);
+ 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");
+}
+
+/**
+ * json_path_new:
+ *
+ * Creates a new #JsonPath instance.
+ *
+ * Once created, the #JsonPath object should be used with json_path_compile()
+ * and json_path_match().
+ *
+ * Return value: (transfer full): the newly created #JsonPath instance. Use
+ * g_object_unref() to free the allocated resources when done
+ *
+ * Since: 0.14
+ */
+JsonPath *
+json_path_new (void)
+{
+ return g_object_new (JSON_TYPE_PATH, NULL);
+}
+
+/**
+ * json_path_compile:
+ * @path: a #JsonPath
+ * @expression: a JSONPath expression
+ * @error: return location for a #GError, or %NULL
+ *
+ * Validates and decomposes @expression.
+ *
+ * A JSONPath expression must be compiled before calling json_path_match().
+ *
+ * Return value: %TRUE on success; on error, @error will be set with
+ * the %JSON_PATH_ERROR domain and a code from the #JsonPathError
+ * enumeration, and %FALSE will be returned
+ *
+ * Since: 0.14
+ */
+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_set_error_literal (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Multiple roots");
+ return FALSE;
+ }
+
+ if (!(*(p + 1) == '.' || *(p + 1) == '['))
+ {
+ g_set_error (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Root node followed by '%c'",
+ *(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) == '\'')
+ {
+ if (*(p + 2) == '*' && *(p + 3) == '\'' && *(p + 4) == ']')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_WILDCARD_MEMBER;
+
+ p += 4;
+ }
+ else
+ {
+ 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 == '[' && *(p + 1) == '*' && *(p + 2) == ']')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_WILDCARD_ELEMENT;
+
+ p += 1;
+ }
+ else if (*p == '[')
+ {
+ int sign = 1;
+ int idx;
+
+ end_p = p + 1;
+
+ if (*end_p == '-')
+ {
+ sign = -1;
+ end_p += 1;
+ }
+
+ /* slice with missing start */
+ if (*end_p == ':')
+ {
+ int slice_end = g_ascii_strtoll (end_p + 1, (char **) &end_p, 10) * sign;
+ int slice_step = 1;
+
+ if (*end_p == ':')
+ {
+ end_p += 1;
+
+ if (*end_p == '-')
+ {
+ sign = -1;
+ end_p += 1;
+ }
+ else
+ sign = 1;
+
+ slice_step = g_ascii_strtoll (end_p, (char **) &end_p, 10) * sign;
+
+ if (*end_p != ']')
+ {
+ g_set_error (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Malformed slice '%*s'",
+ end_p - p,
+ p + 1);
+ goto fail;
+ }
+ }
+
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_ELEMENT_SLICE;
+ node->data.slice.start = 0;
+ node->data.slice.end = slice_end;
+ node->data.slice.step = slice_step;
+
+ nodes = g_list_prepend (nodes, node);
+ p = end_p;
+ break;
+ }
+
+ idx = g_ascii_strtoll (end_p, (char **) &end_p, 10) * sign;
+
+ if (*end_p == ',')
+ {
+ GArray *indices = g_array_new (FALSE, TRUE, sizeof (int));
+
+ g_array_append_val (indices, idx);
+
+ while (*end_p != ']')
+ {
+ end_p += 1;
+
+ if (*end_p == '-')
+ {
+ sign = -1;
+ end_p += 1;
+ }
+ else
+ sign = 1;
+
+ idx = g_ascii_strtoll (end_p, (char **) &end_p, 10) * sign;
+ if (!(*end_p == ',' || *end_p == ']'))
+ {
+ g_array_unref (indices);
+ g_set_error (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Invalid set definition '%*s'",
+ end_p - p,
+ p + 1);
+ goto fail;
+ }
+
+ g_array_append_val (indices, idx);
+ }
+
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_ELEMENT_SET;
+ node->data.set.n_indices = indices->len;
+ node->data.set.indices = (int *) g_array_free (indices, FALSE);
+ nodes = g_list_prepend (nodes, node);
+ p = end_p;
+ break;
+ }
+ else if (*end_p == ':')
+ {
+ int slice_start = idx;
+ int slice_end = 0;
+ int slice_step = 1;
+
+ end_p += 1;
+
+ if (*end_p == '-')
+ {
+ sign = -1;
+ end_p += 1;
+ }
+ else
+ sign = 1;
+
+ slice_end = g_ascii_strtoll (end_p, (char **) &end_p, 10) * sign;
+ if (*end_p == ':')
+ {
+ end_p += 1;
+
+ if (*end_p == '-')
+ {
+ sign = -1;
+ end_p += 1;
+ }
+ else
+ sign = 1;
+
+ slice_step = g_ascii_strtoll (end_p + 1, (char **) &end_p, 10) * sign;
+ }
+
+ if (*end_p != ']')
+ {
+ g_set_error (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Invalid slice definition '%*s'",
+ end_p - p,
+ p + 1);
+ goto fail;
+ }
+
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_ELEMENT_SLICE;
+ node->data.slice.start = slice_start;
+ node->data.slice.end = slice_end;
+ node->data.slice.step = slice_step;
+ nodes = g_list_prepend (nodes, node);
+ p = end_p;
+ break;
+ }
+ else if (*end_p == ']')
+ {
+ node = g_new0 (PathNode, 1);
+ node->node_type = JSON_PATH_NODE_CHILD_ELEMENT;
+ node->data.element_index = idx;
+ nodes = g_list_prepend (nodes, node);
+ p = end_p;
+ break;
+ }
+ else
+ {
+ g_set_error (error, JSON_PATH_ERROR,
+ JSON_PATH_ERROR_INVALID_QUERY,
+ "Invalid array index '%*s'",
+ end_p - p,
+ p + 1);
+ goto fail;
+ }
+ }
+ else
+ break;
+
+ if (node != NULL)
+ nodes = g_list_prepend (nodes, node);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ p += 1;
+ }
+
+ nodes = g_list_reverse (nodes);
+
+#ifdef JSON_ENABLE_DEBUG
+ if (_json_get_debug_flags () & JSON_DEBUG_PATH)
+ {
+ GString *buf = g_string_new (NULL);
+
+ for (l = nodes; l != NULL; l = l->next)
+ {
+ PathNode *cur_node = l->data;
+
+ switch (cur_node->node_type)
+ {
+ case JSON_PATH_NODE_ROOT:
+ g_string_append (buf, "<root");
+ break;
+
+ case JSON_PATH_NODE_CHILD_MEMBER:
+ g_string_append_printf (buf, "<member '%s'", cur_node->data.member_name);
+ break;
+
+ case JSON_PATH_NODE_CHILD_ELEMENT:
+ g_string_append_printf (buf, "<element '%d'", cur_node->data.element_index);
+ break;
+
+ case JSON_PATH_NODE_RECURSIVE_DESCENT:
+ g_string_append (buf, "<recursive descent");
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_MEMBER:
+ g_string_append (buf, "<wildcard member");
+ break;
+
+ case JSON_PATH_NODE_WILDCARD_ELEMENT:
+ g_string_append (buf, "<wildcard element");
+ break;
+
+ case JSON_PATH_NODE_ELEMENT_SET:
+ {
+ int i;
+
+ g_string_append (buf, "<element set ");
+ for (i = 0; i < cur_node->data.set.n_indices - 1; i++)
+ g_string_append_printf (buf, "'%d', ", cur_node->data.set.indices[i]);
+
+ g_string_append_printf (buf, "'%d'", cur_node->data.set.indices[i]);
+ }
+ break;
+
+ case JSON_PATH_NODE_ELEMENT_SLICE:
+ g_string_append_printf (buf, "<slice start '%d', end '%d', step '%d'",
+ cur_node->data.slice.start,
+ cur_node->data.slice.end,
+ cur_node->data.slice.step);
+ break;
+
+ default:
+ g_string_append (buf, "<unknown node");
+ break;
+ }
+
+ if (l->next != NULL)
+ g_string_append (buf, ">, ");
+ else
+ g_string_append (buf, ">");
+ }
+
+ g_message ("[PATH] " G_STRLOC ": expression '%s' => '%s'", expression, buf->str);
+ g_string_free (buf, TRUE);
+ }
+#endif /* JSON_ENABLE_DEBUG */
+
+ 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;
+
+fail:
+ g_list_free_full (nodes, path_node_free);
+
+ return FALSE;
+}
+
+static void
+walk_path_node (GList *path,
+ JsonNode *root,
+ JsonArray *results)
+{
+ PathNode *node = path->data;
+
+ switch (node->node_type)
+ {
+ case JSON_PATH_NODE_ROOT:
+ walk_path_node (path->next, root, results);
+ 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)
+ {
+ JSON_NOTE (PATH, "end of path at member '%s'", node->data.member_name);
+ json_array_add_element (results, json_node_copy (member));
+ }
+ else
+ walk_path_node (path->next, member, results);
+ }
+ }
+ 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)
+ {
+ JSON_NOTE (PATH, "end of path at element '%d'", node->data.element_index);
+ json_array_add_element (results, json_node_copy (element));
+ }
+ else
+ walk_path_node (path->next, element, results);
+ }
+ }
+ break;
+
+ case JSON_PATH_NODE_RECURSIVE_DESCENT:
+ {
+ PathNode *tmp = path->next->data;
+
+ switch (json_node_get_node_type (root))
+ {
+ case JSON_NODE_OBJECT:
+ {
+ 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 *m = json_object_get_member (object, l->data);
+
+ if (tmp->node_type == JSON_PATH_NODE_CHILD_MEMBER &&
+ strcmp (tmp->data.member_name, l->data) == 0)
+ {
+ JSON_NOTE (PATH, "entering '%s'", tmp->data.member_name);
+ walk_path_node (path->next, root, results);
+ }
+ else
+ {
+ JSON_NOTE (PATH, "recursing into '%s'", (char *) l->data);
+ walk_path_node (path, m, results);
+ }
+ }
+ g_list_free (members);
+ }
+ break;
+
+ case JSON_NODE_ARRAY:
+ {
+ JsonArray *array = json_node_get_array (root);
+ GList *members, *l;
+ int i;
+
+ members = json_array_get_elements (array);
+ for (l = members, i = 0; l != NULL; l = l->next, i += 1)
+ {
+ JsonNode *m = l->data;
+
+ if (tmp->node_type == JSON_PATH_NODE_CHILD_ELEMENT &&
+ tmp->data.element_index == i)
+ {
+ JSON_NOTE (PATH, "entering '%d'", tmp->data.element_index);
+ walk_path_node (path->next, root, results);
+ }
+ else
+ {
+ JSON_NOTE (PATH, "recursing into '%d'", i);
+ walk_path_node (path, m, results);
+ }
+ }
+ g_list_free (members);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ 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);
+ else
+ {
+ JSON_NOTE (PATH, "glob match member '%s'", (char *) l->data);
+ 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;
+ int i;
+
+ elements = json_array_get_elements (array);
+ for (l = elements, i = 0; l != NULL; l = l->next, i += 1)
+ {
+ JsonNode *element = l->data;
+
+ if (path->next != NULL)
+ walk_path_node (path->next, element, results);
+ else
+ {
+ JSON_NOTE (PATH, "glob match element '%d'", i);
+ json_array_add_element (results, json_node_copy (root));
+ }
+ }
+ g_list_free (elements);
+ }
+ else
+ json_array_add_element (results, json_node_copy (root));
+ break;
+
+ case JSON_PATH_NODE_ELEMENT_SET:
+ if (JSON_NODE_HOLDS_ARRAY (root))
+ {
+ JsonArray *array = json_node_get_array (root);
+ int i;
+
+ for (i = 0; i < node->data.set.n_indices; i += 1)
+ {
+ int idx = node->data.set.indices[i];
+ JsonNode *element = json_array_get_element (array, idx);
+
+ if (path->next != NULL)
+ walk_path_node (path->next, element, results);
+ else
+ {
+ JSON_NOTE (PATH, "set element '%d'", idx);
+ json_array_add_element (results, json_node_copy (element));
+ }
+ }
+ }
+ break;
+
+ case JSON_PATH_NODE_ELEMENT_SLICE:
+ if (JSON_NODE_HOLDS_ARRAY (root))
+ {
+ JsonArray *array = json_node_get_array (root);
+ int i, start, end;
+
+ if (node->data.slice.start < 0)
+ {
+ start = json_array_get_length (array)
+ + node->data.slice.start;
+
+ end = json_array_get_length (array)
+ + node->data.slice.end;
+ }
+ else
+ {
+ start = node->data.slice.start;
+ end = node->data.slice.end;
+ }
+
+ for (i = start; i < end; i += node->data.slice.step)
+ {
+ JsonNode *element = json_array_get_element (array, i);
+
+ if (path->next != NULL)
+ walk_path_node (path->next, element, results);
+ else
+ {
+ JSON_NOTE (PATH, "slice element '%d'", i);
+ json_array_add_element (results, json_node_copy (element));
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * json_path_match:
+ * @path: a compiled #JsonPath
+ * @root: a #JsonNode
+ *
+ * Matches the JSON tree pointed by @root using the expression compiled
+ * into the #JsonPath.
+ *
+ * The matching #JsonNode<!-- -->s will be copied into a #JsonArray and
+ * returned wrapped in a #JsonNode.
+ *
+ * Return value: (transfer full): a newly-created #JsonNode of type
+ * %JSON_NODE_ARRAY containing an array of matching #JsonNode<!-- -->s.
+ * Use json_node_free() when done
+ *
+ * Since: 0.14
+ */
+JsonNode *
+json_path_match (JsonPath *path,
+ JsonNode *root)
+{
+ 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);
+
+ retval = json_node_new (JSON_NODE_ARRAY);
+ json_node_take_array (retval, results);
+
+ return retval;
+}
+
+/**
+ * json_path_query:
+ * @expression: a JSONPath expression
+ * @root: the root of a JSON tree
+ * @error: return location for a #GError, or %NULL
+ *
+ * Queries a JSON tree using a JSONPath expression.
+ *
+ * This function is a simple wrapper around json_path_new(),
+ * json_path_compile() and json_path_match(). It implicitly
+ * creates a #JsonPath instance, compiles @expression and
+ * matches it against the JSON tree pointed by @root.
+ *
+ * Return value: (transfer full): a newly-created #JsonNode of type
+ * %JSON_NODE_ARRAY containing an array of matching #JsonNode<!-- -->s.
+ * Use json_node_free() when done
+ *
+ * Since: 0.14
+ */
+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);
+
+ 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..2bae608
--- /dev/null
+++ b/json-glib/json-path.h
@@ -0,0 +1,97 @@
+/* json-path.h - JSONPath implementation
+ *
+ * This file is part of JSON-GLib
+ * Copyright © 2011 Intel Corp.
+ *
+ * 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.1 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ * Emmanuele Bassi <ebassi linux intel com>
+ */
+
+#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))
+
+/**
+ * JSON_PATH_ERROR:
+ *
+ * Error domain for #JsonPath errors
+ *
+ * Since: 0.14
+ */
+#define JSON_PATH_ERROR (json_path_error_quark ())
+
+/**
+ * JsonPathError:
+ * @JSON_PATH_ERROR_INVALID_QUERY: Invalid query
+ *
+ * Error code enumeration for the %JSON_PATH_ERROR domain.
+ *
+ * Since: 0.14
+ */
+typedef enum {
+ JSON_PATH_ERROR_INVALID_QUERY
+} JsonPathError;
+
+/**
+ * JsonPath:
+ *
+ * The <structname>JsonPath</structname> structure is an opaque object
+ * whose members cannot be directly accessed except through the provided
+ * API.
+ *
+ * Since: 0.14
+ */
+typedef struct _JsonPath JsonPath;
+
+/**
+ * JsonPathClass:
+ *
+ * The <structname>JsonPathClass</structname> structure is an opaque
+ * object class whose members cannot be directly accessed.
+ *
+ * Since: 0.14
+ */
+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);
+
+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..3a5e41c
--- /dev/null
+++ b/json-glib/tests/path-test.c
@@ -0,0 +1,143 @@
+#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,
+ "[\"8.95\",\"12.99\",\"8.99\",\"22.99\",\"19.95\"]",
+ "[{\"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\"}]",
+ "[{\"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\":\"reference\",\"author\":\"Nigel Rees\",\"title\":\"Sayings of the Century\",\"price\":\"8.95\"},{\"category\":\"fiction\",\"author\":\"Evelyn Waugh\",\"title\":\"Sword of Honour\",\"price\":\"12.99\"}]",
+};
+
+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];
+ GError *error = NULL;
+
+ g_assert (json_path_compile (path, expr, &error));
+ g_assert_no_error (error);
+ }
+
+ 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);
+ 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]