ferencd@0: #include "templater.h" ferencd@0: #include "dictionary.h" ferencd@0: ferencd@0: #ifdef PYTHON_SCRIPTING ferencd@0: #include "python_runner.h" ferencd@0: #endif ferencd@0: ferencd@0: #include ferencd@0: #include ferencd@0: ferencd@0: #include ferencd@0: #include ferencd@0: ferencd@0: ferencd@0: void templater_base::resolve_all_includes(std::string& templatized, bool do_replace) ferencd@0: { ferencd@0: size_t inc_pos = templatized.find(INCLUDE_TAG); ferencd@0: while(inc_pos != std::string::npos) ferencd@0: { ferencd@0: templatized = resolve_includes(templatized, inc_pos, do_replace); ferencd@0: ferencd@0: // see if we have pulled in some extra vars ferencd@0: if(do_replace) ferencd@0: { ferencd@0: templatized = stringholder(templatized).templatize(kps).get(); ferencd@0: } ferencd@0: ferencd@0: inc_pos = templatized.find(INCLUDE_TAG); ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: std::string templater_base::get(const std::string &template_name) ferencd@0: { ferencd@0: // Getting the content ferencd@0: std::string content = template_warehouse::instance().getTemplateContent(template_name); ferencd@0: ferencd@0: // Resolve the #define's. This will update the extra_kp's ferencd@0: std::string content_without_vars = resolve_defines(content); ferencd@0: ferencd@0: // resolve the initializer scripts, since they may modify the kps ferencd@0: std::string scripts_resolved = resolve_scripts(content_without_vars, INIT_SCRIPT_TAG); ferencd@0: ferencd@0: // resolve all the top level template arguments ferencd@0: std::string templatized = stringholder(scripts_resolved).templatize(kps).get(); ferencd@0: ferencd@0: // then search for all the {#include 's and: templatize those with the given variable: Value pairs ferencd@0: // and include them here ferencd@0: bool done = false; ferencd@0: while(!done) ferencd@0: { ferencd@0: // search for structure definitions, there can be more than one ferencd@0: size_t struct_pos = templatized.find(STRUCT_TAG); ferencd@0: while(struct_pos != std::string::npos) ferencd@0: { ferencd@0: templatized = resolve_structure_declaration(struct_pos, templatized); ferencd@0: struct_pos = templatized.find(STRUCT_TAG); ferencd@0: } ferencd@0: ferencd@0: // search for input data definition hints ferencd@0: size_t parameters_pos = templatized.find(PARAMETERS_TAG); ferencd@0: if(parameters_pos != std::string::npos) ferencd@0: { ferencd@0: templatized = resolve_parameters(parameters_pos, templatized); ferencd@0: } ferencd@0: ferencd@0: // search for the "include"'s ferencd@0: resolve_all_includes(templatized); ferencd@0: ferencd@0: // now search for the "if"'s. Remember, ifs cannot have ifs in their body ferencd@0: size_t if_pos = templatized.find(IF_TAG); ferencd@0: if(if_pos != std::string::npos) ferencd@0: { ferencd@0: while(if_pos != std::string::npos) ferencd@0: { ferencd@0: templatized = resolve_ifs(if_pos, templatized); ferencd@0: if_pos = templatized.find(IF_TAG); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: done = true; ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: return templatized; ferencd@0: } ferencd@0: ferencd@0: std::string templater_base::extract_identifier_word(const std::string& input, size_t& i, std::vector separators, ferencd@0: std::set extra_allowed_chars, char&& c) ferencd@0: { ferencd@0: skip_whitespace(input, i); ferencd@0: ferencd@0: std::string result = ""; ferencd@0: while(i < input.length() && (input.at(i) == '_' || ferencd@0: extra_allowed_chars.count(input.at(i)) || ferencd@0: isalnum(input.at(i))) ) ferencd@0: { ferencd@0: if(separators.empty()) ferencd@0: { ferencd@0: result += input.at(i); ferencd@0: i++; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: if(find(separators.begin(), separators.end(), input.at(i)) == separators.end()) ferencd@0: { ferencd@0: result += input.at(i); ferencd@0: i++; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: if(c != 0) ferencd@0: { ferencd@0: c = input[i]; ferencd@0: } ferencd@0: i ++; // skip the actual separator ferencd@0: break; ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: skip_whitespace(input, i); ferencd@0: if(!separators.empty()) ferencd@0: { ferencd@0: if(find(separators.begin(), separators.end(), input.at(i)) != separators.end()) ferencd@0: { ferencd@0: if(c != 0) ferencd@0: { ferencd@0: c = input[i]; ferencd@0: } ferencd@0: i++; // this characater is a separator. Skip it. And the space after ferencd@0: skip_whitespace(input, i); ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: return result; ferencd@0: } ferencd@0: ferencd@0: bool templater_base::check_opening_parenthesis(const std::string& input, size_t& i) ferencd@0: { ferencd@0: bool opening_parentheses = false; ferencd@0: // skip the parenthesis if any ferencd@0: if(i < input.length() && input.at(i) == '(') ferencd@0: { ferencd@0: i ++; ferencd@0: skip_whitespace(input, i); ferencd@0: opening_parentheses = true; ferencd@0: } ferencd@0: ferencd@0: return opening_parentheses; ferencd@0: } ferencd@0: ferencd@0: bool templater_base::check_closing_comment(const std::string& templatized, size_t& i, std::size_t& include_tag_end_position) ferencd@0: { ferencd@0: if(i + 3 < templatized.length()) ferencd@0: { ferencd@0: if(templatized[i+1] == '-' && templatized[i+2] == '-' && templatized[i + 3] == '>') ferencd@0: { ferencd@0: include_tag_end_position = i + 4; ferencd@0: return true; ferencd@0: } ferencd@0: } ferencd@0: return false; ferencd@0: } ferencd@0: ferencd@0: std::string templater_base::get_error() const ferencd@0: { ferencd@0: return error; ferencd@0: } ferencd@0: ferencd@0: std::vector templater_base::variables(bool resolve_includes_too) ferencd@0: { ferencd@0: std::vector result; ferencd@0: ferencd@0: std::string content = template_warehouse::instance().getTemplateContent(name()); ferencd@0: std::string::size_type start_pos = 0; ferencd@0: ferencd@0: if(resolve_includes_too) ferencd@0: { ferencd@0: resolve_all_includes(content, false); ferencd@0: } ferencd@0: ferencd@0: while((start_pos = content.find(TEMPLATE_VARIABLE_START_DELIMITER, start_pos)) != std::string::npos) ferencd@0: { ferencd@0: std::string current_varname = ""; ferencd@0: start_pos += TEMPLATE_VARIABLE_START_DELIMITER.length(); ferencd@0: while(start_pos < content.length() && content[start_pos] != '}') ferencd@0: { ferencd@0: current_varname += content[start_pos++]; ferencd@0: } ferencd@0: if(start_pos == content.length()) ferencd@0: { ferencd@0: set_error("Unterminated variable name"); ferencd@0: return result; ferencd@0: } ferencd@0: result.push_back(current_varname); ferencd@0: start_pos ++; // to skip the closing '}' ferencd@0: } ferencd@0: ferencd@0: return result; ferencd@0: } ferencd@0: ferencd@0: std::string templater_base::resolve_includes(std::string templatized, size_t inc_pos, bool do_variable_replacement) ferencd@0: { ferencd@0: std::map local_pairs; ferencd@0: std::size_t include_tag_end_position = std::string::npos; ferencd@0: std::size_t include_tag_start_position = inc_pos; ferencd@0: ferencd@0: inc_pos += INCLUDE_TAG.length(); ferencd@0: ferencd@0: std::string inc_template = extract_identifier_word(templatized, inc_pos); ferencd@0: bool opening_parentheses = check_opening_parenthesis(templatized, inc_pos); ferencd@0: size_t i = inc_pos; ferencd@0: bool closing_comment_found = false; ferencd@0: ferencd@0: while(i < templatized.length() && ! closing_comment_found) ferencd@0: { ferencd@0: std::string var_name = extract_identifier_word(templatized, i, {':','='}); ferencd@0: if(i == templatized.length() || var_name.empty()) ferencd@0: { ferencd@0: if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment ferencd@0: { ferencd@0: check_closing_comment(templatized, i, include_tag_end_position); ferencd@0: } ferencd@0: ferencd@0: break; ferencd@0: } ferencd@0: ferencd@0: // now read the variable value ferencd@0: std::string var_value = ""; ferencd@0: bool var_value_read = false; ferencd@0: while(!var_value_read && i < templatized.length()) ferencd@0: { ferencd@0: if(templatized[i] == '"') // string starts. Do not read the '"' ferencd@0: { ferencd@0: i++; // skip the '"' ferencd@0: ferencd@0: bool string_read = false; ferencd@0: while(i < templatized.length() && !string_read) ferencd@0: { ferencd@0: if(templatized[i] == '\\') // Skip the backslash from the escape sequence ferencd@0: { ferencd@0: i++; ferencd@0: } ferencd@0: ferencd@0: var_value += templatized[i++]; ferencd@0: ferencd@0: if(i == templatized.length()) ferencd@0: { ferencd@0: set_error("String literal is not finished"); ferencd@0: break; ferencd@0: } ferencd@0: ferencd@0: if(templatized[i] == '"' && templatized[i-1] != '\\') ferencd@0: { ferencd@0: string_read = true; ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: if(i == templatized.length()) // invalid syntax ferencd@0: { ferencd@0: set_error("Invalid syntax, could not resolve an include"); ferencd@0: break; ferencd@0: } ferencd@0: ferencd@0: i++; // skip closing quote ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: // see if this is a closing parenthesis, but only if there was an opening one ferencd@0: if(opening_parentheses && templatized[i] == ')') ferencd@0: { ferencd@0: var_value_read = true; ferencd@0: ferencd@0: // now see if there is a closing comment. Everything after it will be ignored ferencd@0: while(i < templatized.length() && !closing_comment_found) ferencd@0: { ferencd@0: i ++; ferencd@0: skip_whitespace(templatized, i); ferencd@0: if(i < templatized.length() && templatized[i] == '#') // This should be the closing comment ferencd@0: { ferencd@0: closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position); ferencd@0: } ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment ferencd@0: { ferencd@0: closing_comment_found = var_value_read = check_closing_comment(templatized, i, include_tag_end_position); ferencd@0: } ferencd@0: ferencd@0: if(templatized[i] == '{' && i >& translations) ferencd@0: { ferencd@0: bool done = false; ferencd@0: while(!done) ferencd@0: { ferencd@0: size_t ifeq_pos = templatized.find(TRANSLATE_TAG); ferencd@0: if(ifeq_pos != std::string::npos) ferencd@0: { ferencd@0: while(ifeq_pos != std::string::npos) ferencd@0: { ferencd@0: std::map local_translations; ferencd@0: std::string span_id; ferencd@0: ferencd@0: templatized = resolve_translation(ifeq_pos, templatized, target_language, generate_span, span_id, local_translations); ferencd@0: ferencd@0: translations[span_id] = local_translations; ferencd@0: ferencd@0: ifeq_pos = templatized.find(TRANSLATE_TAG); ferencd@0: } ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: done = true; ferencd@0: } ferencd@0: } ferencd@0: return templatized; ferencd@0: ferencd@0: } ferencd@0: ferencd@0: static std::string span_name(std::string str) ferencd@0: { ferencd@0: str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); ferencd@0: return "span_" + str; ferencd@0: } ferencd@0: ferencd@0: std::string templater_base::resolve_translation(size_t tr_pos, std::string templatized, const std::string &target_language, bool generate_span, std::string& span_id, std::map &translations) ferencd@0: { ferencd@0: size_t i = tr_pos; ferencd@0: ferencd@0: // skip the is the 4 ferencd@0: content.begin() + endscript_start_pos); ferencd@0: std::string after_script = content.substr(endscript_start_pos + ENDSCRIPT_TAG.length() + 4); ferencd@0: ferencd@0: std::string result = python_runner().run(kps, between, variables(true)); ferencd@0: ferencd@0: ferencd@0: return before_script + unafrog::utils::trim(result) + after_script; ferencd@0: } ferencd@0: ferencd@0: void stringholder::replace_all(const std::string &from, const std::string &to) ferencd@0: { ferencd@0: if(from.empty()) ferencd@0: { ferencd@0: return; ferencd@0: } ferencd@0: size_t start_pos = 0; ferencd@0: while((start_pos = str.find(from, start_pos)) != std::string::npos) ferencd@0: { ferencd@0: str.replace(start_pos, from.length(), to); ferencd@0: start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: stringholder &stringholder::templatize(const std::map &m) ferencd@0: { ferencd@0: for(auto it = m.begin(); it != m.end(); ++ it) ferencd@0: { ferencd@0: replace_all(TEMPLATE_VARIABLE_START_DELIMITER + it->first + TEMPLATE_VARIABLE_END_DELIMITER, it->second ); ferencd@0: } ferencd@0: return *this; ferencd@0: } ferencd@0: ferencd@0: template_warehouse &template_warehouse::instance() ferencd@0: { ferencd@0: static template_warehouse twh; ferencd@0: return twh; ferencd@0: } ferencd@0: ferencd@0: bool template_warehouse::checkTemplates() ferencd@0: { ferencd@0: for(auto it = templates.begin(); it != templates.end(); ++it) ferencd@0: { ferencd@0: if(!it->second->check()) ferencd@0: { ferencd@0: return false; ferencd@0: } ferencd@0: } ferencd@0: debug() << "All templates OK"; ferencd@0: return true; ferencd@0: } ferencd@0: ferencd@0: std::string template_warehouse::getTemplateContent(const std::string &templateName) ferencd@0: { ferencd@0: if(templates.count(templateName)) ferencd@0: { ferencd@0: std::shared_ptr tb = templates[templateName]; ferencd@0: std::string c = tb->content(); ferencd@0: return c; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: return "Not found:" + templateName; ferencd@0: } ferencd@0: } ferencd@0: ferencd@0: bool template_warehouse::registerTemplate(const std::string &name, template_base *templateClass) ferencd@0: { ferencd@0: // although this code seems stupid, it is due to the fact that every inclusion of the ferencd@0: // templater.h will cause a new object to be created, but we want only one ferencd@0: if(templates.count(name)) ferencd@0: { ferencd@0: debug() << "Freeing existing template class:" << name; ferencd@0: delete templateClass; ferencd@0: return false; ferencd@0: } ferencd@0: debug() << "Registering template class:" << name; ferencd@0: templates[name] = std::shared_ptr(templateClass); ferencd@0: return true; ferencd@0: } ferencd@0: ferencd@0: ferencd@0: std::string file_template::base_dir() const ferencd@0: { ferencd@0: namespace fs = std::filesystem; ferencd@0: if(fs::is_regular_file(fileName())) ferencd@0: { ferencd@0: return ""; ferencd@0: } ferencd@0: else ferencd@0: { ferencd@0: return TEMPLATES_DIRECTORY; ferencd@0: } ferencd@0: ferencd@0: } ferencd@0: ferencd@0: std::string file_template::content() const ferencd@0: { ferencd@0: std::string fn = base_dir() + fileName(); ferencd@0: std::ifstream ifs(fn); ferencd@0: std::string s( (std::istreambuf_iterator(ifs) ), ferencd@0: (std::istreambuf_iterator()) ); ferencd@0: return s; ferencd@0: } ferencd@0: ferencd@0: bool file_template::check() const ferencd@0: { ferencd@0: struct stat buffer; ferencd@0: bool res = (stat ( (base_dir() + fileName()).c_str(), &buffer) == 0); ferencd@0: if(!res) ferencd@0: { ferencd@0: debug() << "Check for "<< fileName() << " failed"; ferencd@0: } ferencd@0: return res; ferencd@0: } ferencd@0: ferencd@0: ferencd@0: