diff options
| author | Joey Adams | 2010-06-15 02:49:13 +0000 |
|---|---|---|
| committer | Joey Adams | 2010-06-15 02:49:13 +0000 |
| commit | 599940cb2fcde6e26b095b8accf7c3a70b2ba05b (patch) | |
| tree | a16053467276d9c009904718bddb910519159e88 /jsonpath.c | |
| parent | 58b696fc6c16123fb8206c386d6d398e47a6c21f (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.c | 173 |
1 files changed, 172 insertions, 1 deletions
@@ -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; } |
