diff --git a/README.md b/README.md index 4db9707..2dadfa6 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,13 @@ Variables can also be defined within the template using the set statment. render("{% set new_hour=23 %}{{ new_hour }}pm", data); // "23pm" ``` +json pointers can be used to set sub-objects: +```.cpp +render("{% set time.start=18 %}{{ time.start }}pm", data); // "18pm" +``` + +Assignments only set the value within the rendering context; they do not modify the json object passed into the `render` call. + ### Functions A few functions are implemented within the inja template syntax. They can be called with diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index 05c3c3b..af774b2 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -184,10 +184,10 @@ class Renderer : public NodeVisitor { void visit(const JsonNode& node) { if (json_additional_data.contains(node.ptr)) { json_eval_stack.push(&(json_additional_data[node.ptr])); - + } else if (json_input->contains(node.ptr)) { json_eval_stack.push(&(*json_input)[node.ptr]); - + } else { // Try to evaluate as a no-argument callback const auto function_data = function_storage.find_function(node.name, 0); @@ -662,7 +662,10 @@ class Renderer : public NodeVisitor { } void visit(const SetStatementNode& node) { - json_additional_data[node.key] = *eval_expression_list(node.expression); + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + json_additional_data[nlohmann::json::json_pointer(ptr)] = *eval_expression_list(node.expression); } public: diff --git a/include/inja/utils.hpp b/include/inja/utils.hpp index 8750759..4336833 100644 --- a/include/inja/utils.hpp +++ b/include/inja/utils.hpp @@ -69,6 +69,17 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po return {count_lines + 1, sliced.length() - last_newline}; } +inline void replace_substring(std::string& s, const std::string& f, + const std::string& t) +{ + if (f.empty()) return; + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + } // namespace inja #endif // INCLUDE_INJA_UTILS_HPP_ diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 4737c01..e27ebb7 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -1907,6 +1907,17 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po return {count_lines + 1, sliced.length() - last_newline}; } +inline void replace_substring(std::string& s, const std::string& f, + const std::string& t) +{ + if (f.empty()) return; + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + } // namespace inja #endif // INCLUDE_INJA_UTILS_HPP_ @@ -3663,10 +3674,10 @@ class Renderer : public NodeVisitor { void visit(const JsonNode& node) { if (json_additional_data.contains(node.ptr)) { json_eval_stack.push(&(json_additional_data[node.ptr])); - + } else if (json_input->contains(node.ptr)) { json_eval_stack.push(&(*json_input)[node.ptr]); - + } else { // Try to evaluate as a no-argument callback const auto function_data = function_storage.find_function(node.name, 0); @@ -4141,7 +4152,10 @@ class Renderer : public NodeVisitor { } void visit(const SetStatementNode& node) { - json_additional_data[node.key] = *eval_expression_list(node.expression); + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + json_additional_data[nlohmann::json::json_pointer(ptr)] = *eval_expression_list(node.expression); } public: diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp index db6c00c..e06b803 100644 --- a/test/test-renderer.cpp +++ b/test/test-renderer.cpp @@ -125,8 +125,13 @@ TEST_CASE("types") { SUBCASE("set statements") { CHECK(env.render("{% set predefined=true %}{% if predefined %}a{% endif %}", data) == "a"); CHECK(env.render("{% set predefined=false %}{% if predefined %}a{% endif %}", data) == ""); - CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data), + CHECK(env.render("{% set age=30 %}{{age}}", data) == "30"); + CHECK(env.render("{% set predefined.value=1 %}{% if existsIn(predefined, \"value\") %}{{predefined.value}}{% endif %}", data) == "1"); + CHECK(env.render("{% set brother.name=\"Bob\" %}{{brother.name}}", data) == "Bob"); + CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data), "[inja.exception.render_error] (at 1:7) variable 'predefined' not found"); + CHECK(env.render("{{age}}", data) == "29"); + CHECK(env.render("{{brother.name}}", data) == "Chris"); } SUBCASE("short circuit evaluation") { @@ -210,7 +215,7 @@ TEST_CASE("templates") { CHECK(env.render("Test\n {%- if is_happy %}{{ name }}{% endif %} ", data) == "Test\nPeter "); CHECK(env.render(" {%+ if is_happy %}{{ name }}{% endif %}", data) == " Peter"); CHECK(env.render(" {%- if is_happy %}{{ name }}{% endif -%} \n ", data) == "Peter"); - + CHECK(env.render(" {{- name -}} \n ", data) == "Peter"); CHECK(env.render("Test\n {{- name }} ", data) == "Test\nPeter "); CHECK(env.render(" {{ name }}\n ", data) == " Peter\n ");