comparison templates/templater.cpp @ 0:a4671277546c tip

created the repository for the thymian project
author ferencd
date Tue, 17 Aug 2021 11:19:54 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:a4671277546c
1 #include "templater.h"
2 #include "dictionary.h"
3
4 #ifdef PYTHON_SCRIPTING
5 #include "python_runner.h"
6 #endif
7
8 #include <algorithm>
9 #include <filesystem>
10
11 #include <log.h>
12 #include <cwctype>
13
14
15 void templater_base::resolve_all_includes(std::string& templatized, bool do_replace)
16 {
17 size_t inc_pos = templatized.find(INCLUDE_TAG);
18 while(inc_pos != std::string::npos)
19 {
20 templatized = resolve_includes(templatized, inc_pos, do_replace);
21
22 // see if we have pulled in some extra vars
23 if(do_replace)
24 {
25 templatized = stringholder(templatized).templatize(kps).get();
26 }
27
28 inc_pos = templatized.find(INCLUDE_TAG);
29 }
30 }
31
32 std::string templater_base::get(const std::string &template_name)
33 {
34 // Getting the content
35 std::string content = template_warehouse::instance().getTemplateContent(template_name);
36
37 // Resolve the #define's. This will update the extra_kp's
38 std::string content_without_vars = resolve_defines(content);
39
40 // resolve the initializer scripts, since they may modify the kps
41 std::string scripts_resolved = resolve_scripts(content_without_vars, INIT_SCRIPT_TAG);
42
43 // resolve all the top level template arguments
44 std::string templatized = stringholder(scripts_resolved).templatize(kps).get();
45
46 // then search for all the {#include 's and: templatize those with the given variable: Value pairs
47 // and include them here
48 bool done = false;
49 while(!done)
50 {
51 // search for structure definitions, there can be more than one
52 size_t struct_pos = templatized.find(STRUCT_TAG);
53 while(struct_pos != std::string::npos)
54 {
55 templatized = resolve_structure_declaration(struct_pos, templatized);
56 struct_pos = templatized.find(STRUCT_TAG);
57 }
58
59 // search for input data definition hints
60 size_t parameters_pos = templatized.find(PARAMETERS_TAG);
61 if(parameters_pos != std::string::npos)
62 {
63 templatized = resolve_parameters(parameters_pos, templatized);
64 }
65
66 // search for the "include"'s
67 resolve_all_includes(templatized);
68
69 // now search for the "if"'s. Remember, ifs cannot have ifs in their body
70 size_t if_pos = templatized.find(IF_TAG);
71 if(if_pos != std::string::npos)
72 {
73 while(if_pos != std::string::npos)
74 {
75 templatized = resolve_ifs(if_pos, templatized);
76 if_pos = templatized.find(IF_TAG);
77 }
78 }
79 else
80 {
81 done = true;
82 }
83 }
84
85 return templatized;
86 }
87
88 std::string templater_base::extract_identifier_word(const std::string& input, size_t& i, std::vector<char> separators,
89 std::set<char> extra_allowed_chars, char&& c)
90 {
91 skip_whitespace(input, i);
92
93 std::string result = "";
94 while(i < input.length() && (input.at(i) == '_' ||
95 extra_allowed_chars.count(input.at(i)) ||
96 isalnum(input.at(i))) )
97 {
98 if(separators.empty())
99 {
100 result += input.at(i);
101 i++;
102 }
103 else
104 {
105 if(find(separators.begin(), separators.end(), input.at(i)) == separators.end())
106 {
107 result += input.at(i);
108 i++;
109 }
110 else
111 {
112 if(c != 0)
113 {
114 c = input[i];
115 }
116 i ++; // skip the actual separator
117 break;
118 }
119 }
120 }
121
122 skip_whitespace(input, i);
123 if(!separators.empty())
124 {
125 if(find(separators.begin(), separators.end(), input.at(i)) != separators.end())
126 {
127 if(c != 0)
128 {
129 c = input[i];
130 }
131 i++; // this characater is a separator. Skip it. And the space after
132 skip_whitespace(input, i);
133 }
134 }
135
136 return result;
137 }
138
139 bool templater_base::check_opening_parenthesis(const std::string& input, size_t& i)
140 {
141 bool opening_parentheses = false;
142 // skip the parenthesis if any
143 if(i < input.length() && input.at(i) == '(')
144 {
145 i ++;
146 skip_whitespace(input, i);
147 opening_parentheses = true;
148 }
149
150 return opening_parentheses;
151 }
152
153 bool templater_base::check_closing_comment(const std::string& templatized, size_t& i, std::size_t& include_tag_end_position)
154 {
155 if(i + 3 < templatized.length())
156 {
157 if(templatized[i+1] == '-' && templatized[i+2] == '-' && templatized[i + 3] == '>')
158 {
159 include_tag_end_position = i + 4;
160 return true;
161 }
162 }
163 return false;
164 }
165
166 std::string templater_base::get_error() const
167 {
168 return error;
169 }
170
171 std::vector<std::string> templater_base::variables(bool resolve_includes_too)
172 {
173 std::vector<std::string> result;
174
175 std::string content = template_warehouse::instance().getTemplateContent(name());
176 std::string::size_type start_pos = 0;
177
178 if(resolve_includes_too)
179 {
180 resolve_all_includes(content, false);
181 }
182
183 while((start_pos = content.find(TEMPLATE_VARIABLE_START_DELIMITER, start_pos)) != std::string::npos)
184 {
185 std::string current_varname = "";
186 start_pos += TEMPLATE_VARIABLE_START_DELIMITER.length();
187 while(start_pos < content.length() && content[start_pos] != '}')
188 {
189 current_varname += content[start_pos++];
190 }
191 if(start_pos == content.length())
192 {
193 set_error("Unterminated variable name");
194 return result;
195 }
196 result.push_back(current_varname);
197 start_pos ++; // to skip the closing '}'
198 }
199
200 return result;
201 }
202
203 std::string templater_base::resolve_includes(std::string templatized, size_t inc_pos, bool do_variable_replacement)
204 {
205 std::map<std::string, std::string> local_pairs;
206 std::size_t include_tag_end_position = std::string::npos;
207 std::size_t include_tag_start_position = inc_pos;
208
209 inc_pos += INCLUDE_TAG.length();
210
211 std::string inc_template = extract_identifier_word(templatized, inc_pos);
212 bool opening_parentheses = check_opening_parenthesis(templatized, inc_pos);
213 size_t i = inc_pos;
214 bool closing_comment_found = false;
215
216 while(i < templatized.length() && ! closing_comment_found)
217 {
218 std::string var_name = extract_identifier_word(templatized, i, {':','='});
219 if(i == templatized.length() || var_name.empty())
220 {
221 if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
222 {
223 check_closing_comment(templatized, i, include_tag_end_position);
224 }
225
226 break;
227 }
228
229 // now read the variable value
230 std::string var_value = "";
231 bool var_value_read = false;
232 while(!var_value_read && i < templatized.length())
233 {
234 if(templatized[i] == '"') // string starts. Do not read the '"'
235 {
236 i++; // skip the '"'
237
238 bool string_read = false;
239 while(i < templatized.length() && !string_read)
240 {
241 if(templatized[i] == '\\') // Skip the backslash from the escape sequence
242 {
243 i++;
244 }
245
246 var_value += templatized[i++];
247
248 if(i == templatized.length())
249 {
250 set_error("String literal is not finished");
251 break;
252 }
253
254 if(templatized[i] == '"' && templatized[i-1] != '\\')
255 {
256 string_read = true;
257 }
258 }
259
260 if(i == templatized.length()) // invalid syntax
261 {
262 set_error("Invalid syntax, could not resolve an include");
263 break;
264 }
265
266 i++; // skip closing quote
267 }
268 else
269 {
270 // see if this is a closing parenthesis, but only if there was an opening one
271 if(opening_parentheses && templatized[i] == ')')
272 {
273 var_value_read = true;
274
275 // now see if there is a closing comment. Everything after it will be ignored
276 while(i < templatized.length() && !closing_comment_found)
277 {
278 i ++;
279 skip_whitespace(templatized, i);
280 if(i < templatized.length() && templatized[i] == '#') // This should be the closing comment
281 {
282 closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
283 }
284 }
285 }
286
287 if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
288 {
289 closing_comment_found = var_value_read = check_closing_comment(templatized, i, include_tag_end_position);
290 }
291
292 if(templatized[i] == '{' && i<templatized.length() && templatized[i + 1] == '#') // The substitution of a variable with another one from this level
293 {
294 // Read in the entire variable and simply change the var_value to the one in our objects' map
295 i+=2; // skip {#
296 std::string upperlevel_var_name = extract_identifier_word(templatized, i, {'}'});
297
298 if(i == templatized.length() || upperlevel_var_name.empty())
299 {
300 break;
301 }
302
303 if(kps.count(upperlevel_var_name) != 0)
304 {
305 var_value += kps[upperlevel_var_name];
306 }
307 }
308
309 // and comma (or space) separatin the variables?
310 if(i < templatized.length() && (templatized[i] == ',' || iswspace(templatized[i])) )
311 {
312 var_value_read = true;
313 i++;
314 skip_whitespace(templatized, i);
315 }
316
317 // still not read the value?
318 if(i < templatized.length() && !var_value_read)
319 {
320 var_value += templatized[i++];
321 }
322 }
323 }
324
325 skip_whitespace(templatized, i);
326
327 // now insert into local_pairs the keys and values
328 local_pairs.insert(make_pair(var_name, var_value));
329 }
330
331 // and now read in the template from inc_template
332 // and replace everything that is supposed to be replaced
333 std::string inc_content = template_warehouse::instance().getTemplateContent( inc_template );
334 std::string inc_templatized = do_variable_replacement ? stringholder(inc_content).templatize(local_pairs).get() : inc_content;
335
336 templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
337 templatized.insert(include_tag_start_position, inc_templatized);
338 return templatized;
339 }
340
341 std::string templater_base::resolve_structure_declaration(size_t struct_pos, std::string templatized)
342 {
343 std::size_t include_tag_start_position = struct_pos;
344
345 struct_pos += STRUCT_TAG.length();
346
347 std::string struct_name = extract_identifier_word(templatized, struct_pos);
348 bool opening_parentheses = check_opening_parenthesis(templatized, struct_pos);
349
350 add_structure_decl(struct_name, struct_name);
351
352 // and now read in the structure members' names
353 size_t i = struct_pos;
354 bool closing_comment_found = false;
355 std::size_t include_tag_end_position = std::string::npos;
356
357 while (i < templatized.length() && !closing_comment_found)
358 {
359 std::string member_name = "";
360 while(i < templatized.length() && (isalnum(templatized[i]) || templatized[i] == '_'))
361 {
362 member_name += templatized[i++];
363 }
364 if(member_name.empty()) // syntax error, just give back what came out
365 {
366 set_error("Invalid syntax, could not resolve a structure declaration");
367 return templatized;
368 }
369
370 skip_whitespace(templatized, i);
371
372 // var_name now is the name of the structure's member, make it
373 // a default value
374 get_structure(struct_name)[member_name] = "";
375
376 // now this should point either to a space or a ","
377 i++;
378
379 // se if we have a "," coming up
380 if(templatized[i] == ',')
381 {
382 i ++;
383 skip_whitespace(templatized, i);
384 }
385
386 // see if this is a closing parenthesis, but only if there was an opening one
387 if(opening_parentheses && templatized[i] == ')')
388 {
389
390 // now see if there is a closing comment. Everything after it will be ignored
391 while(i < templatized.length() && !closing_comment_found)
392 {
393 i ++;
394 skip_whitespace(templatized, i);
395 if(i < templatized.length() && templatized[i] == '#') // This should be the closing comment
396 {
397 closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
398 }
399 }
400 }
401
402 if(templatized[i] == '#' && !closing_comment_found) // This should be the closing comment
403 {
404 closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
405 }
406 }
407
408 templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
409 return templatized;
410 }
411
412 templater_base &templater_base::templatize(const template_struct &s)
413 {
414 precalculated = "";
415 std::string templatized = get(); // will initialize the parameters
416 if(!m_parameters.empty())
417 {
418 for(const auto &p : m_parameters)
419 {
420 if(p.first == s.name)
421 {
422 for(const auto &x : s.struct_members)
423 {
424 std::string fullname = s.name + "." + x.first;
425 kps.insert(make_pair(fullname, unafrog::utils::to_string(x.second)));
426 }
427 }
428 }
429 }
430 templatized = stringholder(templatized).templatize(kps).get();
431 templatized = resolve_ifeqs(templatized);
432 templatized = resolve_scripts(templatized, SCRIPT_TAG);
433 precalculated = templatized;
434
435 return *this;
436 }
437
438 std::string templater_base::resolve_loops(std::string templatized, const template_vector_par &v)
439 {
440 size_t loop_pos = templatized.find(LOOP_TAG);
441 while(loop_pos != std::string::npos)
442 {
443 // find the stuff between the iterate and end iterate tags
444 std::size_t loop_tag_end_position = std::string::npos;
445 std::size_t loop_tag_start_position = loop_pos;
446
447 loop_pos += LOOP_TAG.length();
448
449 // see on what are we iterating through
450 std::string loop_target = extract_identifier_word(templatized, loop_pos);
451
452 // there should be nothing more after this
453 if(!check_closing_comment(templatized, loop_pos, loop_tag_end_position))
454 {
455 return templatized;
456 }
457
458 // now find the end iterate tag
459 size_t end_loop_pos = templatized.find(ENDLOOP_TAG, loop_tag_end_position);
460 size_t save_endpos = end_loop_pos;
461 end_loop_pos += ENDLOOP_TAG.length();
462 size_t endloop_endpos = std::string::npos;
463
464 size_t temp = end_loop_pos;
465
466 while(temp != std::string::npos)
467 {
468 size_t unused = temp;
469
470 std::string endloop_target = extract_identifier_word(templatized, unused);
471 if(endloop_target == loop_target)
472 {
473 if(!check_closing_comment(templatized, unused, endloop_endpos))
474 {
475 return templatized;
476 }
477
478 break;
479 }
480
481 temp = templatized.find(ENDLOOP_TAG, temp);
482 save_endpos = temp;
483 temp += ENDLOOP_TAG.length();
484 }
485
486 // now between loop_tag_end_position and save_endpos there is the strign we need
487 std::string loop_content = templatized.substr(loop_tag_end_position, save_endpos - loop_tag_end_position);
488
489 std::string final_loop_content = "";
490
491 // and now insert a bunch of kps for the elements in v
492 if(!m_parameters.empty())
493 {
494 size_t c = 0;
495 for(const auto &ts : v.value())
496 {
497 c++;
498 for(const auto& p : m_parameters)
499 {
500 if(p.first == v.name())
501 {
502 stringholder sh(loop_content);
503
504 for(const auto &x : ts.struct_members)
505 {
506 //if(x.first == loop_target)
507 {
508 std::string fullname = v.name() + "." + x.first;
509 std::string fullname_to_replace_with = v.name() + "." + x.first + ":" + std::to_string(c);
510
511 kps.insert(make_pair(fullname_to_replace_with, unafrog::utils::to_string(x.second)));
512 sh.replace_all(TEMPLATE_VARIABLE_START_DELIMITER + fullname +TEMPLATE_VARIABLE_END_DELIMITER,
513 TEMPLATE_VARIABLE_START_DELIMITER + fullname_to_replace_with + TEMPLATE_VARIABLE_END_DELIMITER);
514 }
515 }
516
517 final_loop_content += sh.get();
518 }
519 }
520 }
521 }
522 // now fetch out the substring (loop_tag_end_position, save_endpos - loop_tag_end_position) from templatized
523 // and replace with final_loop_content
524
525 if(v.name() == loop_target)
526 {
527 std::string part1 = templatized.substr(0, loop_tag_end_position);
528
529 part1.erase(loop_tag_start_position, loop_tag_end_position - loop_tag_start_position);
530 part1 += final_loop_content;
531 part1 += templatized.substr(endloop_endpos);
532
533 templatized = part1;
534 loop_pos = templatized.find(LOOP_TAG);
535 }
536 else
537 {
538 loop_pos = templatized.find(LOOP_TAG, endloop_endpos);
539 }
540 }
541
542 templatized = stringholder(templatized).templatize(kps).get();
543 precalculated = templatized;
544 return templatized;
545 }
546
547 std::string templater_base::resolve_ifeqs(std::string templatized)
548 {
549 bool done = false;
550 while(!done)
551 {
552 size_t ifeq_pos = templatized.find(IFEQ_TAG);
553 if(ifeq_pos != std::string::npos)
554 {
555 while(ifeq_pos != std::string::npos)
556 {
557 templatized = resolve_ifeq(ifeq_pos, templatized);
558 ifeq_pos = templatized.find(IFEQ_TAG);
559 }
560 }
561 else
562 {
563 done = true;
564 }
565 }
566 return templatized;
567 }
568
569 std::string templater_base::resolve_translations(std::string templatized, const std::string &target_language, bool generate_span, std::map<std::string, std::map<std::string, std::string> >& translations)
570 {
571 bool done = false;
572 while(!done)
573 {
574 size_t ifeq_pos = templatized.find(TRANSLATE_TAG);
575 if(ifeq_pos != std::string::npos)
576 {
577 while(ifeq_pos != std::string::npos)
578 {
579 std::map<std::string, std::string> local_translations;
580 std::string span_id;
581
582 templatized = resolve_translation(ifeq_pos, templatized, target_language, generate_span, span_id, local_translations);
583
584 translations[span_id] = local_translations;
585
586 ifeq_pos = templatized.find(TRANSLATE_TAG);
587 }
588 }
589 else
590 {
591 done = true;
592 }
593 }
594 return templatized;
595
596 }
597
598 static std::string span_name(std::string str)
599 {
600 str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end());
601 return "span_" + str;
602 }
603
604 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<std::string, std::string> &translations)
605 {
606 size_t i = tr_pos;
607
608 // skip the <!--#translate
609 i += TRANSLATE_TAG.length();
610
611 // and any whitespace that might follow
612 skip_whitespace(templatized, i);
613 size_t translateable_start = i;
614
615 // now extract the word to translate
616 if(i < templatized.length())
617 {
618 char c_sep = 32;
619 std::string what_to = extract_identifier_word(templatized, i, {'#'}, {' ', ',', '.', '!', '?', ':', ':'}, std::move(c_sep));
620 skip_whitespace(templatized, i);
621
622 std::string before_translate = templatized.substr(0, tr_pos);
623 std::string between = templatized.substr(translateable_start, what_to.length());
624 std::string after_translate = templatized.substr(translateable_start + what_to.length() + 4);
625
626 span_id = span_name(between);
627
628 templatized = before_translate + (generate_span ? "<span id='" + span_id + "'>" : "") + dictionary::translate(between, target_language, true, translations) +(generate_span ? "</span>" : "") + after_translate;
629 }
630
631
632 return templatized;
633
634 }
635
636 templater_base &templater_base::templatize(const template_vector_par &v)
637 {
638 do_not_resolve_in_get();
639 std::string templatized = precalculated.empty() ? get() : precalculated;
640
641 do_resolve_in_get();
642
643 templatized = resolve_loops(templatized, v);
644 templatized = resolve_ifeqs(templatized);
645 templatized = resolve_scripts(templatized, SCRIPT_TAG);
646 precalculated = templatized;
647
648 return *this;
649 }
650
651 void templater_base::skip_whitespace(const std::string& templatized, size_t& i)
652 {
653 while(i < templatized.length() && std::iswspace(templatized[i]))
654 {
655 i++;
656 }
657 }
658
659 std::string templater_base::resolve_parameters(size_t parameters_pos, std::string templatized)
660 {
661 std::size_t include_tag_end_position = std::string::npos;
662 std::size_t include_tag_start_position = parameters_pos;
663
664 parameters_pos += PARAMETERS_TAG.length();
665 skip_whitespace(templatized, parameters_pos);
666
667 size_t i = parameters_pos;
668 bool closing_comment_found = false;
669 while(i < templatized.length() && ! closing_comment_found)
670 {
671 // first: read in the variable name
672 char c_sep = 32;
673 std::string var_name = extract_identifier_word(templatized, i, {':', ' '}, {}, static_cast<char&&>(c_sep));
674
675 if(i == templatized.length() || var_name.empty())
676 {
677 return templatized;
678 }
679
680 std::string var_type = "";
681 bool iterable = false;
682
683 if(c_sep == ':')
684 {
685 // now read the variable type
686 var_type = extract_identifier_word(templatized, i);
687
688 if(i < templatized.length() && templatized[i] == '[')
689 {
690 i ++;
691 skip_whitespace(templatized, i);
692 if(i < templatized.length() && templatized[i] == ']')
693 {
694 i ++;
695 }
696 iterable = true;
697 }
698 }
699
700 add_parameter(var_name, var_type, iterable);
701
702 skip_whitespace(templatized, i);
703
704 // se if we have a "," coming up
705 if(templatized[i] == ',')
706 {
707 i ++;
708 skip_whitespace(templatized, i);
709 }
710
711 if(templatized[i] == '#') // This should be the closing comment
712 {
713 closing_comment_found = check_closing_comment(templatized, i, include_tag_end_position);
714 }
715 }
716
717 templatized.erase(include_tag_start_position, include_tag_end_position - include_tag_start_position);
718 return templatized;
719 }
720
721 std::string templater_base::resolve_ifs(size_t if_pos, std::string templatized)
722 {
723 size_t i = if_pos;
724
725 // skip the <!--#if
726 i += IF_TAG.length();
727
728 // and any whitespace that might follow
729 skip_whitespace(templatized, i);
730
731 // now extract the variable
732 if(i < templatized.length())
733 {
734 char c_sep = 32;
735 std::string var_name = extract_identifier_word(templatized, i, {' ', '#'}, {}, std::move(c_sep));
736 skip_whitespace(templatized, i);
737
738 std::string var_value = "";
739 // now if var_name is empty remove the if and it's body.
740 if(kps.count(var_name))
741 {
742 var_value = kps[var_name];
743 }
744
745 std::string closing_endif_tag = ENDIF_TAG + " " + var_name;
746 std::string closing_else_tag = ELSE_TAG + " " + var_name;
747
748 if(var_value.empty())
749 {
750 // search for the first <!--#endif var_name
751 size_t endif_pos = templatized.find(closing_endif_tag);
752 if(endif_pos != std::string::npos)
753 {
754 // search for the first <!--#else before the endif tag
755
756 size_t else_pos = templatized.find(closing_else_tag);
757 if(else_pos < endif_pos)
758 {
759 // this is the else of this if, removing the content of the if till the else
760 templatized = templatized.substr(0, if_pos) + templatized.substr(else_pos + closing_else_tag.length() + 4);
761 // we still need to remove the endif
762 size_t final_endif_pos = templatized.find(closing_endif_tag);
763 if(final_endif_pos != std::string::npos)
764 {
765 templatized = templatized.substr(0, final_endif_pos) + templatized.substr(final_endif_pos + closing_endif_tag.length() + 4);
766 }
767
768 }
769 else
770 {
771 // no else, just remove the if tag
772 templatized = templatized.substr(0, if_pos) + templatized.substr(endif_pos + closing_endif_tag.length() + 4);
773 }
774 }
775 else
776 {
777 // oops, user forgot the enclosing endif, just return what is here
778 return templatized;
779 }
780 }
781 else // remove the comments :)
782 {
783 size_t endif_pos = templatized.find(closing_endif_tag);
784
785 if(endif_pos != std::string::npos)
786 {
787 size_t remove_start_pos = endif_pos;
788 std::string remove_tag = closing_endif_tag;
789 size_t additional_remove = 0; // count ofchars between else and endif
790 // search for the first <!--#else before the endif tag
791 size_t else_pos = templatized.find(closing_else_tag);
792 if(else_pos < endif_pos)
793 {
794 remove_start_pos = else_pos;
795 additional_remove = endif_pos - remove_start_pos;
796 }
797
798 std::string before_if = templatized.substr(0, if_pos);
799 std::string between = templatized.substr(i + 3, remove_start_pos - i - 3);
800 std::string after_endif = templatized.substr(remove_start_pos + additional_remove + closing_endif_tag.length() + 4);
801
802 templatized = before_if + between + after_endif;
803 }
804 else
805 {
806 // oops, user forgot the enclosing endif, just return what is here
807 return templatized;
808 }
809 }
810 }
811
812 return templatized;
813 }
814
815 std::string templater_base::resolve_ifeq(size_t if_pos, std::string templatized)
816 {
817 size_t i = if_pos;
818
819 // skip the <!--#ifeq
820 i += IFEQ_TAG.length();
821
822 // and any whitespace that might follow
823 skip_whitespace(templatized, i);
824
825 // now extract the variable
826 if(i < templatized.length())
827 {
828 char c_sep = 32;
829 std::string what_to = extract_identifier_word(templatized, i, {' ', '#'}, {',','-'}, std::move(c_sep));
830 skip_whitespace(templatized, i);
831
832 size_t comma_pos = what_to.find(',');
833 bool are_eq = true;
834 if(comma_pos == std::string::npos)
835 {
836 are_eq = false;
837 }
838 else
839 {
840 std::string before = what_to.substr(0, comma_pos);
841 std::string after = what_to.substr(comma_pos + 1);
842
843 are_eq = before == after;
844 }
845
846
847 if(!are_eq)
848 {
849 // search for the first <!--#endifeq
850 size_t endif_pos = templatized.find(ENDEQ_TAG);
851 if(endif_pos != std::string::npos)
852 {
853 templatized = templatized.substr(0, if_pos) + templatized.substr(endif_pos + ENDEQ_TAG.length() + 4);
854 }
855 }
856 else // remove the comments :)
857 {
858 size_t endif_pos = templatized.find(ENDEQ_TAG);
859
860 if(endif_pos != std::string::npos)
861 {
862 size_t remove_start_pos = endif_pos;
863 std::string before_if = templatized.substr(0, if_pos);
864 std::string between = templatized.substr(i + 3, remove_start_pos - i - 3);
865 std::string after_endif = templatized.substr(remove_start_pos + ENDEQ_TAG.length() + 4);
866
867 templatized = before_if + between + after_endif;
868 }
869 }
870 }
871
872 return templatized;
873 }
874
875 std::string templater_base::resolve_defines(std::string content)
876 {
877 size_t define_pos = content.find(DEFINE_TAG);
878
879 std::vector<std::pair<size_t, size_t>> strings_to_remove; // we will remove the define tags from in here
880 if(define_pos != std::string::npos)
881 {
882 while(define_pos != std::string::npos)
883 {
884 bool define_read = false;
885 // skipping the tag
886 size_t i = define_pos + DEFINE_TAG.length();
887 // skipping the whitespace after the tag
888 skip_whitespace(content, i);
889 while(!define_read)
890 {
891
892 std::string var_name = "";
893 std::string var_value = "";
894 // now comes a list of comma separated variable=value assignments or just simply variable
895 while(i < content.length() && content[i] != '=' && content[i] != ',' && content[i] != '#'
896 && (isalnum(content[i]) || content[i] == '_' ))
897 {
898 var_name += content[i];
899 i ++;
900 }
901 // now if content[i] == '=' we need to parse out the value from it
902 if(content[i] == '=')
903 {
904 i ++;
905 bool string_comes = false;
906 if(content[i] == '"') // string comes after this?
907 {
908 i++;
909 string_comes = true;
910 }
911 while( (i < content.length() && content[i] != ',' && content[i] != '#' && !string_comes)
912 || (i < content.length() && string_comes && content[i] != '"'))
913 {
914 if(content[i] != '\\') // escaping a quote in the string
915 {
916 var_value += content[i];
917 }
918 else
919 {
920 i++;
921 var_value += content[i];
922 }
923 i++;
924 }
925 }
926 else
927 {
928 var_value = "true";
929 }
930 kps.insert({var_name, var_value});
931 if(content[i] == '#')
932 {
933 i++;
934 if(i + 2 < content.length() && content[i] == '-' && content[i+1] == '-' && content[i+2]=='>')
935 {
936 i += 3; // skip the closing comment
937 define_read = true;
938 }
939 else
940 {
941 // this is a malformed entry, just give up
942 return content;
943 }
944 }
945 else
946 {
947 if(content[i] == ',')
948 {
949 i++;
950 }
951 }
952 }
953
954 strings_to_remove.insert(strings_to_remove.begin(), {define_pos, i});
955 define_pos = content.find(DEFINE_TAG, i);
956 }
957 }
958 // now walk through the strings_to_remove and delete from the content everything from there
959 for(const auto& beg_end_pos : strings_to_remove)
960 {
961 content.erase(beg_end_pos.first, beg_end_pos.second - beg_end_pos.first);
962 }
963
964 return content;
965 }
966
967 std::string templater_base::resolve_scripts(std::string templatized, const std::string& tag)
968 {
969 bool done = false;
970 while(!done)
971 {
972 size_t script_tag_pos = templatized.find(tag);
973 if(script_tag_pos != std::string::npos)
974 {
975 while(script_tag_pos != std::string::npos)
976 {
977 templatized = resolve_script(script_tag_pos, templatized, tag);
978 script_tag_pos = templatized.find(tag);
979 }
980 }
981 else
982 {
983 done = true;
984 }
985 }
986 return templatized;
987 }
988
989 std::string templater_base::resolve_script(size_t pos, const std::string& content, const std::string &tag)
990 {
991 size_t endscript_start_pos = content.find(ENDSCRIPT_TAG);
992 size_t old_pos = pos;
993
994 // skip the tag
995 pos += tag.length();
996 // skip the space(s) followwing the "script"
997 while(isspace(content[pos])) pos += 1;
998
999 std::string script_type = "";
1000 while(pos < content.length() && content[pos] != '#' && content[pos] != ':')
1001 {
1002 script_type += content[pos++];
1003 }
1004
1005 if(pos == content.length())
1006 {
1007 set_error("Script tag at", old_pos, "is not closed");
1008 return content.substr(0, old_pos);
1009 }
1010
1011 static const auto script_types = {"python"};
1012
1013 bool found = false;
1014 for(const auto& st : script_types)
1015 {
1016 if(st == script_type) found = true;
1017 }
1018
1019 if(!found)
1020 {
1021 set_error("Unsupported scripting language", script_type);
1022 return content.substr(0, old_pos);
1023 }
1024
1025 // pos points to the script closing tag
1026 std::string before_script = content.substr(0, old_pos);
1027 std::string between = std::string(content.begin() + pos + 4, // #--> is the 4
1028 content.begin() + endscript_start_pos);
1029 std::string after_script = content.substr(endscript_start_pos + ENDSCRIPT_TAG.length() + 4);
1030
1031 std::string result = python_runner().run(kps, between, variables(true));
1032
1033
1034 return before_script + unafrog::utils::trim(result) + after_script;
1035 }
1036
1037 void stringholder::replace_all(const std::string &from, const std::string &to)
1038 {
1039 if(from.empty())
1040 {
1041 return;
1042 }
1043 size_t start_pos = 0;
1044 while((start_pos = str.find(from, start_pos)) != std::string::npos)
1045 {
1046 str.replace(start_pos, from.length(), to);
1047 start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
1048 }
1049 }
1050
1051 stringholder &stringholder::templatize(const std::map<std::string, std::string> &m)
1052 {
1053 for(auto it = m.begin(); it != m.end(); ++ it)
1054 {
1055 replace_all(TEMPLATE_VARIABLE_START_DELIMITER + it->first + TEMPLATE_VARIABLE_END_DELIMITER, it->second );
1056 }
1057 return *this;
1058 }
1059
1060 template_warehouse &template_warehouse::instance()
1061 {
1062 static template_warehouse twh;
1063 return twh;
1064 }
1065
1066 bool template_warehouse::checkTemplates()
1067 {
1068 for(auto it = templates.begin(); it != templates.end(); ++it)
1069 {
1070 if(!it->second->check())
1071 {
1072 return false;
1073 }
1074 }
1075 debug() << "All templates OK";
1076 return true;
1077 }
1078
1079 std::string template_warehouse::getTemplateContent(const std::string &templateName)
1080 {
1081 if(templates.count(templateName))
1082 {
1083 std::shared_ptr<template_base> tb = templates[templateName];
1084 std::string c = tb->content();
1085 return c;
1086 }
1087 else
1088 {
1089 return "Not found:" + templateName;
1090 }
1091 }
1092
1093 bool template_warehouse::registerTemplate(const std::string &name, template_base *templateClass)
1094 {
1095 // although this code seems stupid, it is due to the fact that every inclusion of the
1096 // templater.h will cause a new object to be created, but we want only one
1097 if(templates.count(name))
1098 {
1099 debug() << "Freeing existing template class:" << name;
1100 delete templateClass;
1101 return false;
1102 }
1103 debug() << "Registering template class:" << name;
1104 templates[name] = std::shared_ptr<template_base>(templateClass);
1105 return true;
1106 }
1107
1108
1109 std::string file_template::base_dir() const
1110 {
1111 namespace fs = std::filesystem;
1112 if(fs::is_regular_file(fileName()))
1113 {
1114 return "";
1115 }
1116 else
1117 {
1118 return TEMPLATES_DIRECTORY;
1119 }
1120
1121 }
1122
1123 std::string file_template::content() const
1124 {
1125 std::string fn = base_dir() + fileName();
1126 std::ifstream ifs(fn);
1127 std::string s( (std::istreambuf_iterator<char>(ifs) ),
1128 (std::istreambuf_iterator<char>()) );
1129 return s;
1130 }
1131
1132 bool file_template::check() const
1133 {
1134 struct stat buffer;
1135 bool res = (stat ( (base_dir() + fileName()).c_str(), &buffer) == 0);
1136 if(!res)
1137 {
1138 debug() << "Check for "<< fileName() << " failed";
1139 }
1140 return res;
1141 }
1142
1143
1144