Rlist *RlistFromContainer(const JsonElement *container) { Rlist *list = NULL; switch (JsonGetElementType(container)) { case JSON_ELEMENT_TYPE_PRIMITIVE: RlistAppendContainerPrimitive(&list, container); break; case JSON_ELEMENT_TYPE_CONTAINER: { JsonIterator iter = JsonIteratorInit(container); const JsonElement *child; while (NULL != (child = JsonIteratorNextValue(&iter))) { if (JsonGetElementType(child) == JSON_ELEMENT_TYPE_PRIMITIVE) { RlistAppendContainerPrimitive(&list, child); } } } break; } return list; }
static JsonElement *LookupVariable(Seq *hash_stack, const char *name, size_t name_len) { assert(SeqLength(hash_stack) > 0); size_t num_comps = StringCountTokens(name, name_len, "."); JsonElement *base_var = NULL; { StringRef base_comp = StringGetToken(name, name_len, 0, "."); char *base_comp_str = xstrndup(base_comp.data, base_comp.len); for (ssize_t i = SeqLength(hash_stack) - 1; i >= 0; i--) { JsonElement *hash = SeqAt(hash_stack, i); if (!hash) { continue; } if (JsonGetElementType(hash) == JSON_ELEMENT_TYPE_CONTAINER && JsonGetContainerType(hash) == JSON_CONTAINER_TYPE_OBJECT) { JsonElement *var = JsonObjectGet(hash, base_comp_str); if (var) { base_var = var; break; } } } free(base_comp_str); } if (!base_var) { return NULL; } for (size_t i = 1; i < num_comps; i++) { if (JsonGetElementType(base_var) != JSON_ELEMENT_TYPE_CONTAINER || JsonGetContainerType(base_var) != JSON_CONTAINER_TYPE_OBJECT) { return NULL; } StringRef comp = StringGetToken(name, name_len, i, "."); char *comp_str = xstrndup(comp.data, comp.len); base_var = JsonObjectGet(base_var, comp_str); free(comp_str); if (!base_var) { return NULL; } } assert(base_var); return base_var; }
static void RlistAppendContainerPrimitive(Rlist **list, const JsonElement *primitive) { assert(JsonGetElementType(primitive) == JSON_ELEMENT_TYPE_PRIMITIVE); switch (JsonGetPrimitiveType(primitive)) { case JSON_PRIMITIVE_TYPE_BOOL: RlistAppendScalar(list, JsonPrimitiveGetAsBool(primitive) ? "true" : "false"); break; case JSON_PRIMITIVE_TYPE_INTEGER: { char *str = StringFromLong(JsonPrimitiveGetAsInteger(primitive)); RlistAppendScalar(list, str); free(str); } break; case JSON_PRIMITIVE_TYPE_REAL: { char *str = StringFromDouble(JsonPrimitiveGetAsReal(primitive)); RlistAppendScalar(list, str); free(str); } break; case JSON_PRIMITIVE_TYPE_STRING: RlistAppendScalar(list, JsonPrimitiveGetAsString(primitive)); break; case JSON_PRIMITIVE_TYPE_NULL: break; } }
static bool RenderVariable(Writer *out, const char *content, size_t content_len, bool escaped, Seq *hash_stack) { JsonElement *var = NULL; if (strncmp(content, ".", content_len) == 0) { var = SeqAt(hash_stack, SeqLength(hash_stack) - 1); } else { var = LookupVariable(hash_stack, content, content_len); } if (!var) { return true; } switch (JsonGetElementType(var)) { case JSON_ELEMENT_TYPE_PRIMITIVE: return RenderVariablePrimitive(out, var, escaped); case JSON_ELEMENT_TYPE_CONTAINER: assert(false); return false; } assert(false); return false; }
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 bool RenderVariable(Buffer *out, const char *content, size_t content_len, TagType conversion, Seq *hash_stack) { JsonElement *var = NULL; bool escape = conversion == TAG_TYPE_VAR; bool serialize = conversion == TAG_TYPE_VAR_SERIALIZED; bool serialize_compact = conversion == TAG_TYPE_VAR_SERIALIZED_COMPACT; const bool item_mode = strncmp(content, ".", content_len) == 0; const bool key_mode = strncmp(content, "@", content_len) == 0; if (item_mode || key_mode) { var = SeqAt(hash_stack, SeqLength(hash_stack) - 1); } else { var = LookupVariable(hash_stack, content, content_len); } if (!var) { return true; } switch (JsonGetElementType(var)) { case JSON_ELEMENT_TYPE_PRIMITIVE: // note that this also covers 'serialize' on primitives return RenderVariablePrimitive(out, var, escape, key_mode); case JSON_ELEMENT_TYPE_CONTAINER: if (serialize || serialize_compact) { return RenderVariableContainer(out, var, serialize_compact); } else { assert(false); return false; } } assert(false); return false; }
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); }
/** * Expand a #string into Buffer #out, returning the pointer to the string * itself, inside the Buffer #out. If #out is NULL then the buffer will be * created and destroyed internally. * * @retval NULL something went wrong */ char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out) { bool out_belongs_to_us = false; if (out == NULL) { out = BufferNew(); out_belongs_to_us = true; } assert(string != NULL); assert(out != NULL); Buffer *current_item = BufferNew(); for (const char *sp = string; *sp != '\0'; sp++) { BufferClear(current_item); ExtractScalarPrefix(current_item, sp, strlen(sp)); BufferAppend(out, BufferData(current_item), BufferSize(current_item)); sp += BufferSize(current_item); if (*sp == '\0') { break; } BufferClear(current_item); char varstring = sp[1]; ExtractScalarReference(current_item, sp, strlen(sp), true); sp += BufferSize(current_item) + 2; if (IsCf3VarString(BufferData(current_item))) { Buffer *temp = BufferCopy(current_item); BufferClear(current_item); ExpandScalar(ctx, ns, scope, BufferData(temp), current_item); BufferDestroy(temp); } if (!IsExpandable(BufferData(current_item))) { VarRef *ref = VarRefParseFromNamespaceAndScope( BufferData(current_item), ns, scope, CF_NS, '.'); DataType value_type; const void *value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); switch (DataTypeToRvalType(value_type)) { case RVAL_TYPE_SCALAR: assert(value != NULL); BufferAppendString(out, value); continue; break; case RVAL_TYPE_CONTAINER: { assert(value != NULL); const JsonElement *jvalue = value; /* instead of casts */ if (JsonGetElementType(jvalue) == JSON_ELEMENT_TYPE_PRIMITIVE) { BufferAppendString(out, JsonPrimitiveGetAsString(jvalue)); continue; } break; } default: /* TODO Log() */ break; } } if (varstring == '{') { BufferAppendF(out, "${%s}", BufferData(current_item)); } else { BufferAppendF(out, "$(%s)", BufferData(current_item)); } } BufferDestroy(current_item); LogDebug(LOG_MOD_EXPAND, "ExpandScalar( %s : %s . %s ) => %s", SAFENULL(ns), SAFENULL(scope), string, BufferData(out)); return out_belongs_to_us ? BufferClose(out) : BufferGet(out); }
bool ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out) { assert(string); if (strlen(string) == 0) { return true; } bool fully_expanded = true; Buffer *current_item = BufferNew(); for (const char *sp = string; *sp != '\0'; sp++) { BufferClear(current_item); ExtractScalarPrefix(current_item, sp, strlen(sp)); BufferAppend(out, BufferData(current_item), BufferSize(current_item)); sp += BufferSize(current_item); if (*sp == '\0') { break; } BufferClear(current_item); char varstring = sp[1]; ExtractScalarReference(current_item, sp, strlen(sp), true); sp += BufferSize(current_item) + 2; if (IsCf3VarString(BufferData(current_item))) { Buffer *temp = BufferCopy(current_item); BufferClear(current_item); ExpandScalar(ctx, ns, scope, BufferData(temp), current_item); BufferDestroy(temp); } if (!IsExpandable(BufferData(current_item))) { DataType type = CF_DATA_TYPE_NONE; const void *value = NULL; { VarRef *ref = VarRefParseFromNamespaceAndScope(BufferData(current_item), ns, scope, CF_NS, '.'); value = EvalContextVariableGet(ctx, ref, &type); VarRefDestroy(ref); } if (value) { switch (DataTypeToRvalType(type)) { case RVAL_TYPE_SCALAR: BufferAppendString(out, value); continue; case RVAL_TYPE_CONTAINER: if (JsonGetElementType((JsonElement*)value) == JSON_ELEMENT_TYPE_PRIMITIVE) { BufferAppendString(out, JsonPrimitiveGetAsString((JsonElement*)value)); continue; } break; default: break; } } } if (varstring == '{') { BufferAppendF(out, "${%s}", BufferData(current_item)); } else { BufferAppendF(out, "$(%s)", BufferData(current_item)); } } BufferDestroy(current_item); return fully_expanded; }