summaryrefslogtreecommitdiff
path: root/jsonpath.c
diff options
context:
space:
mode:
authorJoey Adams2010-06-15 02:49:13 +0000
committerJoey Adams2010-06-15 02:49:13 +0000
commit599940cb2fcde6e26b095b8accf7c3a70b2ba05b (patch)
treea16053467276d9c009904718bddb910519159e88 /jsonpath.c
parent58b696fc6c16123fb8206c386d6d398e47a6c21f (diff)
Initial implementation of json_path function.
Currently, it re-encodes JSON nodes rather than preserving original text. This is subject to change.
Diffstat (limited to 'jsonpath.c')
-rw-r--r--jsonpath.c173
1 files changed, 172 insertions, 1 deletions
diff --git a/jsonpath.c b/jsonpath.c
index 8de8327..d816beb 100644
--- a/jsonpath.c
+++ b/jsonpath.c
@@ -1,6 +1,7 @@
#include "jsonpath.h"
#include <ctype.h>
+#include "mb/pg_wchar.h"
/* NB: These macros evaluate their argument multiple times. */
@@ -15,6 +16,29 @@
#define integer_start(c) (isdigit(c) || (c) == '+' || (c) == '-')
/*
+ * In a valid JSONPath list, the first element is always of type JP_ROOT.
+ * This element is used so an otherwise empty JSONPath list won't be NULL.
+ * This allows us to use NULL to indicate an invalid JSONPath.
+ *
+ * This function returns the second cell,
+ * making sure the first is of type JP_ROOT.
+ */
+static ListCell *jp_head(JSONPath *jp)
+{
+ ListCell *cell;
+ jp_element *elem;
+
+ Assert(jp != NULL);
+
+ cell = list_head(jp);
+ elem = lfirst(cell);
+ Assert(elem->type == JP_ROOT);
+
+ cell = lnext(cell);
+ return cell;
+}
+
+/*
* Note that skip_spaces differs from skip_whitespace in json.c
* in that this function treats '\f' and '\v' as whitespace.
* This is because JSON does not accept these characters as
@@ -240,7 +264,154 @@ failed:
return NULL;
}
-List *jp_match(JSONPath *jp, json_node *json)
+static size_t utf8_substring(
+ const char *src, size_t srcbytes,
+ size_t start, size_t length,
+ const char **out_start, size_t *out_bytes)
+{
+ const char *e = src + srcbytes;
+ const char *sub_start;
+ const char *sub_end;
+ size_t sub_length;
+
+ sub_start = src;
+ while (start > 0 && sub_start < e) {
+ sub_start += pg_utf_mblen((const unsigned char*)sub_start);
+ start--;
+ }
+
+ sub_end = sub_start;
+ sub_length = 0;
+ while (sub_length < length && sub_end < e) {
+ sub_end += pg_utf_mblen((const unsigned char*)sub_end);
+ sub_length++;
+ }
+
+ /* Make sure the input didn't have a clipped UTF-8 character */
+ if(sub_start > e) {
+ Assert(false);
+ sub_start = sub_end = e;
+ } else if (sub_end > e) {
+ Assert(false);
+ sub_end = e;
+ }
+
+ *out_start = sub_start;
+ *out_bytes = sub_end - sub_start;
+ return sub_length;
+}
+
+static json_node *json_head(json_node *parent)
{
+ switch (parent->type) {
+ case JSON_ARRAY:
+ case JSON_OBJECT:
+ return parent->v.children.head;
+ default:
+ return NULL;
+ }
+}
+
+#define json_foreach(child, parent) \
+ for ((child) = json_head(parent); (child) != NULL; (child) = (child)->next)
+
+static json_node *json_index_subscript(json_node *json, long index)
+{
+ if (index < 0)
+ return NULL;
+ switch (json->type) {
+ case JSON_STRING: {
+ const char *sub_start;
+ size_t sub_bytes;
+ size_t sub_length;
+ json_node *sub_json;
+
+ sub_length = utf8_substring(
+ json->v.string.str, json->v.string.length,
+ index, 1,
+ &sub_start, &sub_bytes);
+
+ if (sub_length != 1)
+ return NULL;
+
+ sub_json = json_mkstring(sub_start, sub_bytes);
+ sub_json->parent = json;
+ return sub_json;
+ }
+ case JSON_ARRAY: {
+ json_node *child;
+
+ if ((size_t)index >= json->v.children.count)
+ return NULL;
+
+ for (child = json->v.children.head;
+ index && child;
+ child = child->next, index--) {}
+
+ if (index != 0 || child == NULL) {
+ Assert(false);
+ return NULL;
+ }
+
+ return child;
+ }
+ default:
+ return NULL;
+ }
+}
+
+static void match_recurse(List **results, ListCell *path, json_node *json)
+{
+ jp_element *elem;
+ json_node *child;
+
+ if (path == NULL) {
+ /* The end of the JSONPath list is the "accept" state. */
+ *results = lappend(*results, json);
+ return;
+ }
+
+ elem = lfirst(path);
+
+ switch (elem->type) {
+ case JP_WILDCARD:
+ json_foreach(child, json)
+ match_recurse(results, lnext(path), child);
+ break;
+
+ case JP_INDEX_SUBSCRIPT:
+ child = json_index_subscript(json, elem->data.index);
+ if (child != NULL)
+ match_recurse(results, lnext(path), child);
+ break;
+
+ case JP_KEY_SUBSCRIPT:
+ json_foreach(child, json) {
+ if (child->key != NULL &&
+ child->key_length == elem->data.key.length &&
+ !memcmp(child->key, elem->data.key.ptr, child->key_length))
+ {
+ match_recurse(results, lnext(path), child);
+ }
+ }
+ break;
+
+ default:;
+ }
+
+ if (elem->recursive_descent) {
+ json_foreach(child, json)
+ match_recurse(results, path, child);
+ }
+}
+
+List *jp_match(JSONPath *jp, json_node *json)
+{
+ ListCell *lc = jp_head(jp);
+ List *results = NIL;
+
+ match_recurse(&results, lc, json);
+
+ return results;
}