#ifndef JINJA2CPP_SRC_TEMPLATE_IMPL_H #define JINJA2CPP_SRC_TEMPLATE_IMPL_H #include "internal_value.h" #include "jinja2cpp/binding/rapid_json.h" #include "jinja2cpp/template_env.h" #include "jinja2cpp/value.h" #include "renderer.h" #include "template_parser.h" #include "value_visitors.h" #include <boost/optional.hpp> #include <boost/predef/other/endian.h> #include <contrib/restricted/expected-lite/include/nonstd/expected.hpp> #include <rapidjson/error/en.h> #include <string> namespace jinja2 { namespace detail { template<size_t Sz> struct RapidJsonEncodingType; template<> struct RapidJsonEncodingType<1> { using type = rapidjson::UTF8<char>; }; #ifdef BOOST_ENDIAN_BIG_BYTE template<> struct RapidJsonEncodingType<2> { using type = rapidjson::UTF16BE<wchar_t>; }; template<> struct RapidJsonEncodingType<4> { using type = rapidjson::UTF32BE<wchar_t>; }; #else template<> struct RapidJsonEncodingType<2> { using type = rapidjson::UTF16LE<wchar_t>; }; template<> struct RapidJsonEncodingType<4> { using type = rapidjson::UTF32LE<wchar_t>; }; #endif } // namespace detail extern void SetupGlobals(InternalValueMap& globalParams); class ITemplateImpl { public: virtual ~ITemplateImpl() = default; }; template<typename U> struct TemplateLoader; template<> struct TemplateLoader<char> { static auto Load(const std::string& fileName, TemplateEnv* env) { return env->LoadTemplate(fileName); } }; template<> struct TemplateLoader<wchar_t> { static auto Load(const std::string& fileName, TemplateEnv* env) { return env->LoadTemplateW(fileName); } }; template<typename CharT> class GenericStreamWriter : public OutStream::StreamWriter { public: explicit GenericStreamWriter(std::basic_string<CharT>& os) : m_os(os) {} // StreamWriter interface void WriteBuffer(const void* ptr, size_t length) override { m_os.append(reinterpret_cast<const CharT*>(ptr), length); } void WriteValue(const InternalValue& val) override { Apply<visitors::ValueRenderer<CharT>>(val, m_os); } private: std::basic_string<CharT>& m_os; }; template<typename CharT> class StringStreamWriter : public OutStream::StreamWriter { public: explicit StringStreamWriter(std::basic_string<CharT>* targetStr) : m_targetStr(targetStr) {} // StreamWriter interface void WriteBuffer(const void* ptr, size_t length) override { m_targetStr->append(reinterpret_cast<const CharT*>(ptr), length); // m_os.write(reinterpret_cast<const CharT*>(ptr), length); } void WriteValue(const InternalValue& val) override { Apply<visitors::ValueRenderer<CharT>>(val, *m_targetStr); } private: std::basic_string<CharT>* m_targetStr; }; template<typename ErrorTpl1, typename ErrorTpl2> struct ErrorConverter; template<typename CharT1, typename CharT2> struct ErrorConverter<ErrorInfoTpl<CharT1>, ErrorInfoTpl<CharT2>> { static ErrorInfoTpl<CharT1> Convert(const ErrorInfoTpl<CharT2>& srcError) { typename ErrorInfoTpl<CharT1>::Data errorData; errorData.code = srcError.GetCode(); errorData.srcLoc = srcError.GetErrorLocation(); errorData.locationDescr = ConvertString<std::basic_string<CharT1>>(srcError.GetLocationDescr()); errorData.extraParams = srcError.GetExtraParams(); return ErrorInfoTpl<CharT1>(errorData); } }; template<typename CharT> struct ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<CharT>> { static const ErrorInfoTpl<CharT>& Convert(const ErrorInfoTpl<CharT>& srcError) { return srcError; } }; template<typename CharT> inline bool operator==(const MetadataInfo<CharT>& lhs, const MetadataInfo<CharT>& rhs) { if (lhs.metadata != rhs.metadata) return false; if (lhs.metadataType != rhs.metadataType) return false; if (lhs.location != rhs.location) return false; return true; } template<typename CharT> inline bool operator!=(const MetadataInfo<CharT>& lhs, const MetadataInfo<CharT>& rhs) { return !(lhs == rhs); } inline bool operator==(const TemplateEnv& lhs, const TemplateEnv& rhs) { return lhs.IsEqual(rhs); } inline bool operator!=(const TemplateEnv& lhs, const TemplateEnv& rhs) { return !(lhs == rhs); } inline bool operator==(const SourceLocation& lhs, const SourceLocation& rhs) { if (lhs.fileName != rhs.fileName) return false; if (lhs.line != rhs.line) return false; if (lhs.col != rhs.col) return false; return true; } inline bool operator!=(const SourceLocation& lhs, const SourceLocation& rhs) { return !(lhs == rhs); } template<typename CharT> class TemplateImpl : public ITemplateImpl { public: using ThisType = TemplateImpl<CharT>; explicit TemplateImpl(TemplateEnv* env) : m_env(env) { if (env) m_settings = env->GetSettings(); } auto GetRenderer() const {return m_renderer;} auto GetTemplateName() const {}; boost::optional<ErrorInfoTpl<CharT>> Load(std::basic_string<CharT> tpl, std::string tplName) { m_template = std::move(tpl); m_templateName = tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName); TemplateParser<CharT> parser(&m_template, m_settings, m_env, m_templateName); auto parseResult = parser.Parse(); if (!parseResult) return parseResult.error()[0]; m_renderer = *parseResult; m_metadataInfo = parser.GetMetadataInfo(); return boost::optional<ErrorInfoTpl<CharT>>(); } boost::optional<ErrorInfoTpl<CharT>> Render(std::basic_string<CharT>& os, const ValuesMap& params) { boost::optional<ErrorInfoTpl<CharT>> normalResult; if (!m_renderer) { typename ErrorInfoTpl<CharT>::Data errorData; errorData.code = ErrorCode::TemplateNotParsed; errorData.srcLoc.col = 1; errorData.srcLoc.line = 1; errorData.srcLoc.fileName = "<unknown file>"; return ErrorInfoTpl<CharT>(errorData); } try { InternalValueMap extParams; InternalValueMap intParams; auto convertFn = [&intParams](const auto& params) { for (auto& ip : params) { auto valRef = &ip.second.data(); auto newParam = visit(visitors::InputValueConvertor(false, true), *valRef); if (!newParam) intParams[ip.first] = ValueRef(static_cast<const Value&>(*valRef)); else intParams[ip.first] = newParam.get(); } }; if (m_env) { m_env->ApplyGlobals(convertFn); std::swap(extParams, intParams); } convertFn(params); SetupGlobals(extParams); RendererCallback callback(this); RenderContext context(intParams, extParams, &callback); InitRenderContext(context); OutStream outStream([writer = GenericStreamWriter<CharT>(os)]() mutable -> OutStream::StreamWriter* {return &writer;}); m_renderer->Render(outStream, context); } catch (const ErrorInfoTpl<char>& error) { return ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<char>>::Convert(error); } catch (const ErrorInfoTpl<wchar_t>& error) { return ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<wchar_t>>::Convert(error); } catch (const std::exception& ex) { typename ErrorInfoTpl<CharT>::Data errorData; errorData.code = ErrorCode::UnexpectedException; errorData.srcLoc.col = 1; errorData.srcLoc.line = 1; errorData.srcLoc.fileName = m_templateName; errorData.extraParams.push_back(Value(std::string(ex.what()))); return ErrorInfoTpl<CharT>(errorData); } return normalResult; } InternalValueMap& InitRenderContext(RenderContext& context) { auto& curScope = context.GetCurrentScope(); return curScope; } using TplLoadResultType = std::variant<EmptyValue, nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>, nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>>; using TplOrError = nonstd::expected<std::shared_ptr<TemplateImpl<CharT>>, ErrorInfoTpl<CharT>>; TplLoadResultType LoadTemplate(const std::string& fileName) { if (!m_env) return TplLoadResultType(EmptyValue()); auto tplWrapper = TemplateLoader<CharT>::Load(fileName, m_env); if (!tplWrapper) return TplLoadResultType(TplOrError(tplWrapper.get_unexpected())); return TplLoadResultType(TplOrError(std::static_pointer_cast<ThisType>(tplWrapper.value().m_impl))); } TplLoadResultType LoadTemplate(const InternalValue& fileName) { auto name = GetAsSameString(std::string(), fileName); if (!name) { typename ErrorInfoTpl<CharT>::Data errorData; errorData.code = ErrorCode::InvalidTemplateName; errorData.srcLoc.col = 1; errorData.srcLoc.line = 1; errorData.srcLoc.fileName = m_templateName; errorData.extraParams.push_back(IntValue2Value(fileName)); return TplOrError(nonstd::make_unexpected(ErrorInfoTpl<CharT>(errorData))); } return LoadTemplate(name.value()); } nonstd::expected<GenericMap, ErrorInfoTpl<CharT>> GetMetadata() const { auto& metadataString = m_metadataInfo.metadata; if (metadataString.empty()) return GenericMap(); if (m_metadataInfo.metadataType == "json") { m_metadataJson = JsonDocumentType(); rapidjson::ParseResult res = m_metadataJson.value().Parse(metadataString.data(), metadataString.size()); if (!res) { typename ErrorInfoTpl<CharT>::Data errorData; errorData.code = ErrorCode::MetadataParseError; errorData.srcLoc = m_metadataInfo.location; std::string jsonError = rapidjson::GetParseError_En(res.Code()); errorData.extraParams.push_back(Value(std::move(jsonError))); return nonstd::make_unexpected(ErrorInfoTpl<CharT>(errorData)); } m_metadata = std::move(std::get<GenericMap>(Reflect(m_metadataJson.value()).data())); return m_metadata.value(); } return GenericMap(); } nonstd::expected<MetadataInfo<CharT>, ErrorInfoTpl<CharT>> GetMetadataRaw() const { return m_metadataInfo; } bool operator==(const TemplateImpl<CharT>& other) const { if (m_env && other.m_env) { if (*m_env != *other.m_env) return false; } if (m_settings != other.m_settings) return false; if (m_template != other.m_template) return false; if (m_renderer && other.m_renderer && !m_renderer->IsEqual(*other.m_renderer)) return false; if (m_metadata != other.m_metadata) return false; if (m_metadataJson != other.m_metadataJson) return false; if (m_metadataInfo != other.m_metadataInfo) return false; return true; } private: void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) { typename ErrorInfoTpl<CharT>::Data errorData; errorData.code = code; errorData.srcLoc.col = 1; errorData.srcLoc.line = 1; errorData.srcLoc.fileName = m_templateName; errorData.extraParams = std::move(extraParams); throw ErrorInfoTpl<CharT>(std::move(errorData)); } class RendererCallback : public IRendererCallback { public: explicit RendererCallback(ThisType* host) : m_host(host) {} TargetString GetAsTargetString(const InternalValue& val) override { std::basic_string<CharT> os; Apply<visitors::ValueRenderer<CharT>>(val, os); return TargetString(std::move(os)); } OutStream GetStreamOnString(TargetString& str) override { using string_t = std::basic_string<CharT>; str = string_t(); return OutStream([writer = StringStreamWriter<CharT>(&std::get<string_t>(str))]() mutable -> OutStream::StreamWriter* { return &writer; }); } std::variant<EmptyValue, nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>, nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const override { return m_host->LoadTemplate(fileName); } std::variant<EmptyValue, nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>, nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const override { return m_host->LoadTemplate(fileName); } void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) override { m_host->ThrowRuntimeError(code, std::move(extraParams)); } bool IsEqual(const IComparable& other) const override { auto* callback = dynamic_cast<const RendererCallback*>(&other); if (!callback) return false; if (m_host && callback->m_host) return *m_host == *(callback->m_host); if ((!m_host && (callback->m_host)) || (m_host && !(callback->m_host))) return false; return true; } bool operator==(const IComparable& other) const { auto* callback = dynamic_cast<const RendererCallback*>(&other); if (!callback) return false; if (m_host && callback->m_host) return *m_host == *(callback->m_host); if ((!m_host && (callback->m_host)) || (m_host && !(callback->m_host))) return false; return true; } private: ThisType* m_host; }; private: using JsonDocumentType = rapidjson::GenericDocument<typename detail::RapidJsonEncodingType<sizeof(CharT)>::type>; TemplateEnv* m_env{}; Settings m_settings; std::basic_string<CharT> m_template; std::string m_templateName; RendererPtr m_renderer; mutable std::optional<GenericMap> m_metadata; mutable std::optional<JsonDocumentType> m_metadataJson; MetadataInfo<CharT> m_metadataInfo; }; } // namespace jinja2 #endif // JINJA2CPP_SRC_TEMPLATE_IMPL_H