#ifndef PANTOR_INJA_RENDERER_HPP #define PANTOR_INJA_RENDERER_HPP #include #include #include namespace inja { using json = nlohmann::json; class Renderer { public: std::map> map_callbacks; template bool eval_expression(const Parsed::ElementExpression& element, const json& data) { const json var = eval_function(element, data); if (var.empty()) { return false; } else if (var.is_number()) { return (var != 0); } else if (var.is_string()) { return not var.empty(); } try { return var.get(); } catch (json::type_error& e) { inja_throw("json_error", e.what()); throw; } } template T eval_expression(const Parsed::ElementExpression& element, const json& data) { const json var = eval_function(element, data); if (var.empty()) return T(); try { return var.get(); } catch (json::type_error& e) { inja_throw("json_error", e.what()); throw; } } json eval_function(const Parsed::ElementExpression& element, const json& data) { switch (element.function) { case Parsed::Function::Upper: { std::string str = eval_expression(element.args[0], data); std::transform(str.begin(), str.end(), str.begin(), ::toupper); return str; } case Parsed::Function::Lower: { std::string str = eval_expression(element.args[0], data); std::transform(str.begin(), str.end(), str.begin(), ::tolower); return str; } case Parsed::Function::Range: { const int number = eval_expression(element.args[0], data); std::vector result(number); std::iota(std::begin(result), std::end(result), 0); return result; } case Parsed::Function::Length: { const std::vector list = eval_expression>(element.args[0], data); return list.size(); } case Parsed::Function::Sort: { std::vector list = eval_expression>(element.args[0], data); std::sort(list.begin(), list.end()); return list; } case Parsed::Function::First: { const std::vector list = eval_expression>(element.args[0], data); return list.front(); } case Parsed::Function::Last: { const std::vector list = eval_expression>(element.args[0], data); return list.back(); } case Parsed::Function::Round: { const double number = eval_expression(element.args[0], data); const int precision = eval_expression(element.args[1], data); return std::round(number * std::pow(10.0, precision)) / std::pow(10.0, precision); } case Parsed::Function::DivisibleBy: { const int number = eval_expression(element.args[0], data); const int divisor = eval_expression(element.args[1], data); return (divisor != 0) && (number % divisor == 0); } case Parsed::Function::Odd: { const int number = eval_expression(element.args[0], data); return (number % 2 != 0); } case Parsed::Function::Even: { const int number = eval_expression(element.args[0], data); return (number % 2 == 0); } case Parsed::Function::Max: { const std::vector list = eval_expression>(element.args[0], data); return *std::max_element(list.begin(), list.end()); } case Parsed::Function::Min: { const std::vector list = eval_expression>(element.args[0], data); return *std::min_element(list.begin(), list.end()); } case Parsed::Function::Not: { return not eval_expression(element.args[0], data); } case Parsed::Function::And: { return (eval_expression(element.args[0], data) and eval_expression(element.args[1], data)); } case Parsed::Function::Or: { return (eval_expression(element.args[0], data) or eval_expression(element.args[1], data)); } case Parsed::Function::In: { const json value = eval_expression(element.args[0], data); const json list = eval_expression(element.args[1], data); return (std::find(list.begin(), list.end(), value) != list.end()); } case Parsed::Function::Equal: { return eval_expression(element.args[0], data) == eval_expression(element.args[1], data); } case Parsed::Function::Greater: { return eval_expression(element.args[0], data) > eval_expression(element.args[1], data); } case Parsed::Function::Less: { return eval_expression(element.args[0], data) < eval_expression(element.args[1], data); } case Parsed::Function::GreaterEqual: { return eval_expression(element.args[0], data) >= eval_expression(element.args[1], data); } case Parsed::Function::LessEqual: { return eval_expression(element.args[0], data) <= eval_expression(element.args[1], data); } case Parsed::Function::Different: { return eval_expression(element.args[0], data) != eval_expression(element.args[1], data); } case Parsed::Function::Float: { return std::stod(eval_expression(element.args[0], data)); } case Parsed::Function::Int: { return std::stoi(eval_expression(element.args[0], data)); } case Parsed::Function::ReadJson: { try { return data.at(json::json_pointer(element.command)); } catch (std::exception&) { inja_throw("render_error", "variable '" + element.command + "' not found"); } } case Parsed::Function::Result: { return element.result; } case Parsed::Function::Default: { try { return eval_expression(element.args[0], data); } catch (std::exception&) { return eval_expression(element.args[1], data); } } case Parsed::Function::Callback: { Parsed::CallbackSignature signature = std::make_pair(element.command, element.args.size()); return map_callbacks.at(signature)(element.args, data); } case Parsed::Function::Exists: { const std::string name = eval_expression(element.args[0], data); return data.find(name) != data.end(); } case Parsed::Function::ExistsInObject: { const std::string name = eval_expression(element.args[1], data); const json d = eval_expression(element.args[0], data); return d.find(name) != d.end(); } case Parsed::Function::IsBoolean: { const json d = eval_expression(element.args[0], data); return d.is_boolean(); } case Parsed::Function::IsNumber: { const json d = eval_expression(element.args[0], data); return d.is_number(); } case Parsed::Function::IsInteger: { const json d = eval_expression(element.args[0], data); return d.is_number_integer(); } case Parsed::Function::IsFloat: { const json d = eval_expression(element.args[0], data); return d.is_number_float(); } case Parsed::Function::IsObject: { const json d = eval_expression(element.args[0], data); return d.is_object(); } case Parsed::Function::IsArray: { const json d = eval_expression(element.args[0], data); return d.is_array(); } case Parsed::Function::IsString: { const json d = eval_expression(element.args[0], data); return d.is_string(); } } inja_throw("render_error", "unknown function in renderer: " + element.command); return json(); } std::string render(Template temp, const json& data) { std::string result {""}; for (const auto& element: temp.parsed_template().children) { switch (element->type) { case Parsed::Type::String: { auto element_string = std::static_pointer_cast(element); result.append(element_string->text); break; } case Parsed::Type::Expression: { auto element_expression = std::static_pointer_cast(element); const json variable = eval_expression(*element_expression, data); if (variable.is_string()) { result.append( variable.get() ); } else { std::stringstream ss; ss << variable; result.append( ss.str() ); } break; } case Parsed::Type::Loop: { auto element_loop = std::static_pointer_cast(element); switch (element_loop->loop) { case Parsed::Loop::ForListIn: { const std::vector list = eval_expression>(element_loop->list, data); for (unsigned int i = 0; i < list.size(); i++) { json data_loop = data; /* For nested loops, use parent/index */ if (data_loop.count("loop") == 1) { data_loop["loop"]["parent"] = data_loop["loop"]; } data_loop[element_loop->value] = list[i]; data_loop["loop"]["index"] = i; data_loop["loop"]["index1"] = i + 1; data_loop["loop"]["is_first"] = (i == 0); data_loop["loop"]["is_last"] = (i == list.size() - 1); result.append( render(Template(*element_loop), data_loop) ); } break; } case Parsed::Loop::ForMapIn: { const std::map map = eval_expression>(element_loop->list, data); for (const auto& item: map) { json data_loop = data; data_loop[element_loop->key] = item.first; data_loop[element_loop->value] = item.second; result.append( render(Template(*element_loop), data_loop) ); } break; } } break; } case Parsed::Type::Condition: { auto element_condition = std::static_pointer_cast(element); for (const auto& branch: element_condition->children) { auto element_branch = std::static_pointer_cast(branch); if (element_branch->condition_type == Parsed::Condition::Else || eval_expression(element_branch->condition, data)) { result.append( render(Template(*element_branch), data) ); break; } } break; } default: { break; } } } return result; } }; } #endif // PANTOR_INJA_RENDERER_HPP