From a73e1462f07c59e34162de9bef3442d92758f1a5 Mon Sep 17 00:00:00 2001 From: Artem Date: Fri, 7 Jan 2022 13:31:32 +0100 Subject: [PATCH] tmpl.v: fix of is_html_open_tag function and allow usage of V template sign '@' in JS code (#13067) --- vlib/v/parser/tmpl.v | 63 +++++++++++-------- ...selective_interpolation_in_script_tag.html | 11 ++++ .../tmpl_script_tag_interpolation_test.v | 16 +++++ 3 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 vlib/v/tests/tmpl/selective_interpolation_in_script_tag.html create mode 100644 vlib/v/tests/tmpl_script_tag_interpolation_test.v diff --git a/vlib/v/parser/tmpl.v b/vlib/v/parser/tmpl.v index dd9a258a75..ce5b1eb775 100644 --- a/vlib/v/parser/tmpl.v +++ b/vlib/v/parser/tmpl.v @@ -15,25 +15,30 @@ enum State { // span // span.{ } +const tmpl_str_end = "')\n" + // check HTML open tag `` fn is_html_open_tag(name string, s string) bool { - mut len := s.len + trimmed_line := s.trim_space() + mut len := trimmed_line.len + if len < name.len { return false } - mut sub := s[0..1] + + mut sub := trimmed_line[0..1] if sub != '<' { // not start with '<' return false } - sub = s[len - 1..len] + sub = trimmed_line[len - 1..len] if sub != '>' { // not end with '<' return false } - sub = s[len - 2..len - 1] + sub = trimmed_line[len - 2..len - 1] if sub == '/' { // self-closing return false } - sub = s[1..len - 1] + sub = trimmed_line[1..len - 1] if sub.contains_any('<>') { // ` >` return false } @@ -51,6 +56,21 @@ fn is_html_open_tag(name string, s string) bool { } } +fn insert_template_code(fn_name string, tmpl_str_start string, line string) string { + // HTML, may include `@var` + // escaped by cgen, unless it's a `vweb.RawHtml` string + trailing_bs := parser.tmpl_str_end + 'sb_${fn_name}.write_b(92)\n' + tmpl_str_start + round1 := ['\\', '\\\\', r"'", "\\'", r'@', r'$'] + round2 := [r'$$', r'\@', r'.$', r'.@'] + mut rline := line.replace_each(round1).replace_each(round2) + + if rline.ends_with('\\') { + rline = rline[0..rline.len - 2] + trailing_bs + } + + return rline +} + // compile_file compiles the content of a file by the given path as a template pub fn (mut p Parser) compile_template_file(template_file string, fn_name string) string { mut lines := os.read_lines(template_file) or { @@ -60,7 +80,6 @@ pub fn (mut p Parser) compile_template_file(template_file string, fn_name string basepath := os.dir(template_file) lstartlength := lines.len * 30 tmpl_str_start := "sb_${fn_name}.write_string('" - tmpl_str_end := "')\n" mut source := strings.new_builder(1000) source.writeln(' import strings @@ -170,24 +189,24 @@ fn vweb_tmpl_${fn_name}() string { source.write_string(line[pos + 6..line.len - 1]) source.writeln('" rel="stylesheet" type="text/css">') } else if line.contains('@if ') { - source.writeln(tmpl_str_end) + source.writeln(parser.tmpl_str_end) pos := line.index('@if') or { continue } source.writeln('if ' + line[pos + 4..] + '{') source.writeln(tmpl_str_start) } else if line.contains('@end') { // Remove new line byte source.go_back(1) - source.writeln(tmpl_str_end) + source.writeln(parser.tmpl_str_end) source.writeln('}') source.writeln(tmpl_str_start) } else if line.contains('@else') { // Remove new line byte source.go_back(1) - source.writeln(tmpl_str_end) + source.writeln(parser.tmpl_str_end) source.writeln(' } else { ') source.writeln(tmpl_str_start) } else if line.contains('@for') { - source.writeln(tmpl_str_end) + source.writeln(parser.tmpl_str_end) pos := line.index('@for') or { continue } source.writeln('for ' + line[pos + 4..] + '{') source.writeln(tmpl_str_start) @@ -216,28 +235,22 @@ fn vweb_tmpl_${fn_name}() string { source.writeln('') } } else if state == .js { - // replace `$` to `\$` at first to escape JavaScript template literal syntax - source.writeln(line.replace(r'$', r'\$').replace(r'$$', r'@').replace(r'.$', - r'.@').replace(r"'", r"\'")) + if line.contains('//V_TEMPLATE') { + source.writeln(insert_template_code(fn_name, tmpl_str_start, line)) + } else { + // replace `$` to `\$` at first to escape JavaScript template literal syntax + source.writeln(line.replace(r'$', r'\$').replace(r'$$', r'@').replace(r'.$', + r'.@').replace(r"'", r"\'")) + } } else if state == .css { // disable template variable declaration in inline stylesheet // because of some CSS rules prefixed with `@`. source.writeln(line.replace(r'.$', r'.@').replace(r"'", r"\'")) } else { - // HTML, may include `@var` - // escaped by cgen, unless it's a `vweb.RawHtml` string - trailing_bs := tmpl_str_end + 'sb_${fn_name}.write_b(92)\n' + tmpl_str_start - round1 := ['\\', '\\\\', r"'", "\\'", r'@', r'$'] - round2 := [r'$$', r'\@', r'.$', r'.@'] - rline := line.replace_each(round1).replace_each(round2) - if rline.ends_with('\\') { - source.writeln(rline[0..rline.len - 2] + trailing_bs) - } else { - source.writeln(rline) - } + source.writeln(insert_template_code(fn_name, tmpl_str_start, line)) } } - source.writeln(tmpl_str_end) + source.writeln(parser.tmpl_str_end) source.writeln('_tmpl_res_$fn_name := sb_${fn_name}.str() ') source.writeln('return _tmpl_res_$fn_name') source.writeln('}') diff --git a/vlib/v/tests/tmpl/selective_interpolation_in_script_tag.html b/vlib/v/tests/tmpl/selective_interpolation_in_script_tag.html new file mode 100644 index 0000000000..1007509355 --- /dev/null +++ b/vlib/v/tests/tmpl/selective_interpolation_in_script_tag.html @@ -0,0 +1,11 @@ +// V won't parse that + + +// V will parse that + diff --git a/vlib/v/tests/tmpl_script_tag_interpolation_test.v b/vlib/v/tests/tmpl_script_tag_interpolation_test.v new file mode 100644 index 0000000000..bb57f81e4d --- /dev/null +++ b/vlib/v/tests/tmpl_script_tag_interpolation_test.v @@ -0,0 +1,16 @@ +module main + +struct PlotData { + dates []string + numerical_result []int +} + +fn test_template_interpolation_can_be_selectively_turned_on_in_script_tags() { + benchmark_plot_data := PlotData{['2012-11-30', '2022-12-29'], [5, 6, 7, 1]} + text := $tmpl('tmpl/selective_interpolation_in_script_tag.html') + // dump(text) + assert text.contains('var non_interpolated_labels = @benchmark_plot_data.dates;') + assert text.contains('var non_interpolated_values = @benchmark_plot_data.numerical_result;') + assert text.contains("var real_labels = ['2012-11-30', '2022-12-29']; //V_TEMPLATE") + assert text.contains('var real_values = [5, 6, 7, 1]; //V_TEMPLATE') +}