Mercurial > thymian
diff templates/templater.h @ 0:a4671277546c tip
created the repository for the thymian project
| author | ferencd |
|---|---|
| date | Tue, 17 Aug 2021 11:19:54 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/templater.h Tue Aug 17 11:19:54 2021 +0200 @@ -0,0 +1,685 @@ +#ifndef TEMPLATER_H +#define TEMPLATER_H + +#include <common.h> +#include <named_operator.hpp> +#include <json.h> +#include "template_struct.h" +#include <boost/noncopyable.hpp> + +#include <string> +#include <iostream> +#include <vector> +#include <set> +#include <map> +#include <sstream> +#include <string> +#include <map> +#include <fstream> +#include <memory> + +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +static const std::string TEMPLATES_DIRECTORY = "theme/current/"; + +static const std::string TEMPLATE_VARIABLE_START_DELIMITER = "{#"; +static const std::string TEMPLATE_VARIABLE_END_DELIMITER = "}"; + +static const std::string INCLUDE_TAG = "<!--#include"; +static const std::string TRANSLATE_TAG = "<!--#translate"; +static const std::string IF_TAG = "<!--#if"; +static const std::string IFEQ_TAG = "<!--#eq"; +static const std::string ELSE_TAG = "<!--#else"; +static const std::string ENDIF_TAG = "<!--#endif"; +static const std::string ENDEQ_TAG = "<!--#endeq"; +static const std::string STRUCT_TAG = "<!--#struct"; +static const std::string PARAMETERS_TAG = "<!--#parameters"; +static const std::string LOOP_TAG = "<!--#loop"; +static const std::string ENDLOOP_TAG = "<!--#endloop"; +static const std::string ENDTRANSLATE_TAG = "<!--#endtranslate"; +static const std::string DEFINE_TAG = "<!--#define"; + +// difference between script and inline script: inline-script is execute BEFORE the variable replacements, thus +// it has the chance to modify the variables +static const std::string SCRIPT_TAG = "<!--#script"; +static const std::string INIT_SCRIPT_TAG = "<!--#init-script"; +static const std::string ENDSCRIPT_TAG = "<!--#endscript"; + +/** + * @brief The template_base class is the basic class for all the templates + */ +class template_base +{ +public: + virtual ~template_base() = default; + virtual std::string content() const = 0; + virtual std::string base_dir() const = 0; + virtual bool check() const = 0; +}; + +/** + * @brief The file_template class is a class for a template that resides in a file + */ +class file_template : public template_base +{ +public: + file_template() = default; + + virtual std::string fileName() const = 0; + std::string base_dir() const override; + virtual std::string content() const; + virtual bool check() const; +}; + +/** + * @brief The template_warehouse class is the class that keeps track of the templates that were registered + */ +class template_warehouse : public boost::noncopyable +{ +public: + static template_warehouse& instance(); + + std::string getTemplateContent(const std::string& templateName); + bool registerTemplate(const std::string& name, template_base *templateClass); + bool checkTemplates(); + +private: + template_warehouse() = default; + std::map <std::string, std::shared_ptr<template_base>> templates; +}; + +#define GENERIC_TEMPLATE(Class, Type) \ +class Class : public file_template \ +{ \ +public: \ + Class() = default; \ + static std::string name() {return std::string(#Class); } \ + virtual std::string fileName() const {return std::string(#Class) + "." + #Type; } \ +}; \ +[[maybe_unused]] static bool dummy##Class##Type = template_warehouse::instance().registerTemplate(#Class, new Class) + +#define GENERIC_FILE_TEMPLATE(Class, Filename) \ +class Class : public file_template \ +{ \ +public: \ + Class() = default; \ + static std::string name() {return std::string(#Class); } \ + virtual std::string fileName() const {return Filename ; } \ +}; \ +[[maybe_unused]] static bool dummy##Class##Type = template_warehouse::instance().registerTemplate(#Class, new Class) + + +#define CSS_TEMPLATE(Class) GENERIC_TEMPLATE(Class, css) +#define JS_TEMPLATE(Class) GENERIC_TEMPLATE(Class, js) +#define TXT_TEMPLATE(Class) GENERIC_TEMPLATE(Class, txt) +#define HTML_TEMPLATE(Class) GENERIC_TEMPLATE(Class, html) + +#if (defined(__GNUC__) || defined(__GNUG__)) && !(defined(__clang__)) + #define REG_VAR(Class) [[maybe_unused]] static bool dummy##Class##Type __attribute__((unused)) = template_warehouse::instance().registerTemplate(#Class, new Class) +#else + #define REG_VAR(Class) [[gnu::unused]] static bool dummy##Class##Type = template_warehouse::instance().registerTemplate(#Class, new Class) +#endif + +#define STRING_TEMPLATE(Class, s) \ +class Class : public template_base \ +{ \ +public: \ + Class() : m_content(s) {} \ + virtual ~Class() = default; \ + static std::string name() {return std::string(#Class); } \ + std::string base_dir() const override { return TEMPLATES_DIRECTORY; } \ + virtual std::string content() const override \ + { \ + return m_content; \ + } \ + virtual bool check() const override { return true; } \ +private: \ + std::string m_content; \ +}; \ +REG_VAR(Class) + +#define STRING_VAR_TEMPLATE(Class, s) \ +static std::string _TS_ ## _Class = s; \ +class Class : public template_base \ +{ \ +public: \ + Class() : m_content(_TS_ ## _Class) {} \ + virtual ~Class() = default; \ + static std::string name() {return std::string(#Class); } \ + std::string base_dir() const override { return TEMPLATES_DIRECTORY; } \ + virtual std::string content() const \ + { \ + return m_content; \ + } \ + virtual bool check() const { return true; } \ +private: \ + std::string m_content; \ +}; \ +REG_VAR(Class) + +#define GENERIC_STRING_TEMPLATE(Class) \ +class Class : public template_base \ +{ \ +public: \ + Class() : template_base() {} \ + virtual ~Class() = default; \ + static std::string name() {return std::string(#Class); } \ + std::string base_dir() const override { return TEMPLATES_DIRECTORY; } \ + virtual std::string content() const override \ + { \ + return m_content; \ + } \ + virtual bool check() const override { return true; } \ +private: \ + std::string m_content; \ +}; \ +REG_VAR(Class) + + +/** + * This class is the base for the template pairs of (key, value) that can be inserted in the tmeplate + **/ +class template_pair_base +{ +public: + virtual ~template_pair_base() = default; +}; + +class template_special_parameter +{ +public: + template_special_parameter() = default; + virtual ~template_special_parameter() = default; + + template_special_parameter(const std::string& pname, + const std::string& ptype, + bool piterable) : + iterable(piterable), name(pname), type(ptype) {} + +private: + bool iterable; + std::string name; + std::string type; +}; + + +/** + * This is a wrapper class for a string that is used to do the actual replacing of strings + * in the context of a template + **/ +class stringholder +{ +public: + + stringholder(const std::string& d) : str(d) + {} + + void replace_all(const std::string& from, const std::string& to); + + std::string get() const + { + return str; + } + + stringholder& templatize(const std::map<std::string, std::string>& m); + +private: + + std::string str; +}; + +/** + * The template parameter class, holds a classical template parameter + **/ +template <class T> +class template_par : public template_pair_base +{ +public: + + template_par(const std::string& name, const T& value) : m_key(name), m_value(unafrog::utils::to_string(value)) + { + } + + template_par(const char* name, const T& value) : template_par(std::string(name), value) + { + } + + std::string value() const + { + return m_value; + } + + std::string key() const + { + return m_key; + } + + void set_value(const std::string& v) + { + m_value = v; + } + +private: + template_pair_base* child; + std::string m_key; + std::string m_value; +}; + +class template_vector_par : public template_pair_base +{ +public: + template_vector_par() = default; + + template_vector_par(const char* name, const std::vector<template_struct>& value) : mname(name), mvalue(value) + { + } + + template_vector_par(const std::string& name, const std::vector<template_struct>& value) : mname(name), mvalue(value) + { + } + + const std::vector<template_struct>& value() const + { + return mvalue; + } + + std::string name() const + { + return mname; + } + + auto begin() { return mvalue.begin(); } + auto end() { return mvalue.end(); } + std::vector<template_struct>::const_iterator begin() const { return mvalue.begin(); } + std::vector<template_struct>::const_iterator end() const { return mvalue.end(); } + +private: + std::string mname = ""; + std::vector<template_struct> mvalue = {}; +}; + +namespace op +{ + struct is + { + template <typename T> + template_par<T> operator ()(std::string const& vs, T const& v) const + { + return template_par<T>(vs, v); + } + + }; +} +[[maybe_unused]] static auto is = base::make_named_operator(op::is()); + +/** + * @brief The templater_base class is the basic class for a templater engine + */ +class templater_base +{ + +public: + virtual ~templater_base() = default; + + std::map<std::string, std::string> pairs() const + { + return kps; + } + + void add_structure_decl(const std::string& name, const std::string& type) + { + structures[name] = template_struct(name, type); + } + + template_struct& get_structure(const std::string& name) + { + return structures[name]; + } + + void add_parameter(const std::string& name, const std::string& type, bool iterable = false) + { + m_parameters[name] = {name, type, iterable}; + } + + templater_base& templatize(const template_struct& s); + + templater_base& templatize(const template_vector_par& v); + + virtual std::string get() = 0; + virtual void do_not_resolve_in_get() = 0; + virtual void do_resolve_in_get() = 0; + virtual std::string name() const = 0; + + void skip_whitespace(const std::string &templatized, size_t &i); + std::string extract_identifier_word(const std::string &input, size_t &i, std::vector<char> separators = {}, + std::set<char> extra_allowed_chars = {}, char &&c = 0); + bool check_opening_parenthesis(const std::string &input, size_t &i); + bool check_closing_comment(const std::string &templatized, size_t &i, std::size_t &include_tag_end_position); + + template <typename... Args> + void set_error(Args&&... args) + { + std::string terr = unafrog::utils::join_string(' ', std::forward<Args>(args)...); + error += unafrog::utils::trim(terr) + "\n"; + + } + + std::string get_error() const; + + /** + * @brief variables will return all the variables that can be replaced in the template + * @return + */ + std::vector<std::string> variables(bool resolve_includes_too); + + +protected: + + std::string get(const std::string& template_name); + void resolve_all_includes(std::string &templatized, bool do_replace = true); + + /** + * @brief resolve_includes Resolves the template including other templates. + * + * The included template name can have parameters too: <!--#include IncludeableTemplate(str="blaa")#-->" meaning + * that when the IncludeableTemplate is included its variables (specified in the list only) are exchhanged to the + * ones with values from the parameters. + * + * @param templatized - the actual content of the template string + * @param inc_pos - the start position + * @param do_variable_replacement - whether wer should replace the variables after including + * + * @return the templated string, where the includes were resolved and eventually the variables were replaced + */ + std::string resolve_includes(std::string templatized, size_t inc_pos, bool do_variable_replacement = true); + + std::string resolve_structure_declaration(size_t struct_pos, std::string templatized); + std::string resolve_parameters(size_t parameters_pos, std::string templatized); + std::string resolve_ifs(size_t if_pos, std::string templatized); + std::string resolve_ifeq(size_t if_pos, std::string templatized); + std::string resolve_defines(std::string content); + std::string resolve_script(size_t pos, const std::string &content, const std::string& tag); + std::string resolve_scripts(std::string templatized, const std::string& tag); + std::string resolve_loops(std::string templatized, const template_vector_par &v); + std::string resolve_ifeqs(std::string templatized); + + /** + * @brief resolve_translations resolves all the translations + * @param templatized + * @param target_language + * @param generate_span - whether to generate HTML spans for the translated elements + * @param translations - a map with the span ID's and their corresponding translations + * @return + */ + std::string resolve_translations(std::string templatized, const std::string& target_language, bool generate_span, std::map<std::string, std::map<std::string, std::string> > &translations); + + /** + * @brief resolve_translation resolves one translation + * @param if_pos + * @param templatized + * @param target_language + * @param generate_span - whether to generate a span HTML element for this translation + * @param span_id - this will be the ID of the span, generated for this translation, used by the javascript at a later stage to set the language upon change + * @param translations - a map of translations for this given translation, for each language (key) the corresponding translated text (value) + * @return + */ + std::string resolve_translation(size_t if_pos, std::string templatized, const std::string& target_language, bool generate_span, std::string& span_id, std::map<std::string, std::string> &translations); + std::string resolve_dynamic_section(std::string templatized, const template_vector_par &v); + +protected: + + // the string representation of the key/value pairs of the template that will be worked with + std::map<std::string, std::string> kps; + + // the structures that are defined in this template are populated from this map of key/structure paris + std::map<std::string, template_struct> structures; + + // the parameters of te template + std::map<std::string, template_special_parameter> m_parameters; + + // working data + std::string precalculated = ""; + std::string error = ""; +}; + +template<class T> +class templater : public templater_base +{ +protected: + bool resolve_in_get = true; + bool external_content_set = false; + std::string external_content = ""; + std::map<std::string, std::map<std::string, std::string> > m_translation_map; +public: + + const std::map<std::string, std::map<std::string, std::string> >& get_translations() + { + return m_translation_map; + } + + templater& templatize() + { + return *this; + } + + templater& templatize(const template_vector_par& v) + { + return dynamic_cast<templater&>(templater_base::templatize(v)); + } + + template <typename T1> + templater& templatize(const template_par<T1>& b) + { + precalculated = ""; + kps.insert(make_pair(b.key(), unafrog::utils::to_string(b.value()))); + + return *this; + } + + templater& templatize(const nlohmann::json& j) + { + precalculated = ""; + for (const auto& [k, v] : j.items()) + { + if(v.is_string()) + { + std::string s = unafrog::utils::to_string(v); + remove_quotes(s); + kps.insert(make_pair(unafrog::utils::to_string(k), s)); + } + } + return *this; + } + + templater& templatize(const template_struct& s) + { + return dynamic_cast<templater&>(templater_base::templatize(s)); + } + + template<typename... Args1> + templater& templatize(const template_struct& s, Args1... args) + { + precalculated = ""; + templatize(s); + return templatize(args...); + } + + template<typename... Args1> + templater& templatize(const template_vector_par& v, Args1... args) + { + dynamic_cast<templater&>(templater_base::templatize(v)); + return templatize(args...); + } + + template<typename T1, typename... Args1> + templater& templatize(const template_par<T1>& first, Args1... args) + { + precalculated = ""; + kps.insert(make_pair(first.key(), unafrog::utils::to_string(first.value()))); + return templatize(args...); + } + + void do_not_resolve_in_get() override + { + resolve_in_get = false; + } + void do_resolve_in_get() override + { + resolve_in_get = true; + } + + templater& set(const std::string& ec) + { + external_content = ec; + external_content_set = true; + return *this; + } + + std::string get() override + { + if(precalculated.empty()) + { + std::string templatized = (external_content_set && external_content.size() > 0) ? external_content : templater_base::get(name()); + if(resolve_in_get) + { + templatized = resolve_ifeqs(templatized); + templatized = resolve_scripts(templatized, SCRIPT_TAG); + precalculated = templatized; + } + else + { + precalculated = templatized; + } + + if(kps.count("target_language")) + { + precalculated = resolve_translations(precalculated, kps["target_language"], true, m_translation_map); + } + + return precalculated; + } + else + { + return precalculated; + } + } + + std::string name() const override + { + return T::name(); + } + + +}; + +template <class T> +std::ostream& operator << (std::ostream& os, const templater<T>& t) +{ + os << t.get(); + return os; +} + + +template<class T> +struct translator : public templater<T> +{ + translator& templatize() + { + return *this; + } + + translator& templatize(const template_vector_par& v) + { + return dynamic_cast<translator&>(templater_base::templatize(v)); + } + + template <typename T1> + translator& templatize(const template_par<T1>& b) + { + templater<T>::precalculated = ""; + templater<T>::kps.insert(make_pair(b.key(), unafrog::utils::to_string(b.value()))); + + return *this; + } + + translator& templatize(const nlohmann::json& j) + { + templater<T>::precalculated = ""; + for (const auto& [k, v] : j.items()) + { + if(v.is_string()) + { + std::string s = unafrog::utils::to_string(v); + remove_quotes(s); + templater<T>::kps.insert(make_pair(unafrog::utils::to_string(k), s)); + } + } + return *this; + } + + translator& templatize(const template_struct& s) + { + return dynamic_cast<translator&>(templater_base::templatize(s)); + } + + template<typename... Args1> + translator& templatize(const template_struct& s, Args1... args) + { + templater<T>::precalculated = ""; + templatize(s); + return templatize(args...); + } + + template<typename T1, typename... Args1> + translator& templatize(const template_par<T1>& first, Args1... args) + { + templater<T>::precalculated = ""; + templater<T>::kps.insert(make_pair(first.key(), unafrog::utils::to_string(first.value()))); + return templatize(args...); + } + + translator& set() + { + templater<T>::get(); + templater<T>::external_content = templater<T>::precalculated; + templater<T>::external_content_set = true; + return *this; + } + + + std::string translate(const std::string& target_language) + { + if(templater<T>::external_content_set && templater<T>::external_content.length() > 0) + { + return translate(templater<T>::external_content, target_language, this->m_translation_map); + } + else + { + std::string s = templater<T>().templatize().get(); + return translate(s, target_language, this->m_translation_map); + } + } + + static std::string translate(const std::string& in, const std::string& target_language, std::map<std::string, std::map<std::string, std::string> >& translation_map) + { + //TODO mutex + GENERIC_STRING_TEMPLATE(Translateable); + auto t = templater<Translateable>(); + std::string res = t.set(in).templatize("target_language" <is> target_language).get(); + translation_map = t.get_translations(); + return res; + } +}; + +HTML_TEMPLATE(mainpage); +HTML_TEMPLATE(category_list); +HTML_TEMPLATE(recipe); +HTML_TEMPLATE(footer); +HTML_TEMPLATE(languages); + +#endif // TEMPLATER_H
