diff --git a/include/inja/json.hpp b/include/inja/json.hpp index 8ec1352..5a049dc 100644 --- a/include/inja/json.hpp +++ b/include/inja/json.hpp @@ -1,11 +1,228 @@ #ifndef INCLUDE_INJA_JSON_HPP_ #define INCLUDE_INJA_JSON_HPP_ +#include + +//#define INJA_JSONCONS +#ifdef INJA_JSONCONS +#include +#include +#else #include +#endif // def INJA_JSONCONS namespace inja { #ifndef INJA_DATA_TYPE +#ifdef INJA_JSONCONS +using json = jsoncons::json; +using json_pointer = jsoncons::jsonpointer::json_pointer; +namespace json_ { + +template +inline T as(json& j) { + return j.as(); +} +template +inline const T as(const json& j) { + return j.as(); +} + +inline bool is_null(const json& j) noexcept { + return j.is_null(); +} +inline bool is_string(const json& j) noexcept { + return j.is_string(); +} +inline bool is_bignum(const json& j) { + return j.is_bignum(); +} +inline bool is_bool(const json& j) noexcept { + return j.is_bool(); +} +inline bool is_object(const json& j) noexcept { + return j.is_object(); +} +inline bool is_array(const json& j) noexcept { + return j.is_array(); +} +inline bool is_int64(const json& j) noexcept { + return j.is_int64(); +} +inline bool is_uint64(const json& j) noexcept { + return j.is_uint64(); +} +inline bool is_half(const json& j) noexcept { + return j.is_half(); +} +inline bool is_float(const json& j) noexcept { + return j.is_double(); +} +inline bool is_number(const json& j) noexcept { + return j.is_number(); +} +inline bool is_empty(const json& j) noexcept { + return j.empty(); +} + +inline bool contains(const json& j, const json_pointer& p) { + return jsoncons::jsonpointer::contains(j, p); +} +inline bool contains(const json& j, const std::string_view& p) { + return jsoncons::jsonpointer::contains(j, p); +} + +inline bool has(const json& j, const std::string_view& name) { + return j.contains(name); +} + +inline json& get(json& j, const json_pointer& p) { + return jsoncons::jsonpointer::get(j, p); +} +inline const json& get(const json& j, const json_pointer& p) { + return jsoncons::jsonpointer::get(j, p); +} + +inline json::array_range_type array_range(json& j) noexcept { + assert(is_array(j)); + return j.array_range(); +} +inline json::const_array_range_type array_range(const json& j) noexcept { + assert(is_array(j)); + return j.array_range(); +} + +inline auto object_range(json& j) noexcept { + assert(is_object(j)); + return j.object_range(); +} +inline const auto object_range(const json& j) noexcept { + assert(is_object(j)); + return j.object_range(); +} + +template +inline void set_value(json& j, const json_pointer& ptr, T&& val) { + jsoncons::jsonpointer::replace(j, ptr, val, true); +} +template +inline void set_value(json& j, const std::string_view& ptr, T&& val) { + jsoncons::jsonpointer::replace(j, json_pointer(ptr), val, true); +} + +std::string dump(const json& j) +{ + return j.to_string(); +} + +} // namespace json_ +#else using json = nlohmann::json; +using json_pointer = json::json_pointer; + +namespace json_ { + +template +inline T as(json& j) { + return j.get(); +} +template +inline T as(const json& j) { + return j.get(); +} +template <> +inline std::string_view as(const json& j) { + auto& v = j.get_ref(); + return std::string_view(v); +} + +inline bool is_null(const json& j) noexcept { + return j.is_null(); +} +inline bool is_string(const json& j) noexcept { + return j.is_string(); +} +inline bool is_bool(const json& j) noexcept { + return j.is_boolean(); +} +inline bool is_object(const json& j) noexcept { + return j.is_object(); +} +inline bool is_array(const json& j) noexcept { + return j.is_array(); +} +inline bool is_int64(const json& j) noexcept { + return j.is_number_integer(); +} +inline bool is_uint64(const json& j) noexcept { + return j.is_number_unsigned(); +} +inline bool is_number(const json& j) noexcept { + return j.is_number(); +} +inline bool is_float(const json& j) noexcept { + return j.is_number_float(); +} +inline bool is_empty(const json& j) noexcept { + return j.empty(); +} + +inline bool contains(const json& j, const json_pointer& p) { + return j.contains(p); +} +inline bool contains(const json& j, const std::string_view& p) { + return j.contains(json_pointer(std::string(p))); +} + +inline bool has(const json& j, const std::string_view& name) { + return j.find(name) != j.end(); +} + +inline json& get(json& j, const json_pointer& p) { + return j[p]; +} +inline const json& get(const json& j, const json_pointer& p) { + return j[p]; +} + +inline json& array_range(json& j) noexcept { + assert(is_array(j)); + return j; +} +inline const json& array_range(const json& j) noexcept { + assert(is_array(j)); + return j; +} + +inline auto object_range(json& j) noexcept { + assert(is_object(j)); + return j.items(); +} +inline const auto object_range(const json& j) noexcept { + assert(is_object(j)); + return j.items(); +} + +template +inline void set_value(json& j, const json_pointer& ptr, T&& val) { + j[ptr] = val; +} +template +inline void set_value(json& j, const std::string& ptr, T&& val) { + j[json_pointer(ptr)] = val; +} +template +inline void set_value(json& j, const std::string_view& ptr, T&& val) { + j[json_pointer(std::string(ptr))] = val; +} + +std::string dump(const json& j) +{ + return j.dump(); +} + +} // namespace json_ + +#endif // def INJA_JSONCONS #else using json = INJA_DATA_TYPE; #endif diff --git a/include/inja/node.hpp b/include/inja/node.hpp index 21456ca..dbb6317 100644 --- a/include/inja/node.hpp +++ b/include/inja/node.hpp @@ -112,7 +112,7 @@ class LiteralNode : public ExpressionNode { class DataNode : public ExpressionNode { public: const std::string name; - const json::json_pointer ptr; + const json_pointer ptr; static std::string convert_dot_to_ptr(std::string_view ptr_name) { std::string result; @@ -125,7 +125,7 @@ class DataNode : public ExpressionNode { return result; } - 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): ExpressionNode(pos), name(ptr_name), ptr(json_pointer(convert_dot_to_ptr(ptr_name))) {} void accept(NodeVisitor& v) const override { v.visit(*this); diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index e135158..c68ac64 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -22,13 +22,14 @@ #include "template.hpp" #include "throw.hpp" #include "utils.hpp" +#include namespace inja { /*! @brief Escapes HTML */ -inline std::string htmlescape(const std::string& data) { +inline std::string htmlescape(const std::string_view& data) { std::string buffer; buffer.reserve(static_cast(1.1 * data.size())); for (size_t pos = 0; pos != data.size(); ++pos) { @@ -44,6 +45,17 @@ inline std::string htmlescape(const std::string& data) { return buffer; } +/*! +@brief concat std::string_view +*/ +inline std::string operator+(const std::string_view& a, const std::string_view& b) { + std::string buffer; + buffer.reserve(a.size() + b.size()); + std::copy(a.begin(), a.end(), std::back_inserter(buffer)); + std::copy(b.begin(), b.end(), std::back_inserter(buffer)); + return buffer; +} + /*! * \brief Class for rendering a Template with data. */ @@ -72,30 +84,34 @@ class Renderer : public NodeVisitor { bool break_rendering {false}; static bool truthy(const json* data) { - if (data->is_boolean()) { - return data->get(); - } else if (data->is_number()) { + if (json_::is_bool(*data)) { + return json_::as(*data); + } else if (json_::is_number(*data)) { return (*data != 0); } else if (data->is_null()) { return false; } - return !data->empty(); + return !json_::is_empty(*data); } void print_data(const std::shared_ptr& value) { - if (value->is_string()) { + const json& val = *value; + if (json_::is_string(val)) { if (config.html_autoescape) { - *output_stream << htmlescape(value->get_ref()); + *output_stream << htmlescape(json_::as(val)); } else { - *output_stream << value->get_ref(); + *output_stream << json_::as(val); } - } else if (value->is_number_unsigned()) { - *output_stream << value->get(); - } else if (value->is_number_integer()) { - *output_stream << value->get(); - } else if (value->is_null()) { + } else if (json_::is_uint64(val)) { + *output_stream << json_::as(val); + } else if (json_::is_int64(val)) { + *output_stream << json_::as(val); + } else if (json_::is_null(val)) { +#ifdef INJA_JSONCONS + } else if (json_::is_empty(val)) { +#endif // def INJA_JSONCONS } else { - *output_stream << value->dump(); + *output_stream << json_::dump(val); } } @@ -217,10 +233,10 @@ class Renderer : public NodeVisitor { } void visit(const DataNode& node) override { - if (additional_data.contains(node.ptr)) { - data_eval_stack.push(&(additional_data[node.ptr])); - } else if (data_input->contains(node.ptr)) { - data_eval_stack.push(&(*data_input)[node.ptr]); + if (json_::contains(additional_data, node.ptr)) { + data_eval_stack.push(&json_::get(additional_data, node.ptr)); + } else if (json_::contains(*data_input, node.ptr)) { + data_eval_stack.push(&json_::get(*data_input, node.ptr)); } else { // Try to evaluate as a no-argument callback const auto function_data = function_storage.find_function(node.name, 0); @@ -250,7 +266,8 @@ class Renderer : public NodeVisitor { } break; case Op::In: { const auto args = get_arguments<2>(node); - make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); + decltype(auto) range = json_::array_range(*args[1]); + make_result(std::find(range.begin(), range.end(), *args[0]) != range.end()); } break; case Op::Equal: { const auto args = get_arguments<2>(node); @@ -278,50 +295,50 @@ class Renderer : public NodeVisitor { } break; case Op::Add: { const auto args = get_arguments<2>(node); - if (args[0]->is_string() && args[1]->is_string()) { - make_result(args[0]->get_ref() + args[1]->get_ref()); - } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - make_result(args[0]->get() + args[1]->get()); + if (json_::is_string(*args[0]) && json_::is_string(*args[1])) { + make_result(json_::as(*args[0]) + json_::as(*args[1])); + } else if (json_::is_int64(*args[0]) && json_::is_int64(*args[1])) { + make_result(json_::as(*args[0]) + json_::as(*args[1])); } else { - make_result(args[0]->get() + args[1]->get()); + make_result(json_::as(*args[0]) + json_::as(*args[1])); } } break; case Op::Subtract: { const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - make_result(args[0]->get() - args[1]->get()); + if (json_::is_int64(*args[0]) && json_::is_int64(*args[1])) { + make_result(json_::as(*args[0]) - json_::as(*args[1])); } else { - make_result(args[0]->get() - args[1]->get()); + make_result(json_::as(*args[0]) - json_::as(*args[1])); } } break; case Op::Multiplication: { const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - make_result(args[0]->get() * args[1]->get()); + if (json_::is_int64(*args[0]) && json_::is_int64(*args[1])) { + make_result(json_::as(*args[0]) * json_::as(*args[1])); } else { - make_result(args[0]->get() * args[1]->get()); + make_result(json_::as(*args[0]) * json_::as(*args[1])); } } break; case Op::Division: { const auto args = get_arguments<2>(node); - if (args[1]->get() == 0) { + if (json_::as(*args[1]) == 0) { throw_renderer_error("division by zero", node); } - make_result(args[0]->get() / args[1]->get()); + make_result(json_::as(*args[0]) / json_::as(*args[1])); } break; case Op::Power: { const auto args = get_arguments<2>(node); - if (args[0]->is_number_integer() && args[1]->get() >= 0) { - const auto result = static_cast(std::pow(args[0]->get(), args[1]->get())); + if (json_::is_int64(*args[0]) && json_::as(*args[1]) >= 0) { + const auto result = static_cast(std::pow(json_::as(*args[0]), json_::as(*args[1]))); make_result(result); } else { - const auto result = std::pow(args[0]->get(), args[1]->get()); + const auto result = std::pow(json_::as(*args[0]), json_::as(*args[1])); make_result(result); } } break; case Op::Modulo: { const auto args = get_arguments<2>(node); - make_result(args[0]->get() % args[1]->get()); + make_result(json_::as(*args[0]) % json_::as(*args[1])); } break; case Op::AtId: { const auto container = get_arguments<1, 0, false>(node)[0]; @@ -337,13 +354,13 @@ class Renderer : public NodeVisitor { case Op::At: { const auto args = get_arguments<2>(node); if (args[0]->is_object()) { - data_eval_stack.push(&args[0]->at(args[1]->get())); + data_eval_stack.push(&args[0]->at(json_::as(*args[1]))); } else { - data_eval_stack.push(&args[0]->at(args[1]->get())); + data_eval_stack.push(&args[0]->at(json_::as(*args[1]))); } } break; case Op::Capitalize: { - auto result = get_arguments<1>(node)[0]->get(); + auto result = json_::as(*get_arguments<1>(node)[0]); result[0] = static_cast(::toupper(result[0])); std::transform(result.begin() + 1, result.end(), result.begin() + 1, [](char c) { return static_cast(::tolower(c)); }); make_result(std::move(result)); @@ -354,76 +371,77 @@ class Renderer : public NodeVisitor { } break; case Op::DivisibleBy: { const auto args = get_arguments<2>(node); - const auto divisor = args[1]->get(); - make_result((divisor != 0) && (args[0]->get() % divisor == 0)); + const auto divisor = json_::as(*args[1]); + make_result((divisor != 0) && (json_::as(*args[0]) % divisor == 0)); } break; case Op::Even: { - make_result(get_arguments<1>(node)[0]->get() % 2 == 0); + make_result(json_::as(*get_arguments<1>(node)[0]) % 2 == 0); } break; case Op::Exists: { - auto&& name = get_arguments<1>(node)[0]->get_ref(); - make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); + auto&& name = json_::as(*get_arguments<1>(node)[0]); + make_result(json_::contains(*data_input, DataNode::convert_dot_to_ptr(name))); } break; case Op::ExistsInObject: { const auto args = get_arguments<2>(node); - auto&& name = args[1]->get_ref(); - make_result(args[0]->find(name) != args[0]->end()); + auto name = json_::as(*args[1]); + make_result(json_::has(*args[0], name)); } break; case Op::First: { - const auto result = &get_arguments<1>(node)[0]->front(); + const auto result = &get_arguments<1>(node)[0]->at(0); data_eval_stack.push(result); } break; case Op::Float: { - make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); + make_result(std::stod(json_::as(*get_arguments<1>(node)[0]))); } break; case Op::Int: { - make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); + make_result(std::stoi(json_::as(*get_arguments<1>(node)[0]))); } break; case Op::Last: { - const auto result = &get_arguments<1>(node)[0]->back(); + const auto a0 = get_arguments<1>(node)[0]; + const auto result = &a0->at(a0->size() - 1); data_eval_stack.push(result); } break; case Op::Length: { const auto val = get_arguments<1>(node)[0]; if (val->is_string()) { - make_result(val->get_ref().length()); + make_result(json_::as(*val).size()); } else { make_result(val->size()); } } break; case Op::Lower: { - auto result = get_arguments<1>(node)[0]->get(); + auto result = json_::as(*get_arguments<1>(node)[0]); std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::tolower(c)); }); make_result(std::move(result)); } break; case Op::Max: { - const auto args = get_arguments<1>(node); - const auto result = std::max_element(args[0]->begin(), args[0]->end()); + decltype(auto) args = json_::array_range(*get_arguments<1>(node)[0]); + const auto result = std::max_element(args.begin(), args.end()); data_eval_stack.push(&(*result)); } break; case Op::Min: { - const auto args = get_arguments<1>(node); - const auto result = std::min_element(args[0]->begin(), args[0]->end()); + decltype(auto) args = json_::array_range(*get_arguments<1>(node)[0]); + const auto result = std::min_element(args.begin(), args.end()); data_eval_stack.push(&(*result)); } break; case Op::Odd: { - make_result(get_arguments<1>(node)[0]->get() % 2 != 0); + make_result(json_::as(*get_arguments<1>(node)[0]) % 2 != 0); } break; case Op::Range: { - std::vector result(get_arguments<1>(node)[0]->get()); + std::vector result(json_::as(*get_arguments<1>(node)[0])); std::iota(result.begin(), result.end(), 0); make_result(std::move(result)); } break; case Op::Replace: { const auto args = get_arguments<3>(node); - auto result = args[0]->get(); - replace_substring(result, args[1]->get(), args[2]->get()); + auto result = json_::as(*args[0]); + replace_substring(result, json_::as(*args[1]), json_::as(*args[2])); make_result(std::move(result)); } break; case Op::Round: { const auto args = get_arguments<2>(node); - const auto precision = args[1]->get(); - const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + const auto precision = json_::as(*args[1]); + const double result = std::round(json_::as(*args[0]) * std::pow(10.0, precision)) / std::pow(10.0, precision); if (precision == 0) { make_result(static_cast(result)); } else { @@ -431,36 +449,37 @@ class Renderer : public NodeVisitor { } } break; case Op::Sort: { - auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); - std::sort(result_ptr->begin(), result_ptr->end()); + auto result_ptr = std::make_shared(json_::as>(*get_arguments<1>(node)[0])); + decltype(auto) range = json_::array_range(*result_ptr); + std::sort(range.begin(), range.end()); data_tmp_stack.push_back(result_ptr); data_eval_stack.push(result_ptr.get()); } break; case Op::Upper: { - auto result = get_arguments<1>(node)[0]->get(); + auto result = json_::as(*get_arguments<1>(node)[0]); std::transform(result.begin(), result.end(), result.begin(), [](char c) { return static_cast(::toupper(c)); }); make_result(std::move(result)); } break; case Op::IsBoolean: { - make_result(get_arguments<1>(node)[0]->is_boolean()); + make_result(json_::is_bool(*get_arguments<1>(node)[0])); } break; case Op::IsNumber: { - make_result(get_arguments<1>(node)[0]->is_number()); + make_result(json_::is_number(*get_arguments<1>(node)[0])); } break; case Op::IsInteger: { - make_result(get_arguments<1>(node)[0]->is_number_integer()); + make_result(json_::is_int64(*get_arguments<1>(node)[0])); } break; case Op::IsFloat: { - make_result(get_arguments<1>(node)[0]->is_number_float()); + make_result(json_::is_float(*get_arguments<1>(node)[0])); } break; case Op::IsObject: { - make_result(get_arguments<1>(node)[0]->is_object()); + make_result(json_::is_object(*get_arguments<1>(node)[0])); } break; case Op::IsArray: { - make_result(get_arguments<1>(node)[0]->is_array()); + make_result(json_::is_array(*get_arguments<1>(node)[0])); } break; case Op::IsString: { - make_result(get_arguments<1>(node)[0]->is_string()); + make_result(json_::is_string(*get_arguments<1>(node)[0])); } break; case Op::Callback: { auto args = get_argument_vector(node); @@ -469,7 +488,7 @@ class Renderer : public NodeVisitor { case Op::Super: { const auto args = get_argument_vector(node); const size_t old_level = current_level; - const size_t level_diff = (args.size() == 1) ? args[0]->get() : 1; + const size_t level_diff = (args.size() == 1) ? json_::as(*args[0]) : 1; const size_t level = current_level + level_diff; if (block_statement_stack.empty()) { @@ -497,15 +516,15 @@ class Renderer : public NodeVisitor { } break; case Op::Join: { const auto args = get_arguments<2>(node); - const auto separator = args[1]->get(); + const auto separator = json_::as(*args[1]); std::ostringstream os; std::string sep; - for (const auto& value : *args[0]) { + for (const auto& value : json_::array_range(*args[0])) { os << sep; if (value.is_string()) { - os << value.get(); // otherwise the value is surrounded with "" + os << json_::as(value); // otherwise the value is surrounded with "" } else { - os << value.dump(); + os << json_::dump(value); } sep = separator; } @@ -538,8 +557,12 @@ class Renderer : public NodeVisitor { size_t index = 0; (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); - for (auto it = result->begin(); it != result->end(); ++it) { - additional_data[static_cast(node.value)] = *it; + for (auto& it: json_::array_range(*result)) { + additional_data[static_cast(node.value)] = it; +#ifdef INJA_JSONCONS + // modifing additional_data invalidate current_loop_data + current_loop_data = &additional_data["loop"]; +#endif // def INJA_JSONCONS (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -555,6 +578,9 @@ class Renderer : public NodeVisitor { } additional_data[static_cast(node.value)].clear(); +#ifdef INJA_JSONCONS + current_loop_data = &additional_data["loop"]; +#endif // def INJA_JSONCONS if (!(*current_loop_data)["parent"].empty()) { const auto tmp = (*current_loop_data)["parent"]; *current_loop_data = tmp; @@ -576,9 +602,12 @@ class Renderer : public NodeVisitor { size_t index = 0; (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); - for (auto it = result->begin(); it != result->end(); ++it) { + for (const auto& it : json_::object_range(*result)) { additional_data[static_cast(node.key)] = it.key(); additional_data[static_cast(node.value)] = it.value(); +#ifdef INJA_JSONCONS + current_loop_data = &additional_data["loop"]; +#endif // def INJA_JSONCONS (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -595,6 +624,9 @@ class Renderer : public NodeVisitor { additional_data[static_cast(node.key)].clear(); additional_data[static_cast(node.value)].clear(); +#ifdef INJA_JSONCONS + current_loop_data = &additional_data["loop"]; +#endif // def INJA_JSONCONS if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { @@ -650,7 +682,7 @@ class Renderer : public NodeVisitor { std::string ptr = node.key; replace_substring(ptr, ".", "/"); ptr = "/" + ptr; - additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); + json_::set_value(additional_data, ptr, *eval_expression_list(node.expression)); } public: