--- /dev/null
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "json-tree.h"
+
+struct json_tree {
+ pool_t pool;
+ struct json_tree_node *root, *cur, *cur_child;
+};
+
+struct json_tree *json_tree_init(void)
+{
+ struct json_tree *tree;
+ pool_t pool;
+
+ pool = pool_alloconly_create("json tree", 1024);
+ tree = p_new(pool, struct json_tree, 1);
+ tree->pool = pool;
+ tree->root = tree->cur = p_new(tree->pool, struct json_tree_node, 1);
+ tree->cur->value_type = JSON_TYPE_OBJECT;
+ return tree;
+}
+
+void json_tree_deinit(struct json_tree **_tree)
+{
+ struct json_tree *tree = *_tree;
+
+ *_tree = NULL;
+ pool_unref(&tree->pool);
+}
+
+static void
+json_tree_append_child(struct json_tree *tree, enum json_type type,
+ const char *value)
+{
+ struct json_tree_node *node;
+
+ node = p_new(tree->pool, struct json_tree_node, 1);
+ node->parent = tree->cur;
+ node->value_type = type;
+ node->value.str = p_strdup(tree->pool, value);
+
+ if (tree->cur_child == NULL)
+ tree->cur->value.child = node;
+ else
+ tree->cur_child->next = node;
+ tree->cur_child = node;
+}
+
+static void
+json_tree_set_cur(struct json_tree *tree, struct json_tree_node *node)
+{
+ tree->cur = node;
+ tree->cur_child = tree->cur->value.child;
+ if (tree->cur_child != NULL) {
+ while (tree->cur_child->next != NULL)
+ tree->cur_child = tree->cur_child->next;
+ }
+}
+
+static int
+json_tree_append_value(struct json_tree *tree, enum json_type type,
+ const char *value)
+{
+ switch (tree->cur->value_type) {
+ case JSON_TYPE_OBJECT_KEY:
+ /* "key": value - we already added the node and set its key,
+ so now just set the value */
+ tree->cur->value_type = type;
+ tree->cur->value.str = p_strdup(tree->pool, value);
+ json_tree_set_cur(tree, tree->cur->parent);
+ break;
+ case JSON_TYPE_ARRAY:
+ /* element in array - add a new node */
+ json_tree_append_child(tree, type, value);
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+int json_tree_append(struct json_tree *tree, enum json_type type,
+ const char *value)
+{
+ switch (type) {
+ case JSON_TYPE_OBJECT_KEY:
+ if (tree->cur->value_type != JSON_TYPE_OBJECT)
+ return -1;
+ json_tree_append_child(tree, type, NULL);
+ json_tree_set_cur(tree, tree->cur_child);
+ tree->cur->key = p_strdup(tree->pool, value);
+ break;
+ case JSON_TYPE_ARRAY:
+ if (json_tree_append_value(tree, type, NULL) < 0)
+ return -1;
+ json_tree_set_cur(tree, tree->cur_child);
+ break;
+ case JSON_TYPE_OBJECT:
+ if (tree->cur->value_type == JSON_TYPE_OBJECT_KEY)
+ tree->cur->value_type = JSON_TYPE_OBJECT;
+ else if (tree->cur->value_type == JSON_TYPE_ARRAY) {
+ json_tree_append_child(tree, type, NULL);
+ json_tree_set_cur(tree, tree->cur_child);
+ } else {
+ return -1;
+ }
+ break;
+ case JSON_TYPE_OBJECT_END:
+ if (tree->cur->parent == NULL ||
+ tree->cur->value_type != JSON_TYPE_OBJECT)
+ return -1;
+ json_tree_set_cur(tree, tree->cur->parent);
+ break;
+ case JSON_TYPE_ARRAY_END:
+ if (tree->cur->parent == NULL ||
+ tree->cur->value_type != JSON_TYPE_ARRAY)
+ return -1;
+ json_tree_set_cur(tree, tree->cur->parent);
+ break;
+ case JSON_TYPE_STRING:
+ case JSON_TYPE_NUMBER:
+ case JSON_TYPE_TRUE:
+ case JSON_TYPE_FALSE:
+ case JSON_TYPE_NULL:
+ if (json_tree_append_value(tree, type, value) < 0)
+ return -1;
+ break;
+ }
+ return 0;
+}
+
+struct json_tree_node *json_tree_root(struct json_tree *tree)
+{
+ return tree->root;
+}
+
+struct json_tree_node *
+json_tree_find_key(struct json_tree_node *node, const char *key)
+{
+ for (; node != NULL; node = node->next) {
+ if (node->key != NULL && strcmp(node->key, key) == 0)
+ return node;
+ }
+ return NULL;
+}
+
+struct json_tree_node *
+json_tree_find_child_with(struct json_tree_node *node,
+ const char *key, const char *value)
+{
+ struct json_tree_node *child;
+
+ i_assert(node->value_type == JSON_TYPE_OBJECT ||
+ node->value_type == JSON_TYPE_ARRAY);
+
+ for (node = node->value.child; node != NULL; node = node->next) {
+ if (node->value_type != JSON_TYPE_OBJECT)
+ continue;
+
+ child = json_tree_find_key(node->value.child, key);
+ if (child != NULL && child->value.str != NULL &&
+ strcmp(child->value.str, value) == 0)
+ return node;
+ }
+ return NULL;
+}
--- /dev/null
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "json-tree.h"
+
+struct {
+ enum json_type type;
+ const char *value;
+} test_input[] = {
+ { JSON_TYPE_OBJECT_KEY, "key-str" },
+ { JSON_TYPE_STRING, "string" },
+ { JSON_TYPE_OBJECT_KEY, "key-num" },
+ { JSON_TYPE_NUMBER, "1234" },
+ { JSON_TYPE_OBJECT_KEY, "key-true" },
+ { JSON_TYPE_TRUE, "true" },
+ { JSON_TYPE_OBJECT_KEY, "key-false" },
+ { JSON_TYPE_FALSE, "false" },
+ { JSON_TYPE_OBJECT_KEY, "key-null" },
+ { JSON_TYPE_NULL, NULL },
+
+ { JSON_TYPE_OBJECT_KEY, "key-obj-empty" },
+ { JSON_TYPE_OBJECT, NULL },
+ { JSON_TYPE_OBJECT_END, NULL },
+
+ { JSON_TYPE_OBJECT_KEY, "key-obj" },
+ { JSON_TYPE_OBJECT, NULL },
+ { JSON_TYPE_OBJECT_KEY, "sub" },
+ { JSON_TYPE_STRING, "value" },
+ { JSON_TYPE_OBJECT_END, NULL },
+
+ { JSON_TYPE_OBJECT_KEY, "key-arr-empty" },
+ { JSON_TYPE_ARRAY, NULL },
+ { JSON_TYPE_ARRAY_END, NULL },
+
+ { JSON_TYPE_OBJECT_KEY, "key-arr" },
+ { JSON_TYPE_ARRAY, NULL },
+ { JSON_TYPE_STRING, "foo" },
+ { JSON_TYPE_ARRAY, NULL },
+ { JSON_TYPE_TRUE, "true" },
+ { JSON_TYPE_ARRAY_END, NULL },
+ { JSON_TYPE_OBJECT, NULL },
+ { JSON_TYPE_OBJECT_KEY, "aobj" },
+ { JSON_TYPE_ARRAY, NULL },
+ { JSON_TYPE_ARRAY_END, NULL },
+ { JSON_TYPE_OBJECT_END, NULL },
+ { JSON_TYPE_OBJECT, NULL },
+ { JSON_TYPE_OBJECT_KEY, "aobj-key" },
+ { JSON_TYPE_STRING, "value1" },
+ { JSON_TYPE_OBJECT_END, NULL },
+ { JSON_TYPE_OBJECT, NULL },
+ { JSON_TYPE_OBJECT_KEY, "aobj-key" },
+ { JSON_TYPE_STRING, "value2" },
+ { JSON_TYPE_OBJECT_END, NULL },
+ { JSON_TYPE_ARRAY_END, NULL }
+};
+
+void test_json_tree(void)
+{
+ struct json_tree *tree;
+ struct json_tree_node *root, *node, *node1, *node2;
+ unsigned int i;
+
+ test_begin("json tree");
+ tree = json_tree_init();
+ for (i = 0; i < N_ELEMENTS(test_input); i++) {
+ test_assert_idx(json_tree_append(tree, test_input[i].type,
+ test_input[i].value) == 0, i);
+ }
+
+ root = json_tree_root(tree);
+ i_assert(root != NULL);
+ test_assert(root->value_type == JSON_TYPE_OBJECT);
+ root = root->value.child;
+ i_assert(root != NULL);
+
+ for (i = 0; i < 10+2; i += 2) {
+ node = json_tree_find_key(root, test_input[i].value);
+ test_assert(node != NULL &&
+ node->value_type == test_input[i+1].type &&
+ null_strcmp(node->value.str, test_input[i+1].value) == 0);
+ }
+
+ node = json_tree_find_key(root, "key-obj");
+ test_assert(node != NULL);
+
+ node = json_tree_find_key(root, "key-arr-empty");
+ test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY &&
+ node->value.child == NULL);
+
+ node = json_tree_find_key(root, "key-arr");
+ test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY);
+ node = node->value.child;
+ test_assert(node != NULL && node->value_type == JSON_TYPE_STRING &&
+ strcmp(node->value.str, "foo") == 0);
+ node = node->next;
+ test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY &&
+ node->value.child != NULL &&
+ node->value.child->next == NULL &&
+ node->value.child->value_type == JSON_TYPE_TRUE);
+ node = node->next;
+ test_assert(node != NULL && node->value_type == JSON_TYPE_OBJECT &&
+ node->value.child != NULL &&
+ node->value.child->next == NULL &&
+ node->value.child->value_type == JSON_TYPE_ARRAY &&
+ node->value.child->value.child == NULL);
+
+ node1 = json_tree_find_child_with(node->parent, "aobj-key", "value1");
+ node2 = json_tree_find_child_with(node->parent, "aobj-key", "value2");
+ test_assert(node1 != NULL && node2 != NULL && node1 != node2);
+ test_assert(json_tree_find_child_with(node->parent, "aobj-key", "value3") == NULL);
+
+ json_tree_deinit(&tree);
+ test_end();
+}