static void test_merge_array(void) { JsonElement *a = JsonArrayCreate(2); JsonArrayAppendString(a, "a"); JsonArrayAppendString(a, "b"); JsonElement *b = JsonArrayCreate(2); JsonArrayAppendString(b, "c"); JsonArrayAppendString(b, "d"); JsonElement *c = JsonMerge(a, b); assert_int_equal(2, JsonLength(a)); assert_int_equal(2, JsonLength(b)); assert_int_equal(4, JsonLength(c)); assert_string_equal("a", JsonArrayGetAsString(c, 0)); assert_string_equal("b", JsonArrayGetAsString(c, 1)); assert_string_equal("c", JsonArrayGetAsString(c, 2)); assert_string_equal("d", JsonArrayGetAsString(c, 3)); JsonDestroy(a); JsonDestroy(b); JsonDestroy(c); }
static void test_detach_key_from_object(void) { JsonElement *object = JsonObjectCreate(3); JsonObjectAppendInteger(object, "one", 1); JsonObjectAppendInteger(object, "two", 2); JsonObjectAppendInteger(object, "three", 3); JsonElement *detached = JsonObjectDetachKey(object, "two"); assert_int_equal(2, JsonLength(object)); JsonDestroy(object); assert_int_equal(1, JsonLength(detached)); JsonDestroy(detached); }
static void test_object_duplicate_key(void) { JsonElement *a = JsonObjectCreate(1); JsonObjectAppendString(a, "a", "a"); JsonObjectAppendString(a, "a", "a"); assert_int_equal(1, JsonLength(a)); JsonDestroy(a); }
static void test_remove_key_from_object(void) { JsonElement *object = JsonObjectCreate(3); JsonObjectAppendInteger(object, "one", 1); JsonObjectAppendInteger(object, "two", 2); JsonObjectAppendInteger(object, "three", 3); JsonObjectRemoveKey(object, "two"); assert_int_equal(2, JsonLength(object)); JsonDestroy(object); }
static void test_parse_empty_containers(void) { { const char *data = "{}"; JsonElement *obj = NULL; assert_int_equal(JSON_PARSE_OK, JsonParse(&data, &obj)); assert_true(obj != NULL); assert_int_equal(JSON_ELEMENT_TYPE_CONTAINER, JsonGetElementType(obj)); assert_int_equal(JSON_CONTAINER_TYPE_OBJECT, JsonGetContainerType(obj)); assert_int_equal(0, JsonLength(obj)); JsonDestroy(obj); } { const char *data = "[]"; JsonElement *arr = NULL; assert_int_equal(JSON_PARSE_OK, JsonParse(&data, &arr)); assert_true(arr != NULL); assert_int_equal(JSON_ELEMENT_TYPE_CONTAINER, JsonGetElementType(arr)); assert_int_equal(JSON_CONTAINER_TYPE_ARRAY, JsonGetContainerType(arr)); assert_int_equal(0, JsonLength(arr)); JsonDestroy(arr); } }
static void test_merge_object(void) { JsonElement *a = JsonObjectCreate(2); JsonObjectAppendString(a, "a", "a"); JsonObjectAppendString(a, "b", "b"); JsonElement *b = JsonObjectCreate(2); JsonObjectAppendString(b, "b", "b"); JsonObjectAppendString(b, "c", "c"); JsonElement *c = JsonMerge(a, b); assert_int_equal(2, JsonLength(a)); assert_int_equal(2, JsonLength(b)); assert_int_equal(3, JsonLength(c)); assert_string_equal("a", JsonObjectGetAsString(c, "a")); assert_string_equal("b", JsonObjectGetAsString(c, "b")); assert_string_equal("c", JsonObjectGetAsString(c, "c")); JsonDestroy(a); JsonDestroy(b); JsonDestroy(c); }
Rval RvalNewRewriter(const void *item, RvalType type, JsonElement *map) { switch (type) { case RVAL_TYPE_SCALAR: if (NULL != map && JsonLength(map) > 0 && // do we have a rewrite map? (strstr(item, "$(") || strstr(item, "${"))) // are there unresolved variable references? { // TODO: replace with BufferSearchAndReplace when the // string_replace code is merged. // Sorry about the CF_BUFSIZE ugliness. int max_size = 10*CF_BUFSIZE+1; char *buffer_from = xmalloc(max_size); char *buffer_to = xmalloc(max_size); Buffer *format = BufferNew(); strncpy(buffer_from, item, max_size); for (int iteration = 0; iteration < 10; iteration++) { bool replacement_made = false; int var_start = -1; char closing_brace = 0; for (int c = 0; c < buffer_from[c]; c++) { printf("In %s at %i: '%s'\n", __func__, __LINE__, buffer_from); if (buffer_from[c] == '$') { if (buffer_from[c+1] == '(') { closing_brace = ')'; } else if (buffer_from[c+1] == '{') { closing_brace = '}'; } if (closing_brace) { c++; var_start = c-1; } } else if (var_start >= 0 && buffer_from[c] == closing_brace) { char saved = buffer_from[c]; buffer_from[c] = '\0'; const char *repl = JsonObjectGetAsString(map, buffer_from + var_start + 2); buffer_from[c] = saved; if (repl) { // Before the replacement. memcpy(buffer_to, buffer_from, var_start); // The actual replacement. int repl_len = strlen(repl); memcpy(buffer_to + var_start, repl, repl_len); // The text after. strlcpy(buffer_to + var_start + repl_len, buffer_from + c + 1, max_size - var_start - repl_len); // Reset location to immediately after the replacement. c = var_start + repl_len - 1; var_start = -1; strcpy(buffer_from, buffer_to); closing_brace = 0; replacement_made = true; } } } if (!replacement_made) { break; } } char *ret = xstrdup(buffer_to); BufferDestroy(format); free(buffer_to); free(buffer_from); return (Rval) { ret, RVAL_TYPE_SCALAR }; } else { return (Rval) { xstrdup(item), RVAL_TYPE_SCALAR }; } case RVAL_TYPE_FNCALL: return (Rval) { FnCallCopyRewriter(item, map), RVAL_TYPE_FNCALL }; case RVAL_TYPE_LIST: return (Rval) { RlistCopyRewriter(item, map), RVAL_TYPE_LIST }; case RVAL_TYPE_CONTAINER: return (Rval) { JsonCopy(item), RVAL_TYPE_CONTAINER }; case RVAL_TYPE_NOPROMISEE: return ((Rval) {NULL, type}); } assert(false); return ((Rval) { NULL, RVAL_TYPE_NOPROMISEE }); }
static void test_array_remove_range(void) { { // remove whole JsonElement *arr = JsonArrayCreate(5); JsonArrayAppendString(arr, "one"); JsonArrayAppendString(arr, "two"); JsonArrayAppendString(arr, "three"); JsonArrayRemoveRange(arr, 0, 2); assert_int_equal(JsonLength(arr), 0); JsonDestroy(arr); } { // remove middle JsonElement *arr = JsonArrayCreate(5); JsonArrayAppendString(arr, "one"); JsonArrayAppendString(arr, "two"); JsonArrayAppendString(arr, "three"); JsonArrayRemoveRange(arr, 1, 1); assert_int_equal(JsonLength(arr), 2); assert_string_equal(JsonArrayGetAsString(arr, 0), "one"); assert_string_equal(JsonArrayGetAsString(arr, 1), "three"); JsonDestroy(arr); } { // remove rest JsonElement *arr = JsonArrayCreate(5); JsonArrayAppendString(arr, "one"); JsonArrayAppendString(arr, "two"); JsonArrayAppendString(arr, "three"); JsonArrayRemoveRange(arr, 1, 2); assert_int_equal(JsonLength(arr), 1); assert_string_equal(JsonArrayGetAsString(arr, 0), "one"); JsonDestroy(arr); } { // remove but last JsonElement *arr = JsonArrayCreate(5); JsonArrayAppendString(arr, "one"); JsonArrayAppendString(arr, "two"); JsonArrayAppendString(arr, "three"); JsonArrayRemoveRange(arr, 0, 1); assert_int_equal(JsonLength(arr), 1); assert_string_equal(JsonArrayGetAsString(arr, 0), "three"); JsonDestroy(arr); } }
static bool Render(Buffer *out, const char *start, const char *input, Seq *hash_stack, char *delim_start, size_t *delim_start_len, char *delim_end, size_t *delim_end_len, bool skip_content, const char *section, const char **section_end) { while (true) { if (!input) { Log(LOG_LEVEL_ERR, "Unexpected end to Mustache template"); return false; } Mustache tag = NextTag(input, delim_start, *delim_start_len, delim_end, *delim_end_len); { const char *line_begin = NULL; const char *line_end = NULL; if (!IsTagTypeRenderable(tag.type) && IsTagStandalone(start, tag.begin, tag.end, &line_begin, &line_end)) { RenderContent(out, input, line_begin - input, false, skip_content); input = line_end; } else { RenderContent(out, input, tag.begin - input, false, skip_content); input = tag.end; } } switch (tag.type) { case TAG_TYPE_ERR: return false; case TAG_TYPE_DELIM: if (!SetDelimiters(tag.content, tag.content_len, delim_start, delim_start_len, delim_end, delim_end_len)) { return false; } continue; case TAG_TYPE_COMMENT: continue; case TAG_TYPE_NONE: return true; case TAG_TYPE_VAR_SERIALIZED: case TAG_TYPE_VAR_SERIALIZED_COMPACT: case TAG_TYPE_VAR_UNESCAPED: case TAG_TYPE_VAR: if (!skip_content) { if (tag.content_len > 0) { if (!RenderVariable(out, tag.content, tag.content_len, tag.type, hash_stack)) { return false; } } else { RenderContent(out, delim_start, *delim_start_len, false, false); RenderContent(out, delim_end, *delim_end_len, false, false); } } continue; case TAG_TYPE_INVERTED: case TAG_TYPE_SECTION: { char *section = xstrndup(tag.content, tag.content_len); JsonElement *var = LookupVariable(hash_stack, tag.content, tag.content_len); SeqAppend(hash_stack, var); if (!var) { const char *cur_section_end = NULL; if (!Render(out, start, input, hash_stack, delim_start, delim_start_len, delim_end, delim_end_len, skip_content || tag.type != TAG_TYPE_INVERTED, section, &cur_section_end)) { free(section); return false; } free(section); input = cur_section_end; continue; } switch (JsonGetElementType(var)) { case JSON_ELEMENT_TYPE_PRIMITIVE: switch (JsonGetPrimitiveType(var)) { case JSON_PRIMITIVE_TYPE_BOOL: { bool skip = skip_content || (!JsonPrimitiveGetAsBool(var) ^ (tag.type == TAG_TYPE_INVERTED)); const char *cur_section_end = NULL; if (!Render(out, start, input, hash_stack, delim_start, delim_start_len, delim_end, delim_end_len, skip, section, &cur_section_end)) { free(section); return false; } free(section); input = cur_section_end; } continue; default: Log(LOG_LEVEL_WARNING, "Mustache sections can only take a boolean or a container (array or map) value, but section '%s' isn't getting one of those.", section); return false; } break; case JSON_ELEMENT_TYPE_CONTAINER: switch (JsonGetContainerType(var)) { case JSON_CONTAINER_TYPE_OBJECT: case JSON_CONTAINER_TYPE_ARRAY: if (JsonLength(var) > 0) { const char *cur_section_end = NULL; for (size_t i = 0; i < JsonLength(var); i++) { JsonElement *child_hash = JsonAt(var, i); SeqAppend(hash_stack, child_hash); if (!Render(out, start, input, hash_stack, delim_start, delim_start_len, delim_end, delim_end_len, skip_content || tag.type == TAG_TYPE_INVERTED, section, &cur_section_end)) { free(section); return false; } } input = cur_section_end; free(section); } else { const char *cur_section_end = NULL; if (!Render(out, start, input, hash_stack, delim_start, delim_start_len, delim_end, delim_end_len, tag.type != TAG_TYPE_INVERTED, section, &cur_section_end)) { free(section); return false; } free(section); input = cur_section_end; } break; } break; } } continue; case TAG_TYPE_SECTION_END: if (!section) { char *varname = xstrndup(tag.content, tag.content_len); Log(LOG_LEVEL_WARNING, "Unknown section close in mustache template '%s'", varname); free(varname); return false; } else { SeqRemove(hash_stack, SeqLength(hash_stack) - 1); *section_end = input; return true; } break; default: assert(false); return false; } } assert(false); }
/* Inserts an Rlist node with value #rval, right after the rlist node #node. */ void RlistInsertAfter(Rlist *node, Rval rval) { assert(node != NULL); Rlist new_node = { .val = rval, .next = node->next }; node->next = xmemdup(&new_node, sizeof(new_node)); } Rval RvalNewRewriter(const void *item, RvalType type, JsonElement *map) { switch (type) { case RVAL_TYPE_SCALAR: if (map != NULL && JsonLength(map) > 0 && // do we have a rewrite map? (strstr(item, "$(") || strstr(item, "${"))) // are there unresolved variable references? { // TODO: replace with BufferSearchAndReplace when the // string_replace code is merged. // Sorry about the CF_BUFSIZE ugliness. int max_size = 10*CF_BUFSIZE+1; char *buffer_from = xmalloc(max_size); char *buffer_to = xmalloc(max_size); Buffer *format = BufferNew(); StringCopy(item, buffer_from, max_size); for (int iteration = 0; iteration < 10; iteration++) { bool replacement_made = false; int var_start = -1; char closing_brace = 0; for (int c = 0; c < buffer_from[c]; c++) { if (buffer_from[c] == '$') { if (buffer_from[c+1] == '(') { closing_brace = ')'; } else if (buffer_from[c+1] == '{') { closing_brace = '}'; } if (closing_brace) { c++; var_start = c-1; } } else if (var_start >= 0 && buffer_from[c] == closing_brace) { char saved = buffer_from[c]; buffer_from[c] = '\0'; const char *repl = JsonObjectGetAsString(map, buffer_from + var_start + 2); buffer_from[c] = saved; if (repl) { // Before the replacement. memcpy(buffer_to, buffer_from, var_start); // The actual replacement. int repl_len = strlen(repl); memcpy(buffer_to + var_start, repl, repl_len); // The text after. strlcpy(buffer_to + var_start + repl_len, buffer_from + c + 1, max_size - var_start - repl_len); // Reset location to immediately after the replacement. c = var_start + repl_len - 1; var_start = -1; StringCopy(buffer_to, buffer_from, max_size); closing_brace = 0; replacement_made = true; } } } if (!replacement_made) { break; } } char *ret = xstrdup(buffer_to); BufferDestroy(format); free(buffer_to); free(buffer_from); return (Rval) { ret, RVAL_TYPE_SCALAR }; } else { return (Rval) { xstrdup(item), RVAL_TYPE_SCALAR }; } case RVAL_TYPE_FNCALL: return (Rval) { FnCallCopyRewriter(item, map), RVAL_TYPE_FNCALL }; case RVAL_TYPE_LIST: return (Rval) { RlistCopyRewriter(item, map), RVAL_TYPE_LIST }; case RVAL_TYPE_CONTAINER: return (Rval) { JsonCopy(item), RVAL_TYPE_CONTAINER }; case RVAL_TYPE_NOPROMISEE: return ((Rval) {NULL, type}); } assert(false); return ((Rval) { NULL, RVAL_TYPE_NOPROMISEE }); } Rval RvalNew(const void *item, RvalType type) { return RvalNewRewriter(item, type, NULL); } Rval RvalCopyRewriter(Rval rval, JsonElement *map) { return RvalNewRewriter(rval.item, rval.type, map); } Rval RvalCopy(Rval rval) { return RvalNew(rval.item, rval.type); } /*******************************************************************/ Rlist *RlistCopyRewriter(const Rlist *rp, JsonElement *map) { Rlist *start = NULL; while (rp != NULL) { RlistAppendRval(&start, RvalCopyRewriter(rp->val, map)); rp = rp->next; } return start; }