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
|
import unicode
import tables
import strutils
import sequtils
import sugar
import streams
import css/selectorparser
import css/cssparser
import html/dom
func attrSelectorMatches(elem: Element, sel: Selector): bool =
case sel.rel
of ' ': return sel.attr in elem.attributes
of '=': return elem.attr(sel.attr) == sel.value
of '~': return sel.value in unicode.split(elem.attr(sel.attr))
of '|':
let val = elem.attr(sel.attr)
return val == sel.value or sel.value.startsWith(val & '-')
of '^': return elem.attr(sel.attr).startsWith(sel.value)
of '$': return elem.attr(sel.attr).endsWith(sel.value)
of '*': return elem.attr(sel.attr).contains(sel.value)
else: return false
func pseudoSelectorMatches(elem: Element, sel: Selector): bool =
case sel.pseudo
of PSEUDO_FIRST_CHILD: return elem.parentNode.firstElementChild == elem
of PSEUDO_LAST_CHILD: return elem.parentNode.lastElementChild == elem
of PSEUDO_ONLY_CHILD: return elem.parentNode.firstElementChild == elem and elem.parentNode.lastElementChild == elem
of PSEUDO_HOVER: return elem.hover
of PSEUDO_ROOT: return elem == elem.ownerDocument.root
of PSEUDO_NTH_CHILD: return int64(sel.pseudonum - 1) in elem.parentNode.children.low..elem.parentNode.children.high and elem.parentNode.children[int64(sel.pseudonum - 1)] == elem
func selectorsMatch*(elem: Element, selectors: SelectorList): bool
func funcSelectorMatches(elem: Element, sel: Selector): bool =
case sel.name
of "not":
for slist in sel.fsels:
if elem.selectorsMatch(slist):
return false
return true
of "is", "where":
for slist in sel.fsels:
if elem.selectorsMatch(slist):
return true
return false
else: discard
func combinatorSelectorMatches(elem: Element, sel: Selector): bool =
#combinator without at least two members makes no sense
assert sel.csels.len > 1
if elem.selectorsMatch(sel.csels[^1]):
var i = sel.csels.len - 2
case sel.ct
of DESCENDANT_COMBINATOR:
var e = elem.parentElement
while e != nil and i >= 0:
if e.selectorsMatch(sel.csels[i]):
dec i
e = e.parentElement
of CHILD_COMBINATOR:
var e = elem.parentElement
while e != nil and i >= 0:
if not e.selectorsMatch(sel.csels[i]):
return false
dec i
e = e.parentElement
of NEXT_SIBLING_COMBINATOR:
var e = elem.previousElementSibling
while e != nil and i >= 0:
if not e.selectorsMatch(sel.csels[i]):
return false
dec i
e = e.previousElementSibling
of SUBSEQ_SIBLING_COMBINATOR:
var e = elem.previousElementSibling
while e != nil and i >= 0:
if e.selectorsMatch(sel.csels[i]):
dec i
e = e.previousElementSibling
return i == -1
return false
func selectorMatches(elem: Element, sel: Selector): bool =
case sel.t
of TYPE_SELECTOR:
return elem.tagType == sel.tag
of CLASS_SELECTOR:
return sel.class in elem.classList
of ID_SELECTOR:
return sel.id == elem.id
of ATTR_SELECTOR:
return elem.attrSelectorMatches(sel)
of PSEUDO_SELECTOR:
return pseudoSelectorMatches(elem, sel)
of PSELEM_SELECTOR:
return true
of UNIVERSAL_SELECTOR:
return true
of FUNC_SELECTOR:
return funcSelectorMatches(elem, sel)
of COMBINATOR_SELECTOR:
return combinatorSelectorMatches(elem, sel)
func selectorsMatch*(elem: Element, selectors: SelectorList): bool =
for sel in selectors.sels:
if not selectorMatches(elem, sel):
return false
return true
func selectElems(document: Document, sel: Selector): seq[Element] =
case sel.t
of TYPE_SELECTOR:
return document.type_elements[sel.tag]
of ID_SELECTOR:
return document.id_elements.getOrDefault(sel.id, newSeq[Element]())
of CLASS_SELECTOR:
return document.class_elements.getOrDefault(sel.class, newSeq[Element]())
of UNIVERSAL_SELECTOR:
return document.all_elements
of ATTR_SELECTOR:
return document.all_elements.filter((elem) => attrSelectorMatches(elem, sel))
of PSEUDO_SELECTOR:
return document.all_elements.filter((elem) => pseudoSelectorMatches(elem, sel))
of PSELEM_SELECTOR:
return document.all_elements
of FUNC_SELECTOR:
return document.all_elements.filter((elem) => selectorMatches(elem, sel))
of COMBINATOR_SELECTOR:
return document.all_elements.filter((elem) => selectorMatches(elem, sel))
func selectElems(document: Document, selectors: SelectorList): seq[Element] =
assert(selectors.len > 0)
let sellist = optimizeSelectorList(selectors)
result = document.selectElems(selectors[0])
var i = 1
while i < sellist.len:
result = result.filter((elem) => selectorMatches(elem, sellist[i]))
inc i
proc querySelectorAll*(document: Document, q: string): seq[Element] =
let ss = newStringStream(q)
let cvals = parseListOfComponentValues(ss)
let selectors = parseSelectors(cvals)
for sel in selectors:
result.add(document.selectElems(sel))
proc querySelector*(document: Document, q: string): Element =
let elems = document.querySelectorAll(q)
if elems.len > 0:
return elems[0]
return nil
|