-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselectors.go
More file actions
216 lines (187 loc) · 5.09 KB
/
selectors.go
File metadata and controls
216 lines (187 loc) · 5.09 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
package GoHtml
import (
"strings"
"golang.org/x/net/html"
)
type BasicSelector int
const (
Id BasicSelector = iota
Class
Tag
)
// Selector struct represents a single css selector
// Ex: .my-class, #video, div
type Selector struct {
selector string
selectorName string
selectorType BasicSelector
}
func matchNode(node *Node, basicSelectorName string, basicSelectorType BasicSelector) bool {
if basicSelectorName == "" {
return true
} else if node == nil {
return false
}
switch basicSelectorType {
case Id:
idName, _ := node.GetAttribute("id")
return idName == basicSelectorName
case Class:
classList := NewClassList()
classList.DecodeFrom(node)
return classList.Contains(basicSelectorName)
case Tag:
return node.GetTagName() == basicSelectorName
}
return false
}
// NewSelector takes a single css selector and returns a Selector struct.
// Selector string should be only of basic selector.
func NewSelector(selector string) Selector {
selector = strings.TrimSpace(html.EscapeString(selector))
selectorStruct := Selector{}
if len(selector) == 0 || (selector[0] == '.' || selector[0] == '#') && len(selector) <= 1 {
return selectorStruct
}
switch selector[0] {
case '.':
selectorStruct.selectorType = Class
case '#':
selectorStruct.selectorType = Id
default:
selectorStruct.selectorType = Tag
}
//selectorStruct.selector = strings.ToLower(selector)
if selectorStruct.selectorType != Tag {
selectorStruct.selectorName = selector[1:]
} else {
selectorStruct.selectorName = selector
}
return selectorStruct
}
type Combinator int
const (
Descendant Combinator = iota
Child
NextSibling
SubsequentSibling
//if no combinator
NoneCombinator
)
// CombinatorEl is used to represent selectors that are around a combinator.
type CombinatorEl struct {
Type Combinator
Selector1 Selector
Selector2 Selector
}
// This takes a selector or combinators and selectors and then returns a slice of CombinatorEl.
func TokenizeSelectorsAndCombinators(selector string) []CombinatorEl {
iter := func(yield func(string) bool) {
currentStr := ""
for _, char := range selector {
switch char {
case ' ', '>', '+', '~':
if !yield(currentStr) || !yield(string(char)) {
return
}
currentStr = ""
default:
currentStr += string(char)
}
}
yield(currentStr)
}
list := make([]CombinatorEl, 0, 1)
currentCombinator := *new(CombinatorEl)
currentCombinator.Selector1 = NewSelector("")
currentCombinator.Type = NoneCombinator
for str := range iter {
if strings.TrimSpace(str) == "" {
continue
}
switch str {
case "+":
currentCombinator.Type = NextSibling
case ">":
currentCombinator.Type = Child
case "~":
currentCombinator.Type = SubsequentSibling
default:
newSelector := NewSelector(str)
currentCombinator.Selector2 = newSelector
list = append(list, currentCombinator)
currentCombinator = *new(CombinatorEl)
currentCombinator.Selector1 = newSelector
}
}
if len(list) == 1 {
list[0].Type = NoneCombinator
}
return list
}
func (ce *CombinatorEl) getMatchingNode(node *Node) *Node {
switch ce.Type {
case Descendant:
return ce.getDescended(node)
case Child:
return ce.getDirectChild(node)
case NextSibling:
return ce.getNextSibling(node)
case SubsequentSibling:
return ce.getSubsequentSibling(node)
case NoneCombinator:
if matchNode(node, ce.Selector2.selectorName, ce.Selector2.selectorType) {
return node
}
}
return nil
}
// isDescended returns wether the given node is a ce.Selector2 and descended of ce.Selector1.
func (ce *CombinatorEl) getDescended(node *Node) *Node {
if !matchNode(node, ce.Selector2.selectorName, ce.Selector2.selectorType) {
return nil
}
parentNode := node.GetParent()
for parentNode != nil {
if matchNode(parentNode, ce.Selector1.selectorName, ce.Selector1.selectorType) {
return parentNode
}
parentNode = parentNode.GetParent()
}
return nil
}
// isDirectChild returns whether the given node is a direct child of ce.Selector1 and node is of ce.Selector2
func (ce *CombinatorEl) getDirectChild(node *Node) *Node {
if node == nil {
return nil
}
if matchNode(node, ce.Selector2.selectorName, ce.Selector2.selectorType) &&
matchNode(node.GetParent(), ce.Selector1.selectorName, ce.Selector1.selectorType) {
return node.GetParent()
}
return nil
}
// isNextSibling return whether the given node is of ce.Selector2 and next sibling of ce.Selector1
func (ce *CombinatorEl) getNextSibling(node *Node) *Node {
if node == nil {
return nil
}
if matchNode(node, ce.Selector2.selectorName, ce.Selector2.selectorType) &&
matchNode(node.GetPreviousNode(), ce.Selector1.selectorName, ce.Selector1.selectorType) {
return node.GetPreviousNode()
}
return nil
}
func (ce *CombinatorEl) getSubsequentSibling(node *Node) *Node {
if node == nil || !matchNode(node, ce.Selector2.selectorName, ce.Selector2.selectorType) {
return nil
}
traverser := NewTraverser(node)
for traverser.GetCurrentNode() != nil {
if matchNode(traverser.GetCurrentNode(), ce.Selector1.selectorName, ce.Selector1.selectorType) {
return traverser.GetCurrentNode()
}
traverser.Previous()
}
return nil
}