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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
|
import std/strutils
import chame/tags
import css/cssparser
import css/selectorparser
import html/catom
import html/dom
import utils/twtstr
# We use three match types.
# "mtTrue" and "mtFalse" are self-explanatory.
# "mtContinue" is like "mtFalse", but also modifies "depends". This
# "depends" change is only propagated at the end if no selector before
# the pseudo element matches the element, and the last match was
# "mtContinue".
#
# Since style is only recomputed (e.g. when the hovered element changes)
# for elements that are included in "depends", this has the effect of
# minimizing such recomputations to cases where it's really necessary.
type MatchType = enum
mtFalse, mtTrue, mtContinue
converter toMatchType(b: bool): MatchType =
return MatchType(b)
#TODO rfNone should match insensitively for certain properties
func matchesAttr(element: Element; sel: Selector): bool =
case sel.rel.t
of rtExists: return element.attrb(sel.attr)
of rtEquals:
case sel.rel.flag
of rfNone: return element.attr(sel.attr) == sel.value
of rfI: return element.attr(sel.attr).equalsIgnoreCase(sel.value)
of rfS: return element.attr(sel.attr) == sel.value
of rtToken:
let val = element.attr(sel.attr)
case sel.rel.flag
of rfNone: return sel.value in val.split(AsciiWhitespace)
of rfI:
let val = val.toLowerAscii()
let selval = sel.value.toLowerAscii()
return selval in val.split(AsciiWhitespace)
of rfS: return sel.value in val.split(AsciiWhitespace)
of rtBeginDash:
let val = element.attr(sel.attr)
case sel.rel.flag
of rfNone:
return val == sel.value or sel.value.startsWith(val & '-')
of rfI:
return val.equalsIgnoreCase(sel.value) or
sel.value.startsWithIgnoreCase(val & '-')
of rfS:
return val == sel.value or sel.value.startsWith(val & '-')
of rtStartsWith:
let val = element.attr(sel.attr)
case sel.rel.flag
of rfNone: return val.startsWith(sel.value)
of rfI: return val.startsWithIgnoreCase(sel.value)
of rfS: return val.startsWith(sel.value)
of rtEndsWith:
let val = element.attr(sel.attr)
case sel.rel.flag
of rfNone: return val.endsWith(sel.value)
of rfI: return val.endsWithIgnoreCase(sel.value)
of rfS: return val.endsWith(sel.value)
of rtContains:
let val = element.attr(sel.attr)
case sel.rel.flag
of rfNone: return val.contains(sel.value)
of rfI:
let val = val.toLowerAscii()
let selval = sel.value.toLowerAscii()
return val.contains(selval)
of rfS: return val.contains(sel.value)
func matches*(element: Element; cxsel: ComplexSelector;
depends: var DependencyInfo): bool
func matches(element: Element; slist: SelectorList;
depends: var DependencyInfo): bool =
for cxsel in slist:
if element.matches(cxsel, depends):
return true
return false
func matches(element: Element; pseudo: PseudoData;
depends: var DependencyInfo): MatchType =
case pseudo.t
of pcFirstChild: return element.parentNode.firstElementChild == element
of pcLastChild: return element.parentNode.lastElementChild == element
of pcFirstNode: return element.isFirstVisualNode()
of pcLastNode: return element.isLastVisualNode()
of pcOnlyChild:
return element.parentNode.firstElementChild == element and
element.parentNode.lastElementChild == element
of pcHover:
depends.add(element, dtHover)
if element.hover:
return mtTrue
return mtContinue
of pcRoot: return element == element.document.documentElement
of pcNthChild:
let A = pseudo.anb.A # step
let B = pseudo.anb.B # start
if pseudo.ofsels.len == 0:
let i = element.elIndex + 1
if A == 0:
return i == B
let j = (i - B)
if A < 0:
return j <= 0 and j mod A == 0
return j >= 0 and j mod A == 0
if element.matches(pseudo.ofsels, depends):
var i = 1
for child in element.parentNode.elementList:
if child == element:
if A == 0:
return i == B
let j = (i - B)
if A < 0:
return j <= 0 and j mod A == 0
return j >= 0 and j mod A == 0
if child.matches(pseudo.ofsels, depends):
inc i
return mtFalse
of pcNthLastChild:
let A = pseudo.anb.A # step
let B = pseudo.anb.B # start
if pseudo.ofsels.len == 0:
let last = element.parentNode.lastElementChild
let i = last.elIndex + 1 - element.elIndex
if A == 0:
return i == B
let j = (i - B)
if A < 0:
return j <= 0 and j mod A == 0
return j >= 0 and j mod A == 0
if element.matches(pseudo.ofsels, depends):
var i = 1
for child in element.parentNode.elementList_rev:
if child == element:
if A == 0:
return i == B
let j = (i - B)
if A < 0:
return j <= 0 and j mod A == 0
return j >= 0 and j mod A == 0
if child.matches(pseudo.ofsels, depends):
inc i
return mtFalse
of pcChecked:
if element.tagType == TAG_INPUT:
depends.add(element, dtChecked)
if HTMLInputElement(element).checked:
return mtTrue
elif element.tagType == TAG_OPTION:
depends.add(element, dtChecked)
if HTMLOptionElement(element).selected:
return mtTrue
return mtContinue
of pcFocus:
depends.add(element, dtFocus)
if element.document.focus == element:
return mtTrue
return mtContinue
of pcTarget:
depends.add(element, dtTarget)
if element.document.target == element:
return mtTrue
return mtContinue
of pcNot:
return not element.matches(pseudo.fsels, depends)
of pcIs, pcWhere:
return element.matches(pseudo.fsels, depends)
of pcLang:
return pseudo.s == "en" #TODO languages?
of pcLink:
return element.tagType in {TAG_A, TAG_AREA} and element.attrb(satHref)
of pcVisited:
return mtFalse
func matches(element: Element; sel: Selector; depends: var DependencyInfo):
MatchType =
case sel.t
of stType:
return element.localName == sel.tag
of stClass:
for it in element.classList:
if sel.class == it.toLowerAscii():
return mtTrue
return mtFalse
of stId:
return sel.id == element.id.toLowerAscii()
of stAttr:
return element.matchesAttr(sel)
of stPseudoClass:
return element.matches(sel.pseudo, depends)
of stPseudoElement:
return mtTrue
of stUniversal:
return mtTrue
func matches(element: Element; sels: CompoundSelector;
depends: var DependencyInfo): MatchType =
var res = mtTrue
for sel in sels:
case element.matches(sel, depends)
of mtFalse: return mtFalse
of mtTrue: discard
of mtContinue: res = mtContinue
return res
# Note: this modifies "depends".
func matches*(element: Element; cxsel: ComplexSelector;
depends: var DependencyInfo): bool =
var e = element
var pmatch = mtTrue
var mdepends = DependencyInfo.default
for i in countdown(cxsel.high, 0):
var match = mtFalse
let csel = cxsel[i]
case csel.ct
of ctNone:
match = e.matches(csel, mdepends)
of ctDescendant:
e = e.parentElement
while e != nil:
case e.matches(csel, mdepends)
of mtFalse: discard
of mtTrue:
match = mtTrue
break
of mtContinue: match = mtContinue # keep looking
e = e.parentElement
of ctChild:
e = e.parentElement
if e != nil:
match = e.matches(csel, mdepends)
of ctNextSibling:
let prev = e.previousElementSibling
if prev != nil:
e = prev
match = e.matches(csel, mdepends)
of ctSubsequentSibling:
let parent = e.parentNode
for j in countdown(e.index - 1, 0):
let child = parent.childList[j]
if child of Element:
let child = Element(child)
case child.matches(csel, mdepends)
of mtTrue:
e = child
match = mtTrue
break
of mtFalse: discard
of mtContinue: match = mtContinue # keep looking
if match == mtFalse:
return false # we can discard depends.
if pmatch == mtContinue and match == mtTrue or e == nil:
pmatch = mtContinue
break # we must update depends.
pmatch = match
depends.merge(mdepends)
if pmatch == mtContinue:
return false
return true
# Forward declaration hack
matchesImpl = func(element: Element; cxsels: seq[ComplexSelector]): bool
{.nimcall.} =
var dummy = DependencyInfo.default
return element.matches(cxsels, dummy)
|