-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathelems.go
More file actions
163 lines (146 loc) · 3.76 KB
/
Copy pathelems.go
File metadata and controls
163 lines (146 loc) · 3.76 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
package rx
import (
"errors"
"io"
"strings"
"sync"
)
var cachedParseResults = make(map[string]qVM)
var cachedParseResultsMx sync.Mutex
// Get parses the Get text in tpl, and returns a node matching it.
// This is a short hand for the [GetNode], [Node.AddAttr], … constructors, optimized for easy definition.
// The node returned can be further modified.
//
// When used in the short form (defining a single element), the element need not be closed:
//
// Get(`<div class="flex">`)
//
// Get can also create a full tree:
//
// Get(`<div class="flex"><button>Click me</button></div>`)
//
// Get panics if the template is not valid, and should not be used for untrusted inputs.
// Get uses a caching mechanism to prevent unecessary parsing, so it works best with static strings.
func Get(tpl string) *Node {
cachedParseResultsMx.Lock()
defer cachedParseResultsMx.Unlock()
if ctor := cachedParseResults[tpl]; ctor != nil {
return ctor.run(tpl)
}
var vm qVM
z := newTokenizer(tpl)
for {
tt := z.Next()
switch tt {
case errorToken:
if errors.Is(z.Err(), io.EOF) {
cachedParseResults[tpl] = vm
return vm.run(tpl)
}
panic(z.Err())
case textToken:
val := z.Text()
vm = append(vm, qVMOp{Op: qText, R1: val.Start, R2: val.End})
case startTagToken:
tn, hasattr := z.TagName()
vm = append(vm, qVMOp{Op: qNode, R1: tn.Start, R2: tn.End})
for hasattr {
var key, val span
key, val, hasattr = z.TagAttr()
if tpl[key.Start:key.End] == "class" {
vm = append(vm, qVMOp{Op: qClasses, R1: val.Start, R2: val.End})
} else {
vm = append(vm, qVMOp{Op: qAttrs, R1: key.Start, R2: key.End, R3: val.Start, R4: val.End})
}
}
case selfClosingTagToken:
tn, hasattr := z.TagName()
vm = append(vm, qVMOp{Op: qNode, R1: tn.Start, R2: tn.End})
for hasattr {
var key, val span
key, val, hasattr = z.TagAttr()
if tpl[key.Start:key.End] == "class" {
vm = append(vm, qVMOp{Op: qClasses, R1: val.Start, R2: val.End})
} else {
vm = append(vm, qVMOp{Op: qAttrs, R1: key.Start, R2: key.End, R3: val.Start, R4: val.End})
}
}
vm = append(vm, qVMOp{Op: qTerm})
case endTagToken:
vm = append(vm, qVMOp{Op: qTerm})
}
}
}
type qVMOp struct {
Op byte
R1, R2 int // op-dependent
R3, R4 int // op-dependent
}
const (
qTerm byte = iota
qNode
qAttrs
qClasses
qText
)
type qVM []qVMOp
func (vm qVM) run(tpl string) *Node {
p, stack := new(Node), make([]*Node, 0, 16)
for _, op := range vm {
switch op.Op {
case qTerm:
p, stack = pop(stack)
case qNode:
if p.TagName == "" {
p.TagName = tpl[op.R1:op.R2]
stack = append(stack, p) // store root twice so pop in qTerm always a good op
continue
}
c := GetNode(tpl[op.R1:op.R2])
p.AddChildren(c)
p, stack = c, append(stack, p)
case qAttrs:
p.AddAttr(tpl[op.R1:op.R2], tpl[op.R3:op.R4])
case qClasses:
p.Classes = tpl[op.R1:op.R2]
case qText:
p.Text = unescape(tpl[op.R1:op.R2])
}
}
if p.TagName == "" {
panic("invalid tag")
}
return p
}
func unescape(s string) string {
result := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
if s[i] == '&' {
switch {
case strings.HasPrefix(s[i:], "<"):
result = append(result, '<')
i += 3
case strings.HasPrefix(s[i:], ">"):
result = append(result, '>')
i += 3
case strings.HasPrefix(s[i:], "&"):
result = append(result, '&')
i += 4
case strings.HasPrefix(s[i:], """):
result = append(result, '"')
i += 5
case strings.HasPrefix(s[i:], "'"):
result = append(result, '\'')
i += 5
default:
result = append(result, s[i])
}
} else {
result = append(result, s[i])
}
}
return string(result)
}
func pop(stack []*Node) (*Node, []*Node) {
return stack[len(stack)-1], stack[:len(stack)-1]
}