-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy patheval_template.go
More file actions
184 lines (161 loc) · 4.97 KB
/
Copy patheval_template.go
File metadata and controls
184 lines (161 loc) · 4.97 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
package vuego
import (
"encoding/json"
"fmt"
"strings"
"golang.org/x/net/html"
"github.com/titpetric/vuego/internal/helpers"
)
// evalTemplate processes `<template>` entries from nodes.
// If a `<template>` element has a :required attribute, the value(s) must be provided.
// Values can be a single field or comma-separated list (e.g., :required="name,title,author").
// A template element may repeat `:required` as needed. If a value is not provided,
// template evaluation fails with an error that needs to be bubbled up preventing render.
// If a `<template>` element has an include attribute, it loads and processes the included file.
func (v *Vue) evalTemplate(ctx VueContext, nodes []*html.Node, componentData map[string]any, depth int) ([]*html.Node, error) {
// If no nodes, return empty
if len(nodes) == 0 {
return nodes, nil
}
// Check if first node is a template tag
if len(nodes) > 0 && nodes[0].Type == html.ElementNode && nodes[0].Data == "template" {
node := nodes[0]
// Check for include attribute - handle inclusion first
if helpers.HasAttr(node, "include") {
vars, err := v.evalAttributes(ctx, node)
if err != nil {
return nil, err
}
delete(vars, "include")
// auto decode params as json, e.g. `data="{...}"` or `[...]`
for k, v := range vars {
if vs, ok := v.(string); ok {
if strings.HasPrefix(vs, "{") || strings.HasPrefix(vs, "[") {
var out any
if err := json.Unmarshal([]byte(vs), &out); err == nil {
vars[k] = out
}
}
}
}
evaluated, err := v.evalInclude(ctx, node, vars, depth)
if err != nil {
return nil, err
}
return evaluated, nil
}
// Validate :required attributes
var requiredAttrs []string
for _, attr := range node.Attr {
if attr.Key == ":require" || attr.Key == ":required" {
// Support CSV format: :required="name,label,type"
fields := strings.Split(attr.Val, ",")
for _, field := range fields {
field = strings.TrimSpace(field)
if field != "" {
requiredAttrs = append(requiredAttrs, field)
}
}
}
}
// Check if all required attributes are provided
for _, required := range requiredAttrs {
if _, exists := componentData[required]; !exists {
return nil, fmt.Errorf("required attribute '%s' not provided", required)
}
}
// Evaluate v-html if attribute is provided
if err := v.evalVHtml(ctx, nodes[0]); err != nil {
return nil, err
}
// Check if v-html was evaluated (internal attribute set)
hasVHtml := false
for _, attr := range node.Attr {
if attr.Key == "data-v-html-content" {
hasVHtml = true
break
}
}
// If v-html was evaluated, return the template node for rendering to output its content
if hasVHtml {
return nodes, nil
}
// Evaluate attributes and set them in current scope
// This allows templates to set variables like <template count="123"> or <template :x="5">
for _, attr := range node.Attr {
key := attr.Key
val := strings.TrimSpace(attr.Val)
// Skip directive attributes
if strings.HasPrefix(key, "v-") {
continue
}
// Handle bound attributes (: or v-bind:)
if strings.HasPrefix(key, ":") {
boundName := key[1:]
// Skip special attributes like :required
if boundName == "require" || boundName == "required" {
continue
}
// Try evalPipe first to support funcmap functions like jsonFile()
pipe := parsePipeExpr(val)
result, err := v.evalPipe(ctx, pipe)
if err == nil {
ctx.stack.Set(boundName, result)
continue
}
// Fall back to expression evaluator for literals and arithmetic expressions
result, err = v.exprEval.Eval(val, ctx.ExprEnv())
if err == nil {
ctx.stack.Set(boundName, result)
continue
}
// Final fallback to variable resolution
valResolved, ok := ctx.stack.Resolve(val)
if ok {
ctx.stack.Set(boundName, valResolved)
} else {
ctx.stack.Set(boundName, nil)
}
continue
}
if strings.HasPrefix(key, "v-bind:") {
boundName := key[7:]
pipe := parsePipeExpr(val)
result, err := v.evalPipe(ctx, pipe)
if err == nil {
ctx.stack.Set(boundName, result)
continue
}
result, err = v.exprEval.Eval(val, ctx.ExprEnv())
if err == nil {
ctx.stack.Set(boundName, result)
continue
}
valResolved, ok := ctx.stack.Resolve(val)
if ok {
ctx.stack.Set(boundName, valResolved)
} else {
ctx.stack.Set(boundName, nil)
}
continue
}
// Handle plain attributes - auto decode JSON if applicable
if strings.HasPrefix(val, "{") || strings.HasPrefix(val, "[") {
var out any
if err := json.Unmarshal([]byte(val), &out); err == nil {
ctx.stack.Set(key, out)
continue
}
}
ctx.stack.Set(key, val)
}
// Otherwise, 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
}
// If no template tag, return nodes as-is
return nodes, nil
}