-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy patheval_condition.go
More file actions
249 lines (217 loc) · 7.8 KB
/
Copy patheval_condition.go
File metadata and controls
249 lines (217 loc) · 7.8 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package vuego
import (
"strings"
"golang.org/x/net/html"
"github.com/titpetric/vuego/internal/helpers"
)
// evalCondition evaluates a v-if condition without modifying any node attributes.
// Attributes should be removed later during rendering.
func (v *Vue) evalCondition(ctx VueContext, expr string) (bool, error) {
return v.evalConditionExpr(ctx, expr)
}
// evalConditionExpr evaluates a condition expression without modifying any node attributes.
// Used for v-if, v-else-if and other condition evaluations.
func (v *Vue) evalConditionExpr(ctx VueContext, expr string) (bool, error) {
expr = strings.TrimSpace(expr)
// Normalize comparison operators: coalesce === to == and !== to !=
expr = helpers.NormalizeComparisonOperators(expr)
// Try to evaluate as expr expression first (supports ==, !=, &&, ||, !, <, >, <=, >=, and function calls)
result, err := v.exprEval.Eval(expr, ctx.ExprEnv())
if err == nil {
// Successfully evaluated with expr - convert to boolean
return helpers.IsTruthy(result), nil
}
// If expr evaluation failed and expression starts with !, handle nil negation manually.
// expr library fails when trying to negate nil (e.g., "!item.primary" where primary key doesn't exist).
// Workaround: evaluate the inner expression and negate the boolean conversion.
if strings.HasPrefix(expr, "!") {
innerExpr := strings.TrimSpace(expr[1:])
// Try to evaluate inner expression (may return nil)
innerResult, innerErr := v.exprEval.Eval(innerExpr, ctx.ExprEnv())
if innerErr == nil {
// Successfully evaluated - convert nil to bool and negate
return !helpers.IsTruthy(innerResult), nil
}
// Fall back to stack resolution if expr evaluation fails
val, ok := ctx.stack.Resolve(innerExpr)
if ok {
return !helpers.IsTruthy(val), nil
}
// Undefined value: !undefined = true
return true, nil
}
// Fall back to legacy behavior for simple variable references
val, ok := ctx.stack.Resolve(expr)
if !ok {
// Variable not found - return false
return false, nil
}
return helpers.IsTruthy(val), nil
}
// evalElseIfChain evaluates a v-if, v-else-if, v-else chain starting at the given node.
// It returns the result nodes, the number of nodes to skip (including v-else-if/v-else),
// and an error if evaluation fails.
// The skipCount includes all nodes consumed by the chain (up to and including the matched node or the end of the chain).
func (v *Vue) evalElseIfChain(ctx VueContext, node *html.Node, nodes []*html.Node, depth int) ([]*html.Node, int, error) {
var result []*html.Node
lastChainNodeIdx := 0 // Track the last node in the chain for skipCount
// Check for v-if
if vIf := helpers.GetAttr(node, "v-if"); vIf != "" {
ok, err := v.evalCondition(ctx, vIf)
if err != nil {
return nil, 0, err
}
if ok {
// v-if condition is true - evaluate node (don't remove attribute here, filter during rendering)
// Find the last node in the chain to determine skipCount
// We need to skip past any v-else-if and v-else nodes that follow
for idx := 1; idx < len(nodes); idx++ {
nextNode := nodes[idx]
if nextNode.Type != html.ElementNode {
continue
}
if !helpers.HasAttr(nextNode, "v-else-if") && !helpers.HasAttr(nextNode, "v-else") {
break
}
lastChainNodeIdx = idx
}
// Evaluate the node (evaluateNodeAsElement handles cloning internally)
evaluated, err := v.evaluateNodeAsElement(ctx, node, depth)
return evaluated, lastChainNodeIdx, err
}
// v-if condition is false - check next nodes for v-else-if or v-else
} else {
// No v-if attribute found - shouldn't happen, but handle gracefully
return result, lastChainNodeIdx, nil
}
// Check for v-else-if and v-else in following nodes
for idx := 1; idx < len(nodes); idx++ {
nextNode := nodes[idx]
// Skip text nodes (whitespace)
if nextNode.Type != html.ElementNode {
continue
}
// Only consider nodes with v-else-if or v-else
if !helpers.HasAttr(nextNode, "v-else-if") && !helpers.HasAttr(nextNode, "v-else") {
// No more else directives in the chain
break
}
lastChainNodeIdx = idx // Update the last chain node index
if vElseIf := helpers.GetAttr(nextNode, "v-else-if"); vElseIf != "" {
ok, err := v.evalConditionExpr(ctx, vElseIf)
if err != nil {
return nil, 0, err
}
if ok {
// v-else-if condition is true - evaluate and return this node (don't remove attribute, filter during rendering)
// Evaluate the node (evaluateNodeAsElement handles cloning internally)
evaluated, err := v.evaluateNodeAsElement(ctx, nextNode, depth)
return evaluated, idx, err
}
// v-else-if condition is false - continue to next
continue
}
// Check for v-else
if helpers.HasAttr(nextNode, "v-else") {
// v-else always matches - evaluate and return this node (don't remove attribute, filter during rendering)
// Evaluate the node (evaluateNodeAsElement handles cloning internally)
evaluated, err := v.evaluateNodeAsElement(ctx, nextNode, depth)
return evaluated, idx, err
}
}
// No condition in the chain was true - skip all chain nodes anyway
return result, lastChainNodeIdx, nil
}
// evaluateNodeAsElement evaluates a single element node with its v-for and other directives.
// This is used internally by the else-if chain handler.
func (v *Vue) evaluateNodeAsElement(ctx VueContext, node *html.Node, depth int) ([]*html.Node, error) {
var result []*html.Node
// Handle v-for if present
if vFor := helpers.GetAttr(node, "v-for"); vFor != "" {
loopNodes, err := v.evalFor(ctx, node, vFor, depth+1)
if err != nil {
return nil, err
}
for _, n := range loopNodes {
if err := v.evalVHtml(ctx, n); err != nil {
return nil, err
}
if _, err := v.evalAttributes(ctx, n); err != nil {
return nil, err
}
}
result = append(result, loopNodes...)
return result, nil
}
// Special handling for template tags: evaluate bound attributes and set them in current scope
if node.Data == "template" {
// For templates, bound attributes modify the current scope (don't create new scope)
for _, attr := range node.Attr {
// Check for bound attributes (: or v-bind:)
boundName := attr.Key
if strings.HasPrefix(boundName, ":") {
boundName = boundName[1:]
} else if strings.HasPrefix(boundName, "v-bind:") {
boundName = boundName[7:]
} else {
// Not a bound attribute, skip it
continue
}
// Evaluate the bound attribute expression
// Use expression evaluator for templates to support literals and expressions
expr := strings.TrimSpace(attr.Val)
val, err := v.exprEval.Eval(expr, ctx.ExprEnv())
if err == nil {
// Expression evaluated successfully
ctx.stack.Set(boundName, val)
continue
}
// Fall back to variable resolution if expression evaluation fails
valResolved, ok := ctx.stack.Resolve(expr)
if ok {
ctx.stack.Set(boundName, valResolved)
} else {
// Variable not found - set to nil
ctx.stack.Set(boundName, nil)
}
}
// Evaluate children and return them (omitting the template tag)
evaluated, err := v.evaluateChildren(ctx, node, depth+1)
if err != nil {
return nil, err
}
return evaluated, nil
}
// Regular element node processing (no v-for)
hasVHtml := helpers.GetAttr(node, "v-html") != ""
var newNode *html.Node
if hasVHtml {
newNode = helpers.DeepCloneNode(node)
} else {
newNode = helpers.ShallowCloneWithAttrs(node)
}
if err := v.evalVHtml(ctx, newNode); err != nil {
return nil, err
}
if _, err := v.evalAttributes(ctx, newNode); err != nil {
return nil, err
}
if !hasVHtml {
ctx.PushTag(node.Data)
newChildren, err := v.evaluateChildren(ctx, node, depth+1)
ctx.PopTag()
if err != nil {
return nil, err
}
newNode.FirstChild = nil
for i, c := range newChildren {
if i == 0 {
newNode.FirstChild = c
} else {
newChildren[i-1].NextSibling = c
}
}
}
result = append(result, newNode)
return result, nil
}