summaryrefslogtreecommitdiff
path: root/jsonpath.c
diff options
context:
space:
mode:
Diffstat (limited to 'jsonpath.c')
-rw-r--r--jsonpath.c246
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)
+{
+
+}