diff --git a/include/inja/config.hpp b/include/inja/config.hpp index 0a8f9b7f..3fa22622 100644 --- a/include/inja/config.hpp +++ b/include/inja/config.hpp @@ -74,6 +74,7 @@ struct ParserConfig { */ struct RenderConfig { bool throw_at_missing_includes {true}; + bool escape_strings {}; }; } // namespace inja diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index 9cab39c0..e0d12e74 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -83,6 +83,11 @@ class Environment { lexer_config.lstrip_blocks = lstrip_blocks; } + /// Sets the config for rendering strings raw or escaped + void set_escape_strings(bool escape_strings) { + render_config.escape_strings = escape_strings; + } + /// Sets the element notation syntax void set_search_included_templates_in_files(bool search_in_files) { parser_config.search_included_templates_in_files = search_in_files; diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index 354f8df7..f1281711 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -55,7 +55,20 @@ class Renderer : public NodeVisitor { void print_data(const std::shared_ptr value) { if (value->is_string()) { - *output_stream << value->get_ref(); + std::string val; + if (config.escape_strings) { + // get the value as a dump() to properly escape values + val = value->dump(); + + // strip the leading and trailing " characters that are added by dump() + // if C++20 is adopted, val.starts_with and val.ends_with would clean this up a bit + val = val.substr(0,1) == "\"" && val.substr(val.length()-1,1) == "\"" + ? val.substr(1, val.length()-2) + : val; + } else { + val = value->get_ref(); + } + *output_stream << val; } else if (value->is_number_integer()) { *output_stream << value->get(); } else if (value->is_null()) { diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 78b431cb..4d8a9157 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -882,6 +882,7 @@ struct ParserConfig { */ struct RenderConfig { bool throw_at_missing_includes {true}; + bool escape_strings {}; }; } // namespace inja @@ -2124,7 +2125,20 @@ class Renderer : public NodeVisitor { void print_data(const std::shared_ptr value) { if (value->is_string()) { - *output_stream << value->get_ref(); + std::string val; + if (config.escape_strings) { + // get the value as a dump() to properly escape values + val = value->dump(); + + // strip the leading and trailing " characters that are added by dump() + // if C++20 is adopted, val.starts_with and val.ends_with would clean this up a bit + val = val.substr(0,1) == "\"" && val.substr(val.length()-1,1) == "\"" + ? val.substr(1, val.length()-2) + : val; + } else { + val = value->get_ref(); + } + *output_stream << val; } else if (value->is_number_integer()) { *output_stream << value->get(); } else if (value->is_null()) { @@ -2772,6 +2786,11 @@ class Environment { lexer_config.lstrip_blocks = lstrip_blocks; } + /// Sets the config for rendering strings raw or escaped + void set_escape_strings(bool escape_strings) { + render_config.escape_strings = escape_strings; + } + /// Sets the element notation syntax void set_search_included_templates_in_files(bool search_in_files) { parser_config.search_included_templates_in_files = search_in_files; diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp index 9963246a..9f4b27c8 100644 --- a/test/test-renderer.cpp +++ b/test/test-renderer.cpp @@ -18,6 +18,7 @@ TEST_CASE("types") { data["relatives"]["brother"] = "Chris"; data["relatives"]["sister"] = "Jenny"; data["vars"] = {2, 3, 4, 0, -1, -2, -3}; + data["quoted"] = "\"quoted value\""; SUBCASE("basic") { CHECK(env.render("", data) == ""); @@ -39,6 +40,11 @@ TEST_CASE("types") { CHECK(env.render("{{ @name }}", data) == "@name"); CHECK(env.render("{{ $name }}", data) == "$name"); + CHECK(env.render("{\"Value\":\"{{ quoted }}\"}", data) == "{\"Value\":\"\"quoted value\"\"}"); + env.set_escape_strings(true); + CHECK(env.render("{\"Value\":\"{{ quoted }}\"}", data) == "{\"Value\":\"\\\"quoted value\\\"\"}"); + env.set_escape_strings(false); + CHECK_THROWS_WITH(env.render("{{unknown}}", data), "[inja.exception.render_error] (at 1:3) variable 'unknown' not found"); }