tmpl.v: fix of is_html_open_tag function and allow usage of V template sign '@' in JS code (#13067)

pull/13081/head
Artem 2022-01-07 13:31:32 +01:00 committed by GitHub
parent d3317cbd4f
commit a73e1462f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 25 deletions

View File

@ -15,25 +15,30 @@ enum State {
// span // span.{
}
const tmpl_str_end = "')\n"
// check HTML open tag `<name attr="x" >`
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('<>') { // `<name <bad> >`
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('</div>')
}
} 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('}')

View File

@ -0,0 +1,11 @@
// V won't parse that
<script>
var non_interpolated_labels = @benchmark_plot_data.dates;
var non_interpolated_values = @benchmark_plot_data.numerical_result;
</script>
// V will parse that
<script>
var real_labels = @benchmark_plot_data.dates; //V_TEMPLATE
var real_values = @benchmark_plot_data.numerical_result; //V_TEMPLATE
</script>

View File

@ -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')
}