@asamuzakjp/dom-selector 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/js/matcher.js +349 -304
- package/src/js/parser.js +5 -4
- package/types/js/matcher.d.ts +7 -6
- package/types/js/parser.d.ts +3 -1
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"chai": "^4.3.7",
|
|
27
27
|
"eslint": "^8.40.0",
|
|
28
28
|
"eslint-config-standard": "^17.0.0",
|
|
29
|
-
"eslint-plugin-jsdoc": "^44.2.
|
|
29
|
+
"eslint-plugin-jsdoc": "^44.2.3",
|
|
30
30
|
"eslint-plugin-regexp": "^1.15.0",
|
|
31
31
|
"eslint-plugin-unicorn": "^47.0.0",
|
|
32
32
|
"jsdom": "^22.0.0",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"test": "c8 --reporter=text mocha --exit test/**/*.test.js",
|
|
41
41
|
"tsc": "npx tsc"
|
|
42
42
|
},
|
|
43
|
-
"version": "0.
|
|
43
|
+
"version": "0.5.0"
|
|
44
44
|
}
|
package/src/js/matcher.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* import */
|
|
6
|
-
const { parseSelector,
|
|
6
|
+
const { generateCSS, parseSelector, walkAST } = require('./parser.js');
|
|
7
7
|
|
|
8
8
|
/* constants */
|
|
9
9
|
const {
|
|
@@ -11,34 +11,59 @@ const {
|
|
|
11
11
|
NTH, PSEUDO_CLASS_SELECTOR, TYPE_SELECTOR
|
|
12
12
|
} = require('./constant.js');
|
|
13
13
|
const ELEMENT_NODE = 1;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// FIXME: custom element name is not fully implemented
|
|
15
|
+
// @see https://html.spec.whatwg.org/#valid-custom-element-name
|
|
16
|
+
const HTML_CUSTOM_ELEMENT = /^[a-z][\d._a-z]*-[\d\-._a-z]*$/;
|
|
17
|
+
const HTML_FORM_INPUT = /^(?:(?:inpu|selec)t|textarea)$/;
|
|
18
|
+
const HTML_FORM_PARTS = /^(?:button|fieldset|opt(?:group|ion))$/;
|
|
19
|
+
const HTML_INTERACT = /^d(?:etails|ialog)$/;
|
|
20
|
+
const PSEUDO_FUNC = /^(?:(?:ha|i)s|not|where)$/;
|
|
21
|
+
const PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
24
|
* collect nth child
|
|
25
|
+
* @param {object} anb - An+B options
|
|
26
|
+
* @param {number} anb.a - a
|
|
27
|
+
* @param {number} anb.b - b
|
|
28
|
+
* @param {boolean} [anb.reverse] - reverse order
|
|
29
|
+
* @param {string} [anb.selector] - CSS selector
|
|
19
30
|
* @param {object} node - Element node
|
|
20
|
-
* @param {object} opt - options
|
|
21
|
-
* @param {number} opt.a - a
|
|
22
|
-
* @param {number} opt.b - b
|
|
23
|
-
* @param {boolean} [opt.reverse] - reverse order
|
|
24
31
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
25
32
|
*/
|
|
26
|
-
const collectNthChild = (
|
|
27
|
-
const {
|
|
28
|
-
const {
|
|
29
|
-
const
|
|
30
|
-
if (nodeType === ELEMENT_NODE
|
|
31
|
-
Number.isInteger(a) && Number.isInteger(b)) {
|
|
33
|
+
const collectNthChild = (anb = {}, node = {}) => {
|
|
34
|
+
const { a, b, reverse, selector } = anb;
|
|
35
|
+
const { nodeType, ownerDocument, parentNode } = node;
|
|
36
|
+
const matched = [];
|
|
37
|
+
if (Number.isInteger(a) && Number.isInteger(b) && nodeType === ELEMENT_NODE) {
|
|
32
38
|
const arr = [...parentNode.children];
|
|
33
39
|
if (reverse) {
|
|
34
40
|
arr.reverse();
|
|
35
41
|
}
|
|
36
42
|
const l = arr.length;
|
|
37
|
-
|
|
43
|
+
const items = [];
|
|
44
|
+
if (selector) {
|
|
45
|
+
const a = new Matcher(selector, ownerDocument).querySelectorAll();
|
|
46
|
+
if (a.length) {
|
|
47
|
+
items.push(...a);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// :first-child, :last-child, :nth-child(0 of S)
|
|
38
51
|
if (a === 0) {
|
|
39
52
|
if (b >= 0 && b < l) {
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
if (items.length) {
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < l) {
|
|
56
|
+
const current = arr[i];
|
|
57
|
+
if (items.includes(current)) {
|
|
58
|
+
matched.push(current);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
const current = arr[b];
|
|
65
|
+
matched.push(current);
|
|
66
|
+
}
|
|
42
67
|
}
|
|
43
68
|
// :nth-child()
|
|
44
69
|
} else {
|
|
@@ -49,12 +74,25 @@ const collectNthChild = (node = {}, opt = {}) => {
|
|
|
49
74
|
nth += (++n * a);
|
|
50
75
|
}
|
|
51
76
|
}
|
|
52
|
-
if (nth >= 0) {
|
|
77
|
+
if (nth >= 0 && nth < l) {
|
|
53
78
|
let i = 0;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
let j = a > 0 ? 0 : b - 1;
|
|
80
|
+
while (i < l && nth >= 0 && nth < l) {
|
|
81
|
+
const current = arr[i];
|
|
82
|
+
if (items.length) {
|
|
83
|
+
if (items.includes(current)) {
|
|
84
|
+
if (j === nth) {
|
|
85
|
+
matched.push(current);
|
|
86
|
+
nth += a;
|
|
87
|
+
}
|
|
88
|
+
if (a > 0) {
|
|
89
|
+
j++;
|
|
90
|
+
} else {
|
|
91
|
+
j--;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else if (i === nth) {
|
|
95
|
+
matched.push(current);
|
|
58
96
|
nth += a;
|
|
59
97
|
}
|
|
60
98
|
i++;
|
|
@@ -62,24 +100,23 @@ const collectNthChild = (node = {}, opt = {}) => {
|
|
|
62
100
|
}
|
|
63
101
|
}
|
|
64
102
|
}
|
|
65
|
-
return [...
|
|
103
|
+
return [...new Set(matched)];
|
|
66
104
|
};
|
|
67
105
|
|
|
68
106
|
/**
|
|
69
107
|
* collect nth of type
|
|
108
|
+
* @param {object} anb - An+B options
|
|
109
|
+
* @param {number} anb.a - a
|
|
110
|
+
* @param {number} anb.b - b
|
|
111
|
+
* @param {boolean} [anb.reverse] - reverse order
|
|
70
112
|
* @param {object} node - Element node
|
|
71
|
-
* @param {object} opt - options
|
|
72
|
-
* @param {number} opt.a - a
|
|
73
|
-
* @param {number} opt.b - b
|
|
74
|
-
* @param {boolean} [opt.reverse] - reverse order
|
|
75
113
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
76
114
|
*/
|
|
77
|
-
const collectNthOfType = (
|
|
115
|
+
const collectNthOfType = (anb = {}, node = {}) => {
|
|
116
|
+
const { a, b, reverse } = anb;
|
|
78
117
|
const { localName, nodeType, parentNode, prefix } = node;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (nodeType === ELEMENT_NODE &&
|
|
82
|
-
Number.isInteger(a) && Number.isInteger(b)) {
|
|
118
|
+
const matched = [];
|
|
119
|
+
if (Number.isInteger(a) && Number.isInteger(b) && nodeType === ELEMENT_NODE) {
|
|
83
120
|
const arr = [...parentNode.children];
|
|
84
121
|
if (reverse) {
|
|
85
122
|
arr.reverse();
|
|
@@ -91,11 +128,11 @@ const collectNthOfType = (node = {}, opt = {}) => {
|
|
|
91
128
|
let i = 0;
|
|
92
129
|
let j = 0;
|
|
93
130
|
while (i < l) {
|
|
94
|
-
const
|
|
95
|
-
const { localName: itemLocalName, prefix: itemPrefix } =
|
|
131
|
+
const current = arr[i];
|
|
132
|
+
const { localName: itemLocalName, prefix: itemPrefix } = current;
|
|
96
133
|
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
97
134
|
if (j === b) {
|
|
98
|
-
|
|
135
|
+
matched.push(current);
|
|
99
136
|
break;
|
|
100
137
|
}
|
|
101
138
|
j++;
|
|
@@ -111,25 +148,104 @@ const collectNthOfType = (node = {}, opt = {}) => {
|
|
|
111
148
|
nth += a;
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
|
-
if (nth >= 0) {
|
|
151
|
+
if (nth >= 0 && nth < l) {
|
|
115
152
|
let i = 0;
|
|
116
|
-
let j = 0;
|
|
117
|
-
while (i < l && nth < l) {
|
|
118
|
-
const
|
|
119
|
-
const { localName: itemLocalName, prefix: itemPrefix } =
|
|
153
|
+
let j = a > 0 ? 0 : b - 1;
|
|
154
|
+
while (i < l && nth >= 0 && nth < l) {
|
|
155
|
+
const current = arr[i];
|
|
156
|
+
const { localName: itemLocalName, prefix: itemPrefix } = current;
|
|
120
157
|
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
121
158
|
if (j === nth) {
|
|
122
|
-
|
|
159
|
+
matched.push(current);
|
|
123
160
|
nth += a;
|
|
124
161
|
}
|
|
125
|
-
|
|
162
|
+
if (a > 0) {
|
|
163
|
+
j++;
|
|
164
|
+
} else {
|
|
165
|
+
j--;
|
|
166
|
+
}
|
|
126
167
|
}
|
|
127
168
|
i++;
|
|
128
169
|
}
|
|
129
170
|
}
|
|
130
171
|
}
|
|
131
172
|
}
|
|
132
|
-
return [...
|
|
173
|
+
return [...new Set(matched)];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* match An+B
|
|
178
|
+
* @param {string} nthName - nth pseudo-class name
|
|
179
|
+
* @param {object} ast - AST
|
|
180
|
+
* @param {object} node - Element node
|
|
181
|
+
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
182
|
+
*/
|
|
183
|
+
const matchAnPlusB = (nthName, ast = {}, node = {}) => {
|
|
184
|
+
const matched = [];
|
|
185
|
+
if (typeof nthName === 'string') {
|
|
186
|
+
nthName = nthName.trim();
|
|
187
|
+
if (PSEUDO_NTH.test(nthName)) {
|
|
188
|
+
const {
|
|
189
|
+
nth: {
|
|
190
|
+
a,
|
|
191
|
+
b,
|
|
192
|
+
name: identName
|
|
193
|
+
},
|
|
194
|
+
selector: astSelector,
|
|
195
|
+
type: astType
|
|
196
|
+
} = ast;
|
|
197
|
+
const { nodeType } = node;
|
|
198
|
+
if (astType === NTH && nodeType === ELEMENT_NODE) {
|
|
199
|
+
const anbMap = new Map();
|
|
200
|
+
if (identName) {
|
|
201
|
+
if (identName === 'even') {
|
|
202
|
+
anbMap.set('a', 2);
|
|
203
|
+
anbMap.set('b', 0);
|
|
204
|
+
} else if (identName === 'odd') {
|
|
205
|
+
anbMap.set('a', 2);
|
|
206
|
+
anbMap.set('b', 1);
|
|
207
|
+
}
|
|
208
|
+
if (/last/.test(nthName)) {
|
|
209
|
+
anbMap.set('reverse', true);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
if (typeof a === 'string' && /-?\d+/.test(a)) {
|
|
213
|
+
anbMap.set('a', a * 1);
|
|
214
|
+
} else {
|
|
215
|
+
anbMap.set('a', 0);
|
|
216
|
+
}
|
|
217
|
+
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
218
|
+
anbMap.set('b', b * 1);
|
|
219
|
+
} else {
|
|
220
|
+
anbMap.set('b', 0);
|
|
221
|
+
}
|
|
222
|
+
if (/last/.test(nthName)) {
|
|
223
|
+
anbMap.set('reverse', true);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (anbMap.has('a') && anbMap.has('b')) {
|
|
227
|
+
if (/^nth-(?:last-)?child$/.test(nthName)) {
|
|
228
|
+
if (astSelector) {
|
|
229
|
+
const css = generateCSS(astSelector);
|
|
230
|
+
anbMap.set('selector', css);
|
|
231
|
+
}
|
|
232
|
+
const anb = Object.fromEntries(anbMap);
|
|
233
|
+
const arr = collectNthChild(anb, node);
|
|
234
|
+
if (arr.length) {
|
|
235
|
+
matched.push(...arr);
|
|
236
|
+
}
|
|
237
|
+
} else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
|
|
238
|
+
const anb = Object.fromEntries(anbMap);
|
|
239
|
+
const arr = collectNthOfType(anb, node);
|
|
240
|
+
if (arr.length) {
|
|
241
|
+
matched.push(...arr);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return [...new Set(matched)];
|
|
133
249
|
};
|
|
134
250
|
|
|
135
251
|
/**
|
|
@@ -196,7 +312,7 @@ const matchClassSelector = (ast = {}, node = {}) => {
|
|
|
196
312
|
* @param {object} node - Element node
|
|
197
313
|
* @returns {?object} - matched node
|
|
198
314
|
*/
|
|
199
|
-
const
|
|
315
|
+
const matchIDSelector = (ast = {}, node = {}) => {
|
|
200
316
|
const { name: astName, type: astType } = ast;
|
|
201
317
|
const { id, nodeType } = node;
|
|
202
318
|
let res;
|
|
@@ -337,41 +453,35 @@ const matchAttributeSelector = (ast = {}, node = {}) => {
|
|
|
337
453
|
break;
|
|
338
454
|
case '|=':
|
|
339
455
|
if (attrValue) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
456
|
+
const item = attrValues.find(v =>
|
|
457
|
+
(v === attrValue || v.startsWith(`${attrValue}-`))
|
|
458
|
+
);
|
|
459
|
+
if (item) {
|
|
460
|
+
res = node;
|
|
345
461
|
}
|
|
346
462
|
}
|
|
347
463
|
break;
|
|
348
464
|
case '^=':
|
|
349
465
|
if (attrValue) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
466
|
+
const item = attrValues.find(v => v.startsWith(`${attrValue}`));
|
|
467
|
+
if (item) {
|
|
468
|
+
res = node;
|
|
355
469
|
}
|
|
356
470
|
}
|
|
357
471
|
break;
|
|
358
472
|
case '$=':
|
|
359
473
|
if (attrValue) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
474
|
+
const item = attrValues.find(v => v.endsWith(`${attrValue}`));
|
|
475
|
+
if (item) {
|
|
476
|
+
res = node;
|
|
365
477
|
}
|
|
366
478
|
}
|
|
367
479
|
break;
|
|
368
480
|
case '*=':
|
|
369
481
|
if (attrValue) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
482
|
+
const item = attrValues.find(v => v.includes(`${attrValue}`));
|
|
483
|
+
if (item) {
|
|
484
|
+
res = node;
|
|
375
485
|
}
|
|
376
486
|
}
|
|
377
487
|
break;
|
|
@@ -384,89 +494,7 @@ const matchAttributeSelector = (ast = {}, node = {}) => {
|
|
|
384
494
|
};
|
|
385
495
|
|
|
386
496
|
/**
|
|
387
|
-
* match
|
|
388
|
-
* @param {string} nthName - nth pseudo class name
|
|
389
|
-
* @param {object} ast - AST
|
|
390
|
-
* @param {object} node - Element node
|
|
391
|
-
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
392
|
-
*/
|
|
393
|
-
const matchAnPlusB = (nthName, ast = {}, node = {}) => {
|
|
394
|
-
const res = new Set();
|
|
395
|
-
if (typeof nthName === 'string') {
|
|
396
|
-
nthName = nthName.trim();
|
|
397
|
-
if (REG_PSEUDO_NTH.test(nthName)) {
|
|
398
|
-
const {
|
|
399
|
-
nth: {
|
|
400
|
-
a,
|
|
401
|
-
b,
|
|
402
|
-
name: identName
|
|
403
|
-
},
|
|
404
|
-
selector: astSelector,
|
|
405
|
-
type: astType
|
|
406
|
-
} = ast;
|
|
407
|
-
const { nodeType } = node;
|
|
408
|
-
if (astType === NTH && nodeType === ELEMENT_NODE) {
|
|
409
|
-
/*
|
|
410
|
-
// FIXME:
|
|
411
|
-
// :nth-child(An+B of S)
|
|
412
|
-
if (astSelector) {
|
|
413
|
-
}
|
|
414
|
-
*/
|
|
415
|
-
if (!astSelector) {
|
|
416
|
-
const optMap = new Map();
|
|
417
|
-
if (identName) {
|
|
418
|
-
if (identName === 'even') {
|
|
419
|
-
optMap.set('a', 2);
|
|
420
|
-
optMap.set('b', 0);
|
|
421
|
-
} else if (identName === 'odd') {
|
|
422
|
-
optMap.set('a', 2);
|
|
423
|
-
optMap.set('b', 1);
|
|
424
|
-
}
|
|
425
|
-
if (/last/.test(nthName)) {
|
|
426
|
-
optMap.set('reverse', true);
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
if (typeof a === 'string' && /-?\d+/.test(a)) {
|
|
430
|
-
optMap.set('a', a * 1);
|
|
431
|
-
} else {
|
|
432
|
-
optMap.set('a', 0);
|
|
433
|
-
}
|
|
434
|
-
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
435
|
-
optMap.set('b', b * 1);
|
|
436
|
-
} else {
|
|
437
|
-
optMap.set('b', 0);
|
|
438
|
-
}
|
|
439
|
-
if (/last/.test(nthName)) {
|
|
440
|
-
optMap.set('reverse', true);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
if (optMap.size > 1) {
|
|
444
|
-
const opt = Object.fromEntries(optMap);
|
|
445
|
-
if (/^nth-(?:last-)?child$/.test(nthName)) {
|
|
446
|
-
const arr = collectNthChild(node, opt);
|
|
447
|
-
if (arr.length) {
|
|
448
|
-
for (const i of arr) {
|
|
449
|
-
res.add(i);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
} else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
|
|
453
|
-
const arr = collectNthOfType(node, opt);
|
|
454
|
-
if (arr.length) {
|
|
455
|
-
for (const i of arr) {
|
|
456
|
-
res.add(i);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return [...res];
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* match language pseudo class
|
|
497
|
+
* match language pseudo-class
|
|
470
498
|
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
|
|
471
499
|
* @param {object} ast - AST
|
|
472
500
|
* @param {object} node - Element node
|
|
@@ -477,16 +505,16 @@ const matchLanguagePseudoClass = (ast = {}, node = {}) => {
|
|
|
477
505
|
const { lang, nodeType } = node;
|
|
478
506
|
let res;
|
|
479
507
|
if (astType === IDENTIFIER && nodeType === ELEMENT_NODE) {
|
|
480
|
-
//
|
|
481
|
-
/*
|
|
508
|
+
// TBD: what about deprecated xml:lang?
|
|
482
509
|
if (astName === '') {
|
|
483
|
-
if (
|
|
510
|
+
if (node.getAttribute('lang') === '') {
|
|
484
511
|
res = node;
|
|
485
512
|
}
|
|
486
513
|
} else if (astName === '*') {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
514
|
+
if (!node.hasAttribute('lang')) {
|
|
515
|
+
res = node;
|
|
516
|
+
}
|
|
517
|
+
} else if (/[A-Za-z\d-]+/.test(astName)) {
|
|
490
518
|
const codePart = '(?:-[A-Za-z\\d]+)?';
|
|
491
519
|
let reg;
|
|
492
520
|
if (/-/.test(astName)) {
|
|
@@ -523,7 +551,8 @@ const matchLanguagePseudoClass = (ast = {}, node = {}) => {
|
|
|
523
551
|
};
|
|
524
552
|
|
|
525
553
|
/**
|
|
526
|
-
* match pseudo
|
|
554
|
+
* match pseudo-class selector
|
|
555
|
+
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
527
556
|
* @param {object} ast - AST
|
|
528
557
|
* @param {object} node - Element node
|
|
529
558
|
* @param {object} [refPoint] - reference point
|
|
@@ -535,38 +564,36 @@ const matchPseudoClassSelector = (
|
|
|
535
564
|
refPoint = {}
|
|
536
565
|
) => {
|
|
537
566
|
const { children: astChildren, name: astName, type: astType } = ast;
|
|
538
|
-
const { nodeType, ownerDocument } = node;
|
|
539
|
-
const
|
|
567
|
+
const { localName, nodeType, ownerDocument, parentNode } = node;
|
|
568
|
+
const matched = [];
|
|
540
569
|
if (astType === PSEUDO_CLASS_SELECTOR && nodeType === ELEMENT_NODE) {
|
|
541
570
|
if (Array.isArray(astChildren)) {
|
|
542
571
|
const [astChildAst] = astChildren;
|
|
543
572
|
// :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
|
|
544
|
-
if (
|
|
573
|
+
if (PSEUDO_NTH.test(astName)) {
|
|
545
574
|
const arr = matchAnPlusB(astName, astChildAst, node);
|
|
546
575
|
if (arr.length) {
|
|
547
|
-
|
|
548
|
-
res.add(i);
|
|
549
|
-
}
|
|
576
|
+
matched.push(...arr);
|
|
550
577
|
}
|
|
551
578
|
} else {
|
|
552
579
|
switch (astName) {
|
|
553
580
|
case 'dir':
|
|
554
581
|
if (astChildAst.name === node.dir) {
|
|
555
|
-
|
|
582
|
+
matched.push(node);
|
|
556
583
|
}
|
|
557
584
|
break;
|
|
558
585
|
case 'lang':
|
|
559
586
|
if (matchLanguagePseudoClass(astChildAst, node)) {
|
|
560
|
-
|
|
587
|
+
matched.push(node);
|
|
561
588
|
}
|
|
562
589
|
break;
|
|
563
590
|
case 'current':
|
|
564
591
|
case 'nth-col':
|
|
565
592
|
case 'nth-last-col':
|
|
566
|
-
console.warn(`Unsupported pseudo
|
|
593
|
+
console.warn(`Unsupported pseudo-class ${astName}`);
|
|
567
594
|
break;
|
|
568
595
|
default:
|
|
569
|
-
console.warn(`Unknown pseudo
|
|
596
|
+
console.warn(`Unknown pseudo-class ${astName}`);
|
|
570
597
|
}
|
|
571
598
|
}
|
|
572
599
|
} else {
|
|
@@ -575,18 +602,18 @@ const matchPseudoClassSelector = (
|
|
|
575
602
|
switch (astName) {
|
|
576
603
|
case 'any-link':
|
|
577
604
|
case 'link':
|
|
578
|
-
//
|
|
605
|
+
// TBD: what about namespaced href? e.g. xlink:href
|
|
579
606
|
if (node.hasAttribute('href')) {
|
|
580
|
-
|
|
607
|
+
matched.push(node);
|
|
581
608
|
}
|
|
582
609
|
break;
|
|
583
610
|
case 'local-link':
|
|
584
|
-
//
|
|
611
|
+
// TBD: what about namespaced href? e.g. xlink:href
|
|
585
612
|
if (node.hasAttribute('href')) {
|
|
586
613
|
const attrURL = new URL(node.getAttribute('href'), docURL.href);
|
|
587
614
|
if (attrURL.origin === docURL.origin &&
|
|
588
615
|
attrURL.pathname === docURL.pathname) {
|
|
589
|
-
|
|
616
|
+
matched.push(node);
|
|
590
617
|
}
|
|
591
618
|
}
|
|
592
619
|
break;
|
|
@@ -595,7 +622,7 @@ const matchPseudoClassSelector = (
|
|
|
595
622
|
break;
|
|
596
623
|
case 'target':
|
|
597
624
|
if (docURL.hash && node.id && docURL.hash === `#${node.id}`) {
|
|
598
|
-
|
|
625
|
+
matched.push(node);
|
|
599
626
|
}
|
|
600
627
|
break;
|
|
601
628
|
case 'target-within':
|
|
@@ -604,7 +631,7 @@ const matchPseudoClassSelector = (
|
|
|
604
631
|
let current = ownerDocument.getElementById(hash);
|
|
605
632
|
while (current) {
|
|
606
633
|
if (current === node) {
|
|
607
|
-
|
|
634
|
+
matched.push(node);
|
|
608
635
|
break;
|
|
609
636
|
}
|
|
610
637
|
current = current.parentNode;
|
|
@@ -614,22 +641,22 @@ const matchPseudoClassSelector = (
|
|
|
614
641
|
case 'scope':
|
|
615
642
|
if (refPoint?.nodeType === ELEMENT_NODE) {
|
|
616
643
|
if (node === refPoint) {
|
|
617
|
-
|
|
644
|
+
matched.push(node);
|
|
618
645
|
}
|
|
619
646
|
} else if (node === root) {
|
|
620
|
-
|
|
647
|
+
matched.push(node);
|
|
621
648
|
}
|
|
622
649
|
break;
|
|
623
650
|
case 'focus':
|
|
624
651
|
if (node === ownerDocument.activeElement) {
|
|
625
|
-
|
|
652
|
+
matched.push(node);
|
|
626
653
|
}
|
|
627
654
|
break;
|
|
628
655
|
case 'focus-within': {
|
|
629
656
|
let current = ownerDocument.activeElement;
|
|
630
657
|
while (current) {
|
|
631
658
|
if (current === node) {
|
|
632
|
-
|
|
659
|
+
matched.push(node);
|
|
633
660
|
break;
|
|
634
661
|
}
|
|
635
662
|
current = current.parentNode;
|
|
@@ -637,97 +664,154 @@ const matchPseudoClassSelector = (
|
|
|
637
664
|
break;
|
|
638
665
|
}
|
|
639
666
|
case 'open':
|
|
640
|
-
if (node.hasAttribute('open')) {
|
|
641
|
-
|
|
667
|
+
if (HTML_INTERACT.test(localName) && node.hasAttribute('open')) {
|
|
668
|
+
matched.push(node);
|
|
642
669
|
}
|
|
643
670
|
break;
|
|
644
671
|
case 'closed':
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
res.add(node);
|
|
672
|
+
if (HTML_INTERACT.test(localName) && !node.hasAttribute('open')) {
|
|
673
|
+
matched.push(node);
|
|
648
674
|
}
|
|
649
675
|
break;
|
|
650
676
|
case 'disabled':
|
|
651
|
-
if (
|
|
652
|
-
|
|
677
|
+
if ((HTML_FORM_INPUT.test(localName) ||
|
|
678
|
+
HTML_FORM_PARTS.test(localName) ||
|
|
679
|
+
HTML_CUSTOM_ELEMENT.test(localName)) &&
|
|
680
|
+
node.hasAttribute('disabled')) {
|
|
681
|
+
matched.push(node);
|
|
653
682
|
}
|
|
654
683
|
break;
|
|
655
684
|
case 'enabled':
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
685
|
+
if ((HTML_FORM_INPUT.test(localName) ||
|
|
686
|
+
HTML_FORM_PARTS.test(localName) ||
|
|
687
|
+
HTML_CUSTOM_ELEMENT.test(localName)) &&
|
|
688
|
+
!node.hasAttribute('disabled')) {
|
|
689
|
+
matched.push(node);
|
|
659
690
|
}
|
|
660
691
|
break;
|
|
661
692
|
case 'checked':
|
|
662
|
-
if (node.
|
|
663
|
-
|
|
693
|
+
if ((/^input$/.test(localName) && node.hasAttribute('type') &&
|
|
694
|
+
/^(?:checkbox|radio)$/.test(node.getAttribute('type')) &&
|
|
695
|
+
node.checked) ||
|
|
696
|
+
(localName === 'option' && node.selected)) {
|
|
697
|
+
matched.push(node);
|
|
698
|
+
}
|
|
699
|
+
break;
|
|
700
|
+
case 'default':
|
|
701
|
+
// input[type="checkbox"], input[type="radio"]
|
|
702
|
+
if (/^input$/.test(localName) && node.hasAttribute('type') &&
|
|
703
|
+
/^(?:checkbox|radio)$/.test(node.getAttribute('type'))) {
|
|
704
|
+
if (node.hasAttribute('checked')) {
|
|
705
|
+
matched.push(node);
|
|
706
|
+
}
|
|
707
|
+
// option
|
|
708
|
+
} else if (localName === 'option') {
|
|
709
|
+
let isMultiple = false;
|
|
710
|
+
let parent = parentNode;
|
|
711
|
+
while (parent) {
|
|
712
|
+
if (parent.localName === 'datalist') {
|
|
713
|
+
break;
|
|
714
|
+
} else if (parent.localName === 'select') {
|
|
715
|
+
isMultiple = !!parent.multiple;
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
parent = parent.parentNode;
|
|
719
|
+
}
|
|
720
|
+
// FIXME:
|
|
721
|
+
if (isMultiple) {
|
|
722
|
+
console.warn(`Unsupported pseudo-class ${astName}`);
|
|
723
|
+
} else {
|
|
724
|
+
const firstOpt = parentNode.firstElementChild;
|
|
725
|
+
const defaultOpt = [];
|
|
726
|
+
let opt = firstOpt;
|
|
727
|
+
while (opt) {
|
|
728
|
+
if (opt.hasAttribute('selected')) {
|
|
729
|
+
defaultOpt.push(opt);
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
opt = opt.nextElementSibling;
|
|
733
|
+
}
|
|
734
|
+
if (!defaultOpt.length) {
|
|
735
|
+
defaultOpt.push(firstOpt);
|
|
736
|
+
}
|
|
737
|
+
if (defaultOpt.includes(node)) {
|
|
738
|
+
matched.push(node);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// FIXME:
|
|
742
|
+
// button[type="submit"], input[type="submit"], input[type="image"]
|
|
743
|
+
} else if ((localName === 'button' &&
|
|
744
|
+
(!node.hasAttribute('type') ||
|
|
745
|
+
node.getAttribute('type') === 'submit')) ||
|
|
746
|
+
(/^input$/.test(localName) && node.hasAttribute('type') &&
|
|
747
|
+
/^(?:image|submit)$/.test(node.getAttribute('type')))) {
|
|
748
|
+
console.warn(`Unsupported pseudo-class ${astName}`);
|
|
664
749
|
}
|
|
665
750
|
break;
|
|
666
751
|
case 'required':
|
|
667
|
-
if (node.required) {
|
|
668
|
-
|
|
752
|
+
if (HTML_FORM_INPUT.test(localName) && node.required) {
|
|
753
|
+
matched.push(node);
|
|
669
754
|
}
|
|
670
755
|
break;
|
|
671
756
|
case 'optional':
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
res.add(node);
|
|
757
|
+
if (HTML_FORM_INPUT.test(localName) && !node.required) {
|
|
758
|
+
matched.push(node);
|
|
675
759
|
}
|
|
676
760
|
break;
|
|
677
761
|
case 'root':
|
|
678
762
|
if (node === root) {
|
|
679
|
-
|
|
763
|
+
matched.push(node);
|
|
680
764
|
}
|
|
681
765
|
break;
|
|
682
766
|
case 'first-child':
|
|
683
767
|
if (node === node.parentNode.firstElementChild) {
|
|
684
|
-
|
|
768
|
+
matched.push(node);
|
|
685
769
|
}
|
|
686
770
|
break;
|
|
687
771
|
case 'last-child':
|
|
688
772
|
if (node === node.parentNode.lastElementChild) {
|
|
689
|
-
|
|
773
|
+
matched.push(node);
|
|
690
774
|
}
|
|
691
775
|
break;
|
|
692
776
|
case 'only-child':
|
|
693
777
|
if (node === node.parentNode.firstElementChild &&
|
|
694
778
|
node === node.parentNode.lastElementChild) {
|
|
695
|
-
|
|
779
|
+
matched.push(node);
|
|
696
780
|
}
|
|
697
781
|
break;
|
|
698
782
|
case 'first-of-type': {
|
|
699
|
-
const [node1] = collectNthOfType(
|
|
783
|
+
const [node1] = collectNthOfType({
|
|
700
784
|
a: 0,
|
|
701
785
|
b: 0
|
|
702
|
-
});
|
|
786
|
+
}, node);
|
|
703
787
|
if (node1) {
|
|
704
|
-
|
|
788
|
+
matched.push(node1);
|
|
705
789
|
}
|
|
706
790
|
break;
|
|
707
791
|
}
|
|
708
792
|
case 'last-of-type': {
|
|
709
|
-
const [node1] = collectNthOfType(
|
|
793
|
+
const [node1] = collectNthOfType({
|
|
710
794
|
a: 0,
|
|
711
795
|
b: 0,
|
|
712
796
|
reverse: true
|
|
713
|
-
});
|
|
797
|
+
}, node);
|
|
714
798
|
if (node1) {
|
|
715
|
-
|
|
799
|
+
matched.push(node1);
|
|
716
800
|
}
|
|
717
801
|
break;
|
|
718
802
|
}
|
|
719
803
|
case 'only-of-type': {
|
|
720
|
-
const [node1] = collectNthOfType(
|
|
804
|
+
const [node1] = collectNthOfType({
|
|
721
805
|
a: 0,
|
|
722
806
|
b: 0
|
|
723
|
-
});
|
|
724
|
-
const [node2] = collectNthOfType(
|
|
807
|
+
}, node);
|
|
808
|
+
const [node2] = collectNthOfType({
|
|
725
809
|
a: 0,
|
|
726
810
|
b: 0,
|
|
727
811
|
reverse: true
|
|
728
|
-
});
|
|
812
|
+
}, node);
|
|
729
813
|
if (node1 === node && node2 === node) {
|
|
730
|
-
|
|
814
|
+
matched.push(node);
|
|
731
815
|
}
|
|
732
816
|
break;
|
|
733
817
|
}
|
|
@@ -736,7 +820,6 @@ const matchPseudoClassSelector = (
|
|
|
736
820
|
case 'blank':
|
|
737
821
|
case 'buffering':
|
|
738
822
|
case 'current':
|
|
739
|
-
case 'default':
|
|
740
823
|
case 'empty':
|
|
741
824
|
case 'focus-visible':
|
|
742
825
|
case 'fullscreen':
|
|
@@ -761,14 +844,14 @@ const matchPseudoClassSelector = (
|
|
|
761
844
|
case 'user-valid':
|
|
762
845
|
case 'valid':
|
|
763
846
|
case 'volume-locked':
|
|
764
|
-
console.warn(`Unsupported pseudo
|
|
847
|
+
console.warn(`Unsupported pseudo-class ${astName}`);
|
|
765
848
|
break;
|
|
766
849
|
default:
|
|
767
|
-
console.warn(`Unknown pseudo
|
|
850
|
+
console.warn(`Unknown pseudo-class ${astName}`);
|
|
768
851
|
}
|
|
769
852
|
}
|
|
770
853
|
}
|
|
771
|
-
return [...
|
|
854
|
+
return [...new Set(matched)];
|
|
772
855
|
};
|
|
773
856
|
|
|
774
857
|
/**
|
|
@@ -817,20 +900,18 @@ class Matcher {
|
|
|
817
900
|
* @param {object} node - Element node
|
|
818
901
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
819
902
|
*/
|
|
820
|
-
|
|
821
|
-
const items =
|
|
822
|
-
const
|
|
903
|
+
_parseAST(ast, node) {
|
|
904
|
+
const items = walkAST(ast);
|
|
905
|
+
const matched = [];
|
|
823
906
|
if (items.length) {
|
|
824
907
|
for (const item of items) {
|
|
825
908
|
const arr = this._matchSelector(item, node);
|
|
826
909
|
if (arr.length) {
|
|
827
|
-
|
|
828
|
-
res.add(i);
|
|
829
|
-
}
|
|
910
|
+
matched.push(...arr);
|
|
830
911
|
}
|
|
831
912
|
}
|
|
832
913
|
}
|
|
833
|
-
return [...
|
|
914
|
+
return [...new Set(matched)];
|
|
834
915
|
}
|
|
835
916
|
|
|
836
917
|
/**
|
|
@@ -843,28 +924,22 @@ class Matcher {
|
|
|
843
924
|
const [prevLeaf, nextLeaf] = leaves;
|
|
844
925
|
const iterator = this._createIterator(prevLeaf, node);
|
|
845
926
|
let prevNode = iterator.nextNode();
|
|
846
|
-
const
|
|
927
|
+
const items = [];
|
|
847
928
|
while (prevNode) {
|
|
848
929
|
const arr = this._match(prevLeaf, prevNode);
|
|
849
930
|
if (arr.length) {
|
|
850
931
|
for (const item of arr) {
|
|
851
932
|
const a = this._match(nextLeaf, item);
|
|
852
933
|
if (a.length) {
|
|
853
|
-
|
|
854
|
-
nodes.add(i);
|
|
855
|
-
}
|
|
934
|
+
items.push(...a);
|
|
856
935
|
}
|
|
857
936
|
}
|
|
858
937
|
}
|
|
859
938
|
prevNode = iterator.nextNode();
|
|
860
939
|
}
|
|
861
|
-
const items = [...nodes];
|
|
862
940
|
let res;
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
res = item;
|
|
866
|
-
break;
|
|
867
|
-
}
|
|
941
|
+
if (items.includes(node)) {
|
|
942
|
+
res = node;
|
|
868
943
|
}
|
|
869
944
|
return res || null;
|
|
870
945
|
}
|
|
@@ -890,9 +965,7 @@ class Matcher {
|
|
|
890
965
|
while (nextNode) {
|
|
891
966
|
const arr = this._match(item, nextNode);
|
|
892
967
|
if (arr.length) {
|
|
893
|
-
|
|
894
|
-
nodes.add(i);
|
|
895
|
-
}
|
|
968
|
+
nodes.add(...arr);
|
|
896
969
|
}
|
|
897
970
|
nextNode = iterator.nextNode();
|
|
898
971
|
}
|
|
@@ -908,22 +981,22 @@ class Matcher {
|
|
|
908
981
|
}
|
|
909
982
|
}
|
|
910
983
|
}
|
|
911
|
-
const
|
|
984
|
+
const matched = [];
|
|
912
985
|
if (nodes.size && /^[ >+~]$/.test(comboName)) {
|
|
913
|
-
const
|
|
914
|
-
for (const item of
|
|
986
|
+
const arr = [...nodes];
|
|
987
|
+
for (const item of arr) {
|
|
915
988
|
let refNode = item;
|
|
916
989
|
switch (comboName) {
|
|
917
990
|
case '>':
|
|
918
991
|
if (refNode.parentNode === prevNode) {
|
|
919
|
-
|
|
992
|
+
matched.push(item);
|
|
920
993
|
}
|
|
921
994
|
break;
|
|
922
995
|
case '~':
|
|
923
996
|
refNode = refNode.previousElementSibling;
|
|
924
997
|
while (refNode) {
|
|
925
998
|
if (refNode === prevNode) {
|
|
926
|
-
|
|
999
|
+
matched.push(item);
|
|
927
1000
|
break;
|
|
928
1001
|
}
|
|
929
1002
|
refNode = refNode.previousElementSibling;
|
|
@@ -931,14 +1004,14 @@ class Matcher {
|
|
|
931
1004
|
break;
|
|
932
1005
|
case '+':
|
|
933
1006
|
if (refNode.previousElementSibling === prevNode) {
|
|
934
|
-
|
|
1007
|
+
matched.push(item);
|
|
935
1008
|
}
|
|
936
1009
|
break;
|
|
937
1010
|
default:
|
|
938
1011
|
refNode = refNode.parentNode;
|
|
939
1012
|
while (refNode) {
|
|
940
1013
|
if (refNode === prevNode) {
|
|
941
|
-
|
|
1014
|
+
matched.push(item);
|
|
942
1015
|
break;
|
|
943
1016
|
}
|
|
944
1017
|
refNode = refNode.parentNode;
|
|
@@ -946,7 +1019,7 @@ class Matcher {
|
|
|
946
1019
|
}
|
|
947
1020
|
}
|
|
948
1021
|
}
|
|
949
|
-
return [...
|
|
1022
|
+
return [...new Set(matched)];
|
|
950
1023
|
}
|
|
951
1024
|
|
|
952
1025
|
/**
|
|
@@ -958,27 +1031,25 @@ class Matcher {
|
|
|
958
1031
|
_matchArgumentLeaf(leaf, node) {
|
|
959
1032
|
const iterator = this._createIterator(leaf, node);
|
|
960
1033
|
let nextNode = iterator.nextNode();
|
|
961
|
-
const
|
|
1034
|
+
const matched = [];
|
|
962
1035
|
while (nextNode) {
|
|
963
1036
|
const arr = this._match(leaf, nextNode);
|
|
964
1037
|
if (arr.length) {
|
|
965
|
-
|
|
966
|
-
res.add(i);
|
|
967
|
-
}
|
|
1038
|
+
matched.push(...arr);
|
|
968
1039
|
}
|
|
969
1040
|
nextNode = iterator.nextNode();
|
|
970
1041
|
}
|
|
971
|
-
return [...
|
|
1042
|
+
return [...new Set(matched)];
|
|
972
1043
|
}
|
|
973
1044
|
|
|
974
1045
|
/**
|
|
975
|
-
* match logical pseudo
|
|
1046
|
+
* match logical pseudo-class functions - :is(), :has(), :not(), :where()
|
|
976
1047
|
* @param {object} branch - AST branch
|
|
977
1048
|
* @param {object} node - Element node
|
|
978
1049
|
* @returns {?object} - matched node
|
|
979
1050
|
*/
|
|
980
1051
|
_matchLogicalPseudoFunc(branch, node) {
|
|
981
|
-
const ast =
|
|
1052
|
+
const ast = walkAST(branch);
|
|
982
1053
|
let res;
|
|
983
1054
|
if (ast.length) {
|
|
984
1055
|
const { name: branchName } = branch;
|
|
@@ -1078,13 +1149,13 @@ class Matcher {
|
|
|
1078
1149
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
1079
1150
|
*/
|
|
1080
1151
|
_matchSelector(children, node) {
|
|
1081
|
-
const
|
|
1152
|
+
const matched = [];
|
|
1082
1153
|
if (Array.isArray(children) && children.length) {
|
|
1083
1154
|
const [firstChild] = children;
|
|
1084
1155
|
let iteratorLeaf;
|
|
1085
1156
|
if (firstChild.type === COMBINATOR ||
|
|
1086
1157
|
(firstChild.type === PSEUDO_CLASS_SELECTOR &&
|
|
1087
|
-
|
|
1158
|
+
PSEUDO_NTH.test(firstChild.name))) {
|
|
1088
1159
|
iteratorLeaf = {
|
|
1089
1160
|
name: '*',
|
|
1090
1161
|
type: TYPE_SELECTOR
|
|
@@ -1102,18 +1173,16 @@ class Matcher {
|
|
|
1102
1173
|
const item = items.shift();
|
|
1103
1174
|
const { name: itemName, type: itemType } = item;
|
|
1104
1175
|
if (itemType === PSEUDO_CLASS_SELECTOR &&
|
|
1105
|
-
|
|
1176
|
+
PSEUDO_FUNC.test(itemName)) {
|
|
1106
1177
|
nextNode = this._matchLogicalPseudoFunc(item, nextNode);
|
|
1107
1178
|
if (nextNode) {
|
|
1108
|
-
|
|
1179
|
+
matched.push(nextNode);
|
|
1109
1180
|
nextNode = null;
|
|
1110
1181
|
}
|
|
1111
1182
|
} else {
|
|
1112
1183
|
const arr = this._match(item, nextNode);
|
|
1113
1184
|
if (arr.length) {
|
|
1114
|
-
|
|
1115
|
-
res.add(i);
|
|
1116
|
-
}
|
|
1185
|
+
matched.push(...arr);
|
|
1117
1186
|
}
|
|
1118
1187
|
}
|
|
1119
1188
|
} else {
|
|
@@ -1121,7 +1190,7 @@ class Matcher {
|
|
|
1121
1190
|
const item = items.shift();
|
|
1122
1191
|
const { name: itemName, type: itemType } = item;
|
|
1123
1192
|
if (itemType === PSEUDO_CLASS_SELECTOR &&
|
|
1124
|
-
|
|
1193
|
+
PSEUDO_FUNC.test(itemName)) {
|
|
1125
1194
|
nextNode = this._matchLogicalPseudoFunc(item, nextNode);
|
|
1126
1195
|
} else if (itemType === COMBINATOR) {
|
|
1127
1196
|
const leaves = [];
|
|
@@ -1130,9 +1199,9 @@ class Matcher {
|
|
|
1130
1199
|
const [nextItem] = items;
|
|
1131
1200
|
if (nextItem.type === COMBINATOR ||
|
|
1132
1201
|
(nextItem.type === PSEUDO_CLASS_SELECTOR &&
|
|
1133
|
-
|
|
1202
|
+
PSEUDO_NTH.test(nextItem.name)) ||
|
|
1134
1203
|
(nextItem.type === PSEUDO_CLASS_SELECTOR &&
|
|
1135
|
-
|
|
1204
|
+
PSEUDO_FUNC.test(nextItem.name))) {
|
|
1136
1205
|
break;
|
|
1137
1206
|
} else {
|
|
1138
1207
|
leaves.push(items.shift());
|
|
@@ -1146,15 +1215,11 @@ class Matcher {
|
|
|
1146
1215
|
for (const i of arr) {
|
|
1147
1216
|
const a = this._matchSelector(items, i);
|
|
1148
1217
|
if (a.length) {
|
|
1149
|
-
|
|
1150
|
-
res.add(j);
|
|
1151
|
-
}
|
|
1218
|
+
matched.push(...a);
|
|
1152
1219
|
}
|
|
1153
1220
|
}
|
|
1154
1221
|
} else {
|
|
1155
|
-
|
|
1156
|
-
res.add(i);
|
|
1157
|
-
}
|
|
1222
|
+
matched.push(...arr);
|
|
1158
1223
|
}
|
|
1159
1224
|
nextNode = null;
|
|
1160
1225
|
}
|
|
@@ -1163,28 +1228,28 @@ class Matcher {
|
|
|
1163
1228
|
}
|
|
1164
1229
|
} while (items.length && nextNode);
|
|
1165
1230
|
if (nextNode) {
|
|
1166
|
-
|
|
1231
|
+
matched.push(nextNode);
|
|
1167
1232
|
}
|
|
1168
1233
|
}
|
|
1169
1234
|
} else if (nextNode) {
|
|
1170
|
-
|
|
1235
|
+
matched.push(nextNode);
|
|
1171
1236
|
}
|
|
1172
1237
|
nextNode = iterator.nextNode();
|
|
1173
1238
|
}
|
|
1174
1239
|
} else if (firstChild.type === PSEUDO_CLASS_SELECTOR &&
|
|
1175
|
-
|
|
1240
|
+
PSEUDO_FUNC.test(firstChild.name) &&
|
|
1176
1241
|
node.nodeType === ELEMENT_NODE) {
|
|
1177
1242
|
nextNode = node;
|
|
1178
1243
|
while (nextNode) {
|
|
1179
1244
|
nextNode = this._matchLogicalPseudoFunc(firstChild, nextNode);
|
|
1180
1245
|
if (nextNode) {
|
|
1181
|
-
|
|
1246
|
+
matched.push(nextNode);
|
|
1182
1247
|
}
|
|
1183
1248
|
nextNode = nextNode.nextElementSibling;
|
|
1184
1249
|
}
|
|
1185
1250
|
}
|
|
1186
1251
|
}
|
|
1187
|
-
return [...
|
|
1252
|
+
return [...new Set(matched)];
|
|
1188
1253
|
}
|
|
1189
1254
|
|
|
1190
1255
|
/**
|
|
@@ -1194,49 +1259,45 @@ class Matcher {
|
|
|
1194
1259
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
1195
1260
|
*/
|
|
1196
1261
|
_match(ast = this.#ast, node = this.#node) {
|
|
1197
|
-
const
|
|
1262
|
+
const matched = [];
|
|
1198
1263
|
const { name, type } = ast;
|
|
1199
1264
|
switch (type) {
|
|
1200
1265
|
case TYPE_SELECTOR:
|
|
1201
1266
|
if (matchTypeSelector(ast, node)) {
|
|
1202
|
-
|
|
1267
|
+
matched.push(node);
|
|
1203
1268
|
}
|
|
1204
1269
|
break;
|
|
1205
1270
|
case CLASS_SELECTOR:
|
|
1206
1271
|
if (matchClassSelector(ast, node)) {
|
|
1207
|
-
|
|
1272
|
+
matched.push(node);
|
|
1208
1273
|
}
|
|
1209
1274
|
break;
|
|
1210
1275
|
case ID_SELECTOR:
|
|
1211
|
-
if (
|
|
1212
|
-
|
|
1276
|
+
if (matchIDSelector(ast, node)) {
|
|
1277
|
+
matched.push(node);
|
|
1213
1278
|
}
|
|
1214
1279
|
break;
|
|
1215
1280
|
case ATTRIBUTE_SELECTOR:
|
|
1216
1281
|
if (matchAttributeSelector(ast, node)) {
|
|
1217
|
-
|
|
1282
|
+
matched.push(node);
|
|
1218
1283
|
}
|
|
1219
1284
|
break;
|
|
1220
1285
|
case PSEUDO_CLASS_SELECTOR:
|
|
1221
|
-
if (!
|
|
1286
|
+
if (!PSEUDO_FUNC.test(name)) {
|
|
1222
1287
|
const arr = matchPseudoClassSelector(ast, node, this.#node);
|
|
1223
1288
|
if (arr.length) {
|
|
1224
|
-
|
|
1225
|
-
res.add(i);
|
|
1226
|
-
}
|
|
1289
|
+
matched.push(...arr);
|
|
1227
1290
|
}
|
|
1228
1291
|
}
|
|
1229
1292
|
break;
|
|
1230
1293
|
default: {
|
|
1231
|
-
const arr = this.
|
|
1294
|
+
const arr = this._parseAST(ast, node);
|
|
1232
1295
|
if (arr.length) {
|
|
1233
|
-
|
|
1234
|
-
res.add(i);
|
|
1235
|
-
}
|
|
1296
|
+
matched.push(...arr);
|
|
1236
1297
|
}
|
|
1237
1298
|
}
|
|
1238
1299
|
}
|
|
1239
|
-
return [...
|
|
1300
|
+
return [...new Set(matched)];
|
|
1240
1301
|
}
|
|
1241
1302
|
|
|
1242
1303
|
/**
|
|
@@ -1245,16 +1306,7 @@ class Matcher {
|
|
|
1245
1306
|
*/
|
|
1246
1307
|
matches() {
|
|
1247
1308
|
const arr = this._match(this.#ast, this.#document);
|
|
1248
|
-
const
|
|
1249
|
-
let res;
|
|
1250
|
-
if (arr.length) {
|
|
1251
|
-
for (const i of arr) {
|
|
1252
|
-
if (i === node) {
|
|
1253
|
-
res = true;
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1309
|
+
const res = arr.length && arr.includes(this.#node);
|
|
1258
1310
|
return !!res;
|
|
1259
1311
|
}
|
|
1260
1312
|
|
|
@@ -1267,13 +1319,8 @@ class Matcher {
|
|
|
1267
1319
|
let node = this.#node;
|
|
1268
1320
|
let res;
|
|
1269
1321
|
while (node) {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
res = i;
|
|
1273
|
-
break;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
if (res) {
|
|
1322
|
+
if (arr.includes(node)) {
|
|
1323
|
+
res = node;
|
|
1277
1324
|
break;
|
|
1278
1325
|
}
|
|
1279
1326
|
node = node.parentNode;
|
|
@@ -1306,15 +1353,13 @@ class Matcher {
|
|
|
1306
1353
|
*/
|
|
1307
1354
|
querySelectorAll() {
|
|
1308
1355
|
const arr = this._match(this.#ast, this.#node);
|
|
1309
|
-
const res = new Set();
|
|
1310
1356
|
if (arr.length) {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1357
|
+
const i = arr.findIndex(node => node === this.#node);
|
|
1358
|
+
if (i >= 0) {
|
|
1359
|
+
arr.splice(i, 1);
|
|
1315
1360
|
}
|
|
1316
1361
|
}
|
|
1317
|
-
return [...
|
|
1362
|
+
return [...new Set(arr)];
|
|
1318
1363
|
}
|
|
1319
1364
|
};
|
|
1320
1365
|
|
|
@@ -1325,7 +1370,7 @@ module.exports = {
|
|
|
1325
1370
|
matchAnPlusB,
|
|
1326
1371
|
matchAttributeSelector,
|
|
1327
1372
|
matchClassSelector,
|
|
1328
|
-
|
|
1373
|
+
matchIDSelector,
|
|
1329
1374
|
matchLanguagePseudoClass,
|
|
1330
1375
|
matchPseudoClassSelector,
|
|
1331
1376
|
matchTypeSelector
|
package/src/js/parser.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* api */
|
|
6
|
-
const { parse, toPlainObject, walk } = require('css-tree');
|
|
6
|
+
const { generate, parse, toPlainObject, walk } = require('css-tree');
|
|
7
7
|
const { SELECTOR } = require('./constant.js');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -24,7 +24,7 @@ const parseSelector = selector => {
|
|
|
24
24
|
* @param {object} ast - AST
|
|
25
25
|
* @returns {Array.<object|undefined>} - collection of AST branches
|
|
26
26
|
*/
|
|
27
|
-
const
|
|
27
|
+
const walkAST = (ast = {}) => {
|
|
28
28
|
const selectors = new Set();
|
|
29
29
|
const opt = {
|
|
30
30
|
enter: branch => {
|
|
@@ -35,7 +35,7 @@ const walkAst = (ast = {}) => {
|
|
|
35
35
|
leave: branch => {
|
|
36
36
|
let skip;
|
|
37
37
|
if (branch.type === SELECTOR) {
|
|
38
|
-
skip =
|
|
38
|
+
skip = walkAST.skip;
|
|
39
39
|
}
|
|
40
40
|
return skip;
|
|
41
41
|
}
|
|
@@ -46,6 +46,7 @@ const walkAst = (ast = {}) => {
|
|
|
46
46
|
|
|
47
47
|
/* export */
|
|
48
48
|
module.exports = {
|
|
49
|
+
generateCSS: generate,
|
|
49
50
|
parseSelector,
|
|
50
|
-
|
|
51
|
+
walkAST
|
|
51
52
|
};
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class Matcher {
|
|
2
2
|
constructor(selector: string, refPoint: object);
|
|
3
3
|
_createIterator(ast?: object, root?: object): object;
|
|
4
|
-
|
|
4
|
+
_parseAST(ast: object, node: object): Array<object | undefined>;
|
|
5
5
|
_matchAdjacentLeaves(leaves: Array<object>, node: object): object | null;
|
|
6
6
|
_matchCombinator(leaves: Array<object>, prevNode: object): Array<object | undefined>;
|
|
7
7
|
_matchArgumentLeaf(leaf: object, node: object): Array<object | undefined>;
|
|
@@ -14,20 +14,21 @@ export class Matcher {
|
|
|
14
14
|
querySelectorAll(): Array<object | undefined>;
|
|
15
15
|
#private;
|
|
16
16
|
}
|
|
17
|
-
export function collectNthChild(
|
|
17
|
+
export function collectNthChild(anb?: {
|
|
18
18
|
a: number;
|
|
19
19
|
b: number;
|
|
20
20
|
reverse?: boolean;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
selector?: string;
|
|
22
|
+
}, node?: object): Array<object | undefined>;
|
|
23
|
+
export function collectNthOfType(anb?: {
|
|
23
24
|
a: number;
|
|
24
25
|
b: number;
|
|
25
26
|
reverse?: boolean;
|
|
26
|
-
}): Array<object | undefined>;
|
|
27
|
+
}, node?: object): Array<object | undefined>;
|
|
27
28
|
export function matchAnPlusB(nthName: string, ast?: object, node?: object): Array<object | undefined>;
|
|
28
29
|
export function matchAttributeSelector(ast?: object, node?: object): object | null;
|
|
29
30
|
export function matchClassSelector(ast?: object, node?: object): object | null;
|
|
30
|
-
export function
|
|
31
|
+
export function matchIDSelector(ast?: object, node?: object): object | null;
|
|
31
32
|
export function matchLanguagePseudoClass(ast?: object, node?: object): object | null;
|
|
32
33
|
export function matchPseudoClassSelector(ast?: object, node?: object, refPoint?: object): Array<object | undefined>;
|
|
33
34
|
export function matchTypeSelector(ast?: object, node?: object): object | null;
|
package/types/js/parser.d.ts
CHANGED