diff --git a/include/inja/config.hpp b/include/inja/config.hpp index 0a8f9b7f..49341b9d 100644 --- a/include/inja/config.hpp +++ b/include/inja/config.hpp @@ -8,6 +8,8 @@ namespace inja { +enum class ElementNotation { Dot, Pointer }; + /*! * \brief Class for lexer configuration. */ @@ -28,6 +30,8 @@ struct LexerConfig { std::string comment_close_force_rstrip {"-#}"}; std::string open_chars {"#{"}; + ElementNotation notation {ElementNotation::Dot}; + bool trim_blocks {false}; bool lstrip_blocks {false}; @@ -64,6 +68,8 @@ struct LexerConfig { * \brief Class for parser configuration. */ struct ParserConfig { + ElementNotation notation {ElementNotation::Dot}; + bool search_included_templates_in_files {true}; std::function include_callback; diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index 9cab39c0..46fbc856 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -82,6 +82,11 @@ class Environment { void set_lstrip_blocks(bool lstrip_blocks) { lexer_config.lstrip_blocks = lstrip_blocks; } + /// Sets the element notation syntax + void set_element_notation(ElementNotation notation) { + parser_config.notation = notation; + lexer_config.notation = notation; + } /// Sets the element notation syntax void set_search_included_templates_in_files(bool search_in_files) { diff --git a/include/inja/lexer.hpp b/include/inja/lexer.hpp index 789c61a1..a8612617 100644 --- a/include/inja/lexer.hpp +++ b/include/inja/lexer.hpp @@ -81,7 +81,7 @@ class Lexer { } pos = tok_start + 1; - if (std::isalpha(ch)) { + if (std::isalpha(ch) || ch == '~') { minus_state = MinusState::Operator; return scan_id(); } @@ -177,12 +177,13 @@ class Lexer { } Token scan_id() { + bool isDotNotation = config.notation == ElementNotation::Dot; for (;;) { if (pos >= m_in.size()) { break; } const char ch = m_in[pos]; - if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-' && (isDotNotation || ch != '~')) { break; } pos += 1; diff --git a/include/inja/node.hpp b/include/inja/node.hpp index 9ad9ef6e..e783a59d 100644 --- a/include/inja/node.hpp +++ b/include/inja/node.hpp @@ -10,6 +10,11 @@ namespace inja { +enum NotationFlag { + Dot = 0x00, + Pointer = 0x01, +}; + class NodeVisitor; class BlockNode; class TextNode; @@ -110,6 +115,15 @@ class DataNode : public ExpressionNode { const std::string name; const json::json_pointer ptr; + static std::string get_ptr(std::string_view ptr_name, NotationFlag notation) { + auto ptr = notation == NotationFlag::Dot ? convert_dot_to_ptr(ptr_name) : ptr_name.data(); + if(ptr.substr(0,1) != "/") { + ptr = "/" + ptr; + } + + return ptr; + } + static std::string convert_dot_to_ptr(std::string_view ptr_name) { std::string result; do { @@ -123,6 +137,10 @@ class DataNode : public ExpressionNode { explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} + explicit DataNode(std::string_view ptr_name, size_t pos, NotationFlag notation) + : ExpressionNode(pos), name(ptr_name), + ptr(json::json_pointer(get_ptr(ptr_name, notation))) {} + void accept(NodeVisitor& v) const { v.visit(*this); } diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index 0cd22336..6f35535b 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -237,7 +237,8 @@ class Parser { // Variables } else { - arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + auto notation = this->config.notation == ElementNotation::Dot ? NotationFlag::Dot : NotationFlag::Pointer; + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str(), notation)); } // Operators diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 78b431cb..21db7e5a 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -348,6 +348,11 @@ inline void replace_substring(std::string& s, const std::string& f, const std::s namespace inja { +enum NotationFlag { + Dot = 0x00, + Pointer = 0x01, +}; + class NodeVisitor; class BlockNode; class TextNode; @@ -448,6 +453,15 @@ class DataNode : public ExpressionNode { const std::string name; const json::json_pointer ptr; + static std::string get_ptr(std::string_view ptr_name, NotationFlag notation) { + auto ptr = notation == NotationFlag::Dot ? convert_dot_to_ptr(ptr_name) : ptr_name.data(); + if(ptr.substr(0,1) != "/") { + ptr = "/" + ptr; + } + + return ptr; + } + static std::string convert_dot_to_ptr(std::string_view ptr_name) { std::string result; do { @@ -461,6 +475,10 @@ class DataNode : public ExpressionNode { explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} + explicit DataNode(std::string_view ptr_name, size_t pos, NotationFlag notation) + : ExpressionNode(pos), name(ptr_name), + ptr(json::json_pointer(get_ptr(ptr_name, notation))) {} + void accept(NodeVisitor& v) const { v.visit(*this); } @@ -816,6 +834,8 @@ using TemplateStorage = std::map; namespace inja { +enum class ElementNotation { Dot, Pointer }; + /*! * \brief Class for lexer configuration. */ @@ -836,6 +856,8 @@ struct LexerConfig { std::string comment_close_force_rstrip {"-#}"}; std::string open_chars {"#{"}; + ElementNotation notation {ElementNotation::Dot}; + bool trim_blocks {false}; bool lstrip_blocks {false}; @@ -872,6 +894,8 @@ struct LexerConfig { * \brief Class for parser configuration. */ struct ParserConfig { + ElementNotation notation {ElementNotation::Dot}; + bool search_included_templates_in_files {true}; std::function include_callback; @@ -1066,7 +1090,7 @@ class Lexer { } pos = tok_start + 1; - if (std::isalpha(ch)) { + if (std::isalpha(ch) || ch == '~') { minus_state = MinusState::Operator; return scan_id(); } @@ -1162,12 +1186,13 @@ class Lexer { } Token scan_id() { + bool isDotNotation = config.notation == ElementNotation::Dot; for (;;) { if (pos >= m_in.size()) { break; } const char ch = m_in[pos]; - if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-' && (isDotNotation || ch != '~')) { break; } pos += 1; @@ -1649,7 +1674,8 @@ class Parser { // Variables } else { - arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + auto notation = this->config.notation == ElementNotation::Dot ? NotationFlag::Dot : NotationFlag::Pointer; + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str(), notation)); } // Operators @@ -2771,6 +2797,11 @@ class Environment { void set_lstrip_blocks(bool lstrip_blocks) { lexer_config.lstrip_blocks = lstrip_blocks; } + /// Sets the element notation syntax + void set_element_notation(ElementNotation notation) { + parser_config.notation = notation; + lexer_config.notation = notation; + } /// Sets the element notation syntax void set_search_included_templates_in_files(bool search_in_files) { diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp index 9963246a..00f4a16c 100644 --- a/test/test-renderer.cpp +++ b/test/test-renderer.cpp @@ -18,6 +18,9 @@ TEST_CASE("types") { data["relatives"]["brother"] = "Chris"; data["relatives"]["sister"] = "Jenny"; data["vars"] = {2, 3, 4, 0, -1, -2, -3}; + data["json_pointers"]["example.com"] = "online"; + data["json_pointers"]["and/or"] = "slash"; + data["json_pointers"]["and~or"] = "tilde"; SUBCASE("basic") { CHECK(env.render("", data) == ""); @@ -39,6 +42,12 @@ TEST_CASE("types") { CHECK(env.render("{{ @name }}", data) == "@name"); CHECK(env.render("{{ $name }}", data) == "$name"); + env.set_element_notation(inja::ElementNotation::Pointer); + CHECK(env.render("{{ json_pointers/example.com }}", data) == "online"); + CHECK(env.render("{{ json_pointers/and~1or }}", data) == "slash"); + CHECK(env.render("{{ json_pointers/and~0or }}", data) == "tilde"); + env.set_element_notation(inja::ElementNotation::Dot); + CHECK_THROWS_WITH(env.render("{{unknown}}", data), "[inja.exception.render_error] (at 1:3) variable 'unknown' not found"); }