-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathinterpolate.go
More file actions
152 lines (127 loc) · 3.87 KB
/
Copy pathinterpolate.go
File metadata and controls
152 lines (127 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package vuego
import (
"fmt"
"html"
"io"
"strings"
"sync"
"github.com/titpetric/vuego/internal/helpers"
)
// bufferPool reuses strings.Builder instances to reduce allocations.
var bufferPool = sync.Pool{
New: func() any { return &strings.Builder{} },
}
func containsInterpolation(input string) bool {
open := strings.Count(input, "{{")
close := strings.Count(input, "}}")
return open == close && open > 0
}
// interpolateToWriter writes interpolated values to w, escaping for HTML safety.
// This is the core implementation that does not allocate a string result.
// For script and style tags, values are not HTML-escaped.
func (v *Vue) interpolateToWriter(ctx VueContext, w io.Writer, input string) error {
if !strings.Contains(input, "{{") {
_, err := io.WriteString(w, input)
return err
}
last := 0
for {
// Find the next {{ occurrence
start := strings.Index(input[last:], "{{")
if start < 0 {
break
}
start += last
// Find the closing }}
endPos := strings.Index(input[start+2:], "}}")
if endPos < 0 {
break
}
endPos += start + 2 // Position of first }
endPos += 2 // Skip past }}
// Write the text before the interpolation
if _, err := io.WriteString(w, input[last:start]); err != nil {
return err
}
// Extract the expression between {{ and }}, trim whitespace
exprStart := start + 2
exprEnd := endPos - 2
// Skip leading whitespace
for exprStart < exprEnd && (input[exprStart] == ' ' || input[exprStart] == '\t' || input[exprStart] == '\n' || input[exprStart] == '\r') {
exprStart++
}
// Skip trailing whitespace
for exprEnd > exprStart && (input[exprEnd-1] == ' ' || input[exprEnd-1] == '\t' || input[exprEnd-1] == '\n' || input[exprEnd-1] == '\r') {
exprEnd--
}
expr := input[exprStart:exprEnd]
var val any
var err error
// Try unified pipe/expr evaluation (handles both filters and expressions)
if strings.Contains(expr, "|") || helpers.IsFunctionCall(expr) || helpers.IsComplexExpr(expr) {
pipe := parsePipeExpr(expr)
val, err = v.evalPipe(ctx, pipe)
if err != nil {
return fmt.Errorf("in expression '{{ %s }}': %w", expr, err)
}
} else {
// Simple variable reference
var ok bool
val, ok = ctx.stack.Resolve(expr)
if !ok {
val = nil
}
}
if val != nil {
// Escape value for HTML output (unless in a script/style tag)
valStr := fmt.Sprint(val)
parentTag := ctx.CurrentTag()
// Skip escaping inside script and style tags, since they contain code/CSS, not HTML
if parentTag == "script" || parentTag == "style" {
if _, err := io.WriteString(w, valStr); err != nil {
return err
}
} else {
// Skip escaping if the string doesn't contain special characters
// (avoids allocation in html.EscapeString for most cases)
if !helpers.NeedsHTMLEscape(valStr) {
if _, err := io.WriteString(w, valStr); err != nil {
return err
}
} else {
if _, err := io.WriteString(w, html.EscapeString(valStr)); err != nil {
return err
}
}
}
}
last = endPos
}
// Write remaining text
if _, err := io.WriteString(w, input[last:]); err != nil {
return err
}
return nil
}
// interpolate escapes interpolated values for HTML safety.
// Uses a buffer pool to minimize allocations.
// For script and style tags, values are not HTML-escaped.
func (v *Vue) interpolate(ctx VueContext, input string) (string, error) {
buf := bufferPool.Get().(*strings.Builder)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
// Early return for no-interpolation case
if !containsInterpolation(input) {
return input, nil
}
// Pre-allocate builder capacity to reduce grow operations
// Estimate output will be ~120% of input (for escaped HTML entities)
estimatedLen := len(input) + len(input)/5
buf.Grow(estimatedLen)
if err := v.interpolateToWriter(ctx, buf, input); err != nil {
return "", err
}
return buf.String(), nil
}