diff options
Diffstat (limited to 'jsonpath.c')
| -rw-r--r-- | jsonpath.c | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/jsonpath.c b/jsonpath.c new file mode 100644 index 0000000..8de8327 --- /dev/null +++ b/jsonpath.c @@ -0,0 +1,246 @@ +#include "jsonpath.h" + +#include <ctype.h> + +/* NB: These macros evaluate their argument multiple times. */ + +#define isletter(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z')) + /* isalpha() is locale-specific. This simply matches [A-Za-z] . */ +#define isextended(c) ((unsigned char)(c) > 127) + +/* Note that Unicode characters are allowed in identifiers. */ +#define identifier_start(c) (isletter(c) || (c) == '_' || (c) == '$' || isextended(c)) +#define identifier_char(c) (identifier_start(c) || isdigit(c)) + +#define integer_start(c) (isdigit(c) || (c) == '+' || (c) == '-') + +/* + * 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 + * whitespace, but since this is JSONPath, + * we can do whatever we want here :-) + */ +static void skip_spaces(const char **sp) +{ + const char *s = *sp; + while (isspace(*s)) + s++; + *sp = s; +} + +static jp_element *mkElement(jp_element_type type, bool rd) +{ + jp_element *elem = palloc0(sizeof(*elem)); + elem->type = type; + elem->recursive_descent = rd; + return elem; +} + +static jp_element *mkRoot(void) +{ + jp_element *elem = mkElement(JP_ROOT, false); + return elem; +} + +static jp_element *mkWildcard(bool rd) +{ + jp_element *elem = mkElement(JP_WILDCARD, rd); + return elem; +} + +static jp_element *mkIndexSubscript(int index, bool rd) +{ + jp_element *elem = mkElement(JP_INDEX_SUBSCRIPT, rd); + elem->data.index = index; + return elem; +} + +static jp_element *mkKeySubscript(char *key, size_t length, bool rd) +{ + jp_element *elem = mkElement(JP_KEY_SUBSCRIPT, rd); + elem->data.key.ptr = key; + elem->data.key.length = length; + return elem; +} + +char *jp_show(JSONPath *jp) +{ + StringInfoData string[1]; + ListCell *cell; + jp_element *elem; + bool rd; + char *tmp; + + initStringInfo(string); + + foreach(cell, jp) { + elem = lfirst(cell); + rd = elem->recursive_descent; + + switch (elem->type) { + case JP_ROOT: + appendStringInfoChar(string, '$'); + break; + case JP_WILDCARD: + appendStringInfoString(string, rd ? "..[*]" : "[*]"); + break; + case JP_INDEX_SUBSCRIPT: + appendStringInfo(string, "%s[%ld]", rd ? ".." : "", elem->data.index); + break; + case JP_KEY_SUBSCRIPT: + tmp = json_encode_string(elem->data.key.ptr, elem->data.key.length, '"'); + Assert(tmp != NULL); + appendStringInfo(string, "%s[%s]", rd ? ".." : "", tmp); + pfree(tmp); + break; + default: + Assert(false); + } + } + + return string->data; +} + +JSONPath *jp_parse(const char *pattern) +{ + JSONPath *jp = NIL; + const char *s = pattern; + const char *p; + bool recursive_descent = false; + bool bracket = false; + const char *err_msg = NULL; + long index; + char *key; + size_t key_length; + + skip_spaces(&s); + + /* pattern may not be empty */ + if (!*s) + return NULL; + + jp = lappend(jp, mkRoot()); + + if (*s == '$') { + s++; + goto begin_element; + } else if (*s != '.') { + goto dot_subscript; // implicit '.' at beginning + } + +begin_element: + skip_spaces(&s); + + recursive_descent = false; + bracket = false; + + if (*s == '\0') + goto end; + if (s[0] == '.' && s[1] == '.') { + recursive_descent = true; + s += 2; + goto dot_subscript; + } + if (s[0] == '.') { + s++; + goto dot_subscript; + } + if (s[0] == '[') { + s++; + goto bracket_subscript; + } + + goto failed; + +next_element: + if (bracket) { + skip_spaces(&s); + if (*s != ']') + goto failed; + s++; + } + goto begin_element; + +dot_subscript: + skip_spaces(&s); + + if (*s == '*') + goto wildcard; + if (integer_start(*s)) + goto integer; + if (identifier_start(*s)) + goto identifier; + if (*s == '"' || *s == '\'') + goto string; + if (*s == '[') { + s++; + goto bracket_subscript; + } + + goto failed; + +bracket_subscript: + skip_spaces(&s); + + bracket = true; + + if (*s == '*') + goto wildcard; + if (integer_start(*s)) + goto integer; + if (identifier_start(*s)) { + err_msg = "Identifiers may not be bracketed. This syntax is reserved for future use."; + goto failed; + } + if (*s == '"' || *s == '\'') + goto string; + + goto failed; + +wildcard: + s++; + jp = lappend(jp, mkWildcard(recursive_descent)); + goto next_element; + +integer: + p = s; + errno = 0; + index = strtol(s, (char**)&p, 10); + if (p <= s || errno != 0) + goto failed; + s = p; + + jp = lappend(jp, mkIndexSubscript(index, recursive_descent)); + goto next_element; + +identifier: + p = s; + while (identifier_char(*p)) + p++; + key = pnstrdup(s, p - s); + key_length = p - s; + s = p; + + jp = lappend(jp, mkKeySubscript(key, key_length, recursive_descent)); + goto next_element; + +string: + key = json_decode_string(&s, &key_length, false); + if (!key) + goto failed; + + jp = lappend(jp, mkKeySubscript(key, key_length, recursive_descent)); + goto next_element; + +end: + return jp; + +failed: + return NULL; +} + +List *jp_match(JSONPath *jp, json_node *json) +{ + +} |
