-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathselector.js
76 lines (67 loc) · 1.83 KB
/
selector.js
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
var csswhat = require('css-what').parse
module.exports = createMatcher
function createMatcher (sel) {
var selectors = csswhat(sel)
return function (stack) {
return selectors.some(function (parts) {
return match(stack, parts)
})
}
}
// check if a parsed selector `parts` matches the element ancestor list `stack`.
// `stack` elements are objects with at least a `.tagName`, and an object `.attrs`
function match (stack, parts) {
var si = stack.length - 1
for (var i = parts.length - 1; i >= 0; i--) {
if (!test()) return false
}
return true
function test () {
var part = parts[i]
var el = stack[si]
if (part.type === 'universal') {
return true
} if (part.type === 'attribute') {
return checkAttr(el, part)
} else if (part.type === 'tag') {
return checkTag(el, part)
} else if (part.type === 'child') {
si--
return true
} else if (part.type === 'descendant') {
// Move to the parent selector,
i--
// and keep walking up the DOM tree to check if it matches
si--
while (si >= 0) {
if (test()) return true
si--
}
return false
} else {
throw new Error('unknown selector: ' + JSON.stringify(part))
}
}
}
function checkAttr (el, part) {
if (part.action === 'exists') {
return part.name in el.attrs
}
var attr = el.attrs[part.name]
if (!attr) {
return false
} else if (part.action === 'start') {
attr = attr.slice(0, part.value.length)
} else if (part.action === 'end') {
attr = attr.slice(-part.value.length)
}
var value = part.value
if (part.ignoreCase) {
attr = attr.toLowerCase()
value = value.toLowerCase()
}
return part.name === 'class' ? attr.split(' ').includes(value) : attr === value
}
function checkTag (el, part) {
return el.tagName === part.name
}