@asamuzakjp/dom-selector 0.2.1 → 0.2.2
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/README.md +2 -2
- package/package.json +1 -1
- package/src/js/constant.js +1 -29
- package/src/js/matcher.js +173 -131
- package/src/js/parser.js +6 -6
- package/types/js/constant.d.ts +0 -28
- package/types/js/matcher.d.ts +8 -8
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml)
|
|
4
4
|
[](https://github.com/asamuzaK/domSelector/actions/workflows/codeql.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@asamuzakjp/dom-selector)
|
|
5
6
|
<!--
|
|
6
|
-
[](https://www.npmjs.com/package/url-sanitizer)
|
|
7
7
|
[](https://github.com/asamuzaK/domSelector/releases)
|
|
8
8
|
-->
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ Retrieve DOM node from the given CSS selector.
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
16
|
```console
|
|
17
|
-
npm i dom-selector
|
|
17
|
+
npm i @asamuzakjp/dom-selector
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
|
package/package.json
CHANGED
package/src/js/constant.js
CHANGED
|
@@ -4,44 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
AN_PLUS_B: 'AnPlusB',
|
|
7
|
-
AT_RULE: 'Atrule',
|
|
8
|
-
AT_RULE_PRELUDE: 'AtrulePrelude',
|
|
9
7
|
ATTRIBUTE_SELECTOR: 'AttributeSelector',
|
|
10
|
-
BLOCK: 'Block',
|
|
11
|
-
BRACKETS: 'Brackets',
|
|
12
|
-
CDC: 'CDC',
|
|
13
|
-
CDO: 'CDO',
|
|
14
8
|
CLASS_SELECTOR: 'ClassSelector',
|
|
15
9
|
COMBINATOR: 'Combinator',
|
|
16
|
-
COMMENT: 'Comment',
|
|
17
|
-
DECLARATION: 'Declaration',
|
|
18
|
-
DECLARATION_LIST: 'DeclarationList',
|
|
19
|
-
DIMENSION: 'Dimension',
|
|
20
|
-
FUNCTION: 'Function',
|
|
21
|
-
HASH: 'Hash',
|
|
22
10
|
ID_SELECTOR: 'IdSelector',
|
|
23
11
|
IDENTIFIER: 'Identifier',
|
|
24
|
-
MEDIA_FEATURE: 'MediaFeature',
|
|
25
|
-
MEDIA_QUERY: 'MediaQuery',
|
|
26
|
-
MEDIA_QUERY_LIST: 'MediaQueryList',
|
|
27
|
-
NESTING_SELECTOR: 'NestingSelector',
|
|
28
12
|
NTH: 'Nth',
|
|
29
|
-
NUMBER: 'Number',
|
|
30
|
-
OPERATOR: 'Operator',
|
|
31
|
-
PARENTHESES: 'Parentheses',
|
|
32
|
-
PERCENTAGE: 'Percentage',
|
|
33
13
|
PSEUDO_CLASS_SELECTOR: 'PseudoClassSelector',
|
|
34
|
-
PSEUDO_ELEMENT_SELECTOR: 'PseudoElementSelector',
|
|
35
|
-
RATIO: 'Ratio',
|
|
36
14
|
RAW: 'Raw',
|
|
37
|
-
RULE: 'Rule',
|
|
38
15
|
SELECTOR: 'Selector',
|
|
39
16
|
SELECTOR_LIST: 'SelectorList',
|
|
40
17
|
STRING: 'String',
|
|
41
|
-
|
|
42
|
-
TYPE_SELECTOR: 'TypeSelector',
|
|
43
|
-
UNICODE_RANGE: 'UnicodeRange',
|
|
44
|
-
URL: 'Url',
|
|
45
|
-
VALUE: 'Value',
|
|
46
|
-
WHITESPACE: 'WhiteSpace'
|
|
18
|
+
TYPE_SELECTOR: 'TypeSelector'
|
|
47
19
|
};
|
package/src/js/matcher.js
CHANGED
|
@@ -16,7 +16,7 @@ const REG_PSEUDO_NTH = /^nth-(?:last-)?(?:child|of-type)$/;
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* collect nth child
|
|
19
|
-
* @param {object} node -
|
|
19
|
+
* @param {object} node - Element node
|
|
20
20
|
* @param {object} opt - options
|
|
21
21
|
* @param {number} opt.a - a
|
|
22
22
|
* @param {number} opt.b - b
|
|
@@ -44,8 +44,10 @@ const collectNthChild = (node = {}, opt = {}) => {
|
|
|
44
44
|
} else {
|
|
45
45
|
let n = 0;
|
|
46
46
|
let nth = b - 1;
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (a > 0) {
|
|
48
|
+
while (nth < 0) {
|
|
49
|
+
nth += (++n * a);
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
let i = 0;
|
|
51
53
|
while (i < l && nth < l) {
|
|
@@ -63,7 +65,7 @@ const collectNthChild = (node = {}, opt = {}) => {
|
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
67
|
* collect nth of type
|
|
66
|
-
* @param {object} node -
|
|
68
|
+
* @param {object} node - Element node
|
|
67
69
|
* @param {object} opt - options
|
|
68
70
|
* @param {number} opt.a - a
|
|
69
71
|
* @param {number} opt.b - b
|
|
@@ -102,8 +104,10 @@ const collectNthOfType = (node = {}, opt = {}) => {
|
|
|
102
104
|
// :nth-of-type()
|
|
103
105
|
} else {
|
|
104
106
|
let nth = b - 1;
|
|
105
|
-
|
|
106
|
-
nth
|
|
107
|
+
if (a > 0) {
|
|
108
|
+
while (nth < 0) {
|
|
109
|
+
nth += a;
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
let i = 0;
|
|
109
113
|
let j = 0;
|
|
@@ -126,25 +130,41 @@ const collectNthOfType = (node = {}, opt = {}) => {
|
|
|
126
130
|
|
|
127
131
|
/**
|
|
128
132
|
* match type selector
|
|
129
|
-
* @param {object}
|
|
130
|
-
* @param {object} node -
|
|
133
|
+
* @param {object} ast - AST
|
|
134
|
+
* @param {object} node - Element node
|
|
131
135
|
* @returns {?object} - matched node
|
|
132
136
|
*/
|
|
133
|
-
const matchTypeSelector = (
|
|
134
|
-
const {
|
|
135
|
-
const { localName, nodeType, prefix } = node;
|
|
137
|
+
const matchTypeSelector = (ast = {}, node = {}) => {
|
|
138
|
+
const { type: astType } = ast;
|
|
139
|
+
const { localName, nodeType, ownerDocument, prefix } = node;
|
|
136
140
|
let res;
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
if (astType === TYPE_SELECTOR && nodeType === ELEMENT_NODE) {
|
|
142
|
+
let astName = ast.name;
|
|
143
|
+
let astPrefix, astNodeName, nodePrefix, nodeName;
|
|
144
|
+
if (/\|/.test(astName)) {
|
|
145
|
+
[astPrefix, astNodeName] = astName.split('|');
|
|
146
|
+
} else {
|
|
147
|
+
astPrefix = '';
|
|
148
|
+
astNodeName = astName;
|
|
149
|
+
}
|
|
150
|
+
if (ownerDocument?.contentType === 'text/html') {
|
|
151
|
+
astNodeName = astNodeName.toLowerCase();
|
|
152
|
+
astName = astName.toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
// just in case that the namespaced content is parsed as text/html
|
|
155
|
+
if (/:/.test(localName)) {
|
|
156
|
+
[nodePrefix, nodeName] = localName.split(':');
|
|
157
|
+
} else {
|
|
158
|
+
nodePrefix = prefix || '';
|
|
159
|
+
nodeName = localName;
|
|
160
|
+
}
|
|
161
|
+
if (astName === '*' || astName === '*|*' || astName === nodeName ||
|
|
162
|
+
(astName === '|*' && !nodePrefix) ||
|
|
163
|
+
(astPrefix === '*' && astNodeName === nodeName) ||
|
|
164
|
+
(astPrefix === '' && !nodePrefix &&
|
|
165
|
+
(astNodeName === '*' || astNodeName === nodeName)) ||
|
|
166
|
+
(astPrefix === nodePrefix &&
|
|
167
|
+
(astNodeName === '*' || astNodeName === nodeName))) {
|
|
148
168
|
res = node;
|
|
149
169
|
}
|
|
150
170
|
}
|
|
@@ -153,16 +173,16 @@ const matchTypeSelector = (leaf = {}, node = {}) => {
|
|
|
153
173
|
|
|
154
174
|
/**
|
|
155
175
|
* match class selector
|
|
156
|
-
* @param {object}
|
|
157
|
-
* @param {object} node -
|
|
176
|
+
* @param {object} ast - AST
|
|
177
|
+
* @param {object} node - Element node
|
|
158
178
|
* @returns {?object} - matched node
|
|
159
179
|
*/
|
|
160
|
-
const matchClassSelector = (
|
|
161
|
-
const { name:
|
|
180
|
+
const matchClassSelector = (ast = {}, node = {}) => {
|
|
181
|
+
const { name: astName, type: astType } = ast;
|
|
162
182
|
const { classList, nodeType } = node;
|
|
163
183
|
let res;
|
|
164
|
-
if (
|
|
165
|
-
classList.contains(
|
|
184
|
+
if (astType === CLASS_SELECTOR && nodeType === ELEMENT_NODE &&
|
|
185
|
+
classList.contains(astName)) {
|
|
166
186
|
res = node;
|
|
167
187
|
}
|
|
168
188
|
return res || null;
|
|
@@ -170,16 +190,16 @@ const matchClassSelector = (leaf = {}, node = {}) => {
|
|
|
170
190
|
|
|
171
191
|
/**
|
|
172
192
|
* match ID selector
|
|
173
|
-
* @param {object}
|
|
174
|
-
* @param {object} node -
|
|
193
|
+
* @param {object} ast - AST
|
|
194
|
+
* @param {object} node - Element node
|
|
175
195
|
* @returns {?object} - matched node
|
|
176
196
|
*/
|
|
177
|
-
const matchIdSelector = (
|
|
178
|
-
const { name:
|
|
197
|
+
const matchIdSelector = (ast = {}, node = {}) => {
|
|
198
|
+
const { name: astName, type: astType } = ast;
|
|
179
199
|
const { id, nodeType } = node;
|
|
180
200
|
let res;
|
|
181
|
-
if (
|
|
182
|
-
|
|
201
|
+
if (astType === ID_SELECTOR && nodeType === ELEMENT_NODE &&
|
|
202
|
+
astName === id) {
|
|
183
203
|
res = node;
|
|
184
204
|
}
|
|
185
205
|
return res || null;
|
|
@@ -187,32 +207,32 @@ const matchIdSelector = (leaf = {}, node = {}) => {
|
|
|
187
207
|
|
|
188
208
|
/**
|
|
189
209
|
* match attribute selector
|
|
190
|
-
* @param {object}
|
|
191
|
-
* @param {object} node -
|
|
210
|
+
* @param {object} ast - AST
|
|
211
|
+
* @param {object} node - Element node
|
|
192
212
|
* @returns {?object} - matched node
|
|
193
213
|
*/
|
|
194
|
-
const matchAttributeSelector = (
|
|
214
|
+
const matchAttributeSelector = (ast = {}, node = {}) => {
|
|
195
215
|
const {
|
|
196
|
-
flags:
|
|
197
|
-
value:
|
|
198
|
-
} =
|
|
216
|
+
flags: astFlags, matcher: astMatcher, name: astName, type: astType,
|
|
217
|
+
value: astValue
|
|
218
|
+
} = ast;
|
|
199
219
|
const { attributes, nodeType } = node;
|
|
200
220
|
let res;
|
|
201
|
-
if (
|
|
221
|
+
if (astType === ATTRIBUTE_SELECTOR && nodeType === ELEMENT_NODE &&
|
|
202
222
|
attributes?.length) {
|
|
203
|
-
const { name:
|
|
204
|
-
const caseInsensitive = !(
|
|
223
|
+
const { name: astAttrName } = astName;
|
|
224
|
+
const caseInsensitive = !(astFlags && /^s$/i.test(astFlags));
|
|
205
225
|
const attrValues = [];
|
|
206
226
|
const l = attributes.length;
|
|
207
227
|
// namespaced
|
|
208
|
-
if (/\|/.test(
|
|
209
|
-
const [
|
|
228
|
+
if (/\|/.test(astAttrName)) {
|
|
229
|
+
const [astAttrPrefix, astAttrLocalName] = astAttrName.split('|');
|
|
210
230
|
let i = 0;
|
|
211
231
|
while (i < l) {
|
|
212
232
|
const { name: itemName, value: itemValue } = attributes.item(i);
|
|
213
|
-
switch (
|
|
233
|
+
switch (astAttrPrefix) {
|
|
214
234
|
case '':
|
|
215
|
-
if (
|
|
235
|
+
if (astAttrLocalName === itemName) {
|
|
216
236
|
if (caseInsensitive) {
|
|
217
237
|
attrValues.push(itemValue.toLowerCase());
|
|
218
238
|
} else {
|
|
@@ -222,14 +242,14 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
222
242
|
break;
|
|
223
243
|
case '*':
|
|
224
244
|
if (/:/.test(itemName)) {
|
|
225
|
-
if (itemName.endsWith(`:${
|
|
245
|
+
if (itemName.endsWith(`:${astAttrLocalName}`)) {
|
|
226
246
|
if (caseInsensitive) {
|
|
227
247
|
attrValues.push(itemValue.toLowerCase());
|
|
228
248
|
} else {
|
|
229
249
|
attrValues.push(itemValue);
|
|
230
250
|
}
|
|
231
251
|
}
|
|
232
|
-
} else if (
|
|
252
|
+
} else if (astAttrLocalName === itemName) {
|
|
233
253
|
if (caseInsensitive) {
|
|
234
254
|
attrValues.push(itemValue.toLowerCase());
|
|
235
255
|
} else {
|
|
@@ -240,8 +260,8 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
240
260
|
default:
|
|
241
261
|
if (/:/.test(itemName)) {
|
|
242
262
|
const [itemNamePrefix, itemNameLocalName] = itemName.split(':');
|
|
243
|
-
if (
|
|
244
|
-
|
|
263
|
+
if (astAttrPrefix === itemNamePrefix &&
|
|
264
|
+
astAttrLocalName === itemNameLocalName) {
|
|
245
265
|
if (caseInsensitive) {
|
|
246
266
|
attrValues.push(itemValue.toLowerCase());
|
|
247
267
|
} else {
|
|
@@ -258,14 +278,14 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
258
278
|
const { name: itemName, value: itemValue } = attributes.item(i);
|
|
259
279
|
if (/:/.test(itemName)) {
|
|
260
280
|
const [, itemNameLocalName] = itemName.split(':');
|
|
261
|
-
if (
|
|
281
|
+
if (astAttrName === itemNameLocalName) {
|
|
262
282
|
if (caseInsensitive) {
|
|
263
283
|
attrValues.push(itemValue.toLowerCase());
|
|
264
284
|
} else {
|
|
265
285
|
attrValues.push(itemValue);
|
|
266
286
|
}
|
|
267
287
|
}
|
|
268
|
-
} else if (
|
|
288
|
+
} else if (astAttrName === itemName) {
|
|
269
289
|
if (caseInsensitive) {
|
|
270
290
|
attrValues.push(itemValue.toLowerCase());
|
|
271
291
|
} else {
|
|
@@ -277,23 +297,23 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
277
297
|
}
|
|
278
298
|
if (attrValues.length) {
|
|
279
299
|
const {
|
|
280
|
-
name:
|
|
281
|
-
} =
|
|
300
|
+
name: astAttrIdentValue, value: astAttrStringValue
|
|
301
|
+
} = astValue || {};
|
|
282
302
|
let attrValue;
|
|
283
|
-
if (
|
|
303
|
+
if (astAttrIdentValue) {
|
|
284
304
|
if (caseInsensitive) {
|
|
285
|
-
attrValue =
|
|
305
|
+
attrValue = astAttrIdentValue.toLowerCase();
|
|
286
306
|
} else {
|
|
287
|
-
attrValue =
|
|
307
|
+
attrValue = astAttrIdentValue;
|
|
288
308
|
}
|
|
289
|
-
} else if (
|
|
309
|
+
} else if (astAttrStringValue) {
|
|
290
310
|
if (caseInsensitive) {
|
|
291
|
-
attrValue =
|
|
311
|
+
attrValue = astAttrStringValue.toLowerCase();
|
|
292
312
|
} else {
|
|
293
|
-
attrValue =
|
|
313
|
+
attrValue = astAttrStringValue;
|
|
294
314
|
}
|
|
295
315
|
}
|
|
296
|
-
switch (
|
|
316
|
+
switch (astMatcher) {
|
|
297
317
|
case null:
|
|
298
318
|
res = node;
|
|
299
319
|
break;
|
|
@@ -354,7 +374,7 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
354
374
|
}
|
|
355
375
|
break;
|
|
356
376
|
default:
|
|
357
|
-
console.warn(`Unknown matcher ${
|
|
377
|
+
console.warn(`Unknown matcher ${astMatcher}`);
|
|
358
378
|
}
|
|
359
379
|
}
|
|
360
380
|
}
|
|
@@ -363,34 +383,34 @@ const matchAttributeSelector = (leaf = {}, node = {}) => {
|
|
|
363
383
|
|
|
364
384
|
/**
|
|
365
385
|
* match An+B
|
|
366
|
-
* @param {string}
|
|
367
|
-
* @param {object}
|
|
368
|
-
* @param {object} node -
|
|
386
|
+
* @param {string} nthName - nth pseudo class name
|
|
387
|
+
* @param {object} ast - AST
|
|
388
|
+
* @param {object} node - Element node
|
|
369
389
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
370
390
|
*/
|
|
371
|
-
const matchAnPlusB = (
|
|
391
|
+
const matchAnPlusB = (nthName, ast = {}, node = {}) => {
|
|
372
392
|
const res = new Set();
|
|
373
|
-
if (typeof
|
|
374
|
-
|
|
375
|
-
if (REG_PSEUDO_NTH.test(
|
|
393
|
+
if (typeof nthName === 'string') {
|
|
394
|
+
nthName = nthName.trim();
|
|
395
|
+
if (REG_PSEUDO_NTH.test(nthName)) {
|
|
376
396
|
const {
|
|
377
397
|
nth: {
|
|
378
398
|
a,
|
|
379
399
|
b,
|
|
380
400
|
name: identName
|
|
381
401
|
},
|
|
382
|
-
selector:
|
|
383
|
-
type:
|
|
384
|
-
} =
|
|
402
|
+
selector: astSelector,
|
|
403
|
+
type: astType
|
|
404
|
+
} = ast;
|
|
385
405
|
const { nodeType } = node;
|
|
386
|
-
if (
|
|
406
|
+
if (astType === NTH && nodeType === ELEMENT_NODE) {
|
|
387
407
|
/*
|
|
388
408
|
// FIXME:
|
|
389
409
|
// :nth-child(An+B of S)
|
|
390
|
-
if (
|
|
410
|
+
if (astSelector) {
|
|
391
411
|
}
|
|
392
412
|
*/
|
|
393
|
-
if (!
|
|
413
|
+
if (!astSelector) {
|
|
394
414
|
const optMap = new Map();
|
|
395
415
|
if (identName) {
|
|
396
416
|
if (identName === 'even') {
|
|
@@ -400,7 +420,7 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
|
|
|
400
420
|
optMap.set('a', 2);
|
|
401
421
|
optMap.set('b', 1);
|
|
402
422
|
}
|
|
403
|
-
if (/last/.test(
|
|
423
|
+
if (/last/.test(nthName)) {
|
|
404
424
|
optMap.set('reverse', true);
|
|
405
425
|
}
|
|
406
426
|
} else {
|
|
@@ -414,20 +434,20 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
|
|
|
414
434
|
} else {
|
|
415
435
|
optMap.set('b', 0);
|
|
416
436
|
}
|
|
417
|
-
if (/last/.test(
|
|
437
|
+
if (/last/.test(nthName)) {
|
|
418
438
|
optMap.set('reverse', true);
|
|
419
439
|
}
|
|
420
440
|
}
|
|
421
441
|
if (optMap.size > 1) {
|
|
422
442
|
const opt = Object.fromEntries(optMap);
|
|
423
|
-
if (/^nth-(?:last-)?child$/.test(
|
|
443
|
+
if (/^nth-(?:last-)?child$/.test(nthName)) {
|
|
424
444
|
const arr = collectNthChild(node, opt);
|
|
425
445
|
if (arr.length) {
|
|
426
446
|
for (const i of arr) {
|
|
427
447
|
res.add(i);
|
|
428
448
|
}
|
|
429
449
|
}
|
|
430
|
-
} else if (/^nth-(?:last-)?of-type$/.test(
|
|
450
|
+
} else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
|
|
431
451
|
const arr = collectNthOfType(node, opt);
|
|
432
452
|
if (arr.length) {
|
|
433
453
|
for (const i of arr) {
|
|
@@ -446,29 +466,29 @@ const matchAnPlusB = (leafName, leaf = {}, node = {}) => {
|
|
|
446
466
|
/**
|
|
447
467
|
* match language pseudo class
|
|
448
468
|
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
|
|
449
|
-
* @param {object}
|
|
450
|
-
* @param {object} node -
|
|
469
|
+
* @param {object} ast - AST
|
|
470
|
+
* @param {object} node - Element node
|
|
451
471
|
* @returns {?object} - matched node
|
|
452
472
|
*/
|
|
453
|
-
const matchLanguagePseudoClass = (
|
|
454
|
-
const { name:
|
|
473
|
+
const matchLanguagePseudoClass = (ast = {}, node = {}) => {
|
|
474
|
+
const { name: astName, type: astType } = ast;
|
|
455
475
|
const { lang, nodeType } = node;
|
|
456
476
|
let res;
|
|
457
|
-
if (
|
|
477
|
+
if (astType === IDENTIFIER && nodeType === ELEMENT_NODE) {
|
|
458
478
|
// FIXME:
|
|
459
479
|
/*
|
|
460
|
-
if (
|
|
480
|
+
if (astName === '') {
|
|
461
481
|
if (!lang) {
|
|
462
482
|
res = node;
|
|
463
483
|
}
|
|
464
|
-
} else if (
|
|
484
|
+
} else if (astName === '*') {
|
|
465
485
|
}
|
|
466
486
|
*/
|
|
467
|
-
if (/[A-Za-z\d-]+/.test(
|
|
487
|
+
if (/[A-Za-z\d-]+/.test(astName)) {
|
|
468
488
|
const codePart = '(?:-[A-Za-z\\d]+)?';
|
|
469
489
|
let reg;
|
|
470
|
-
if (/-/.test(
|
|
471
|
-
const [langMain, langSub, ...langRest] =
|
|
490
|
+
if (/-/.test(astName)) {
|
|
491
|
+
const [langMain, langSub, ...langRest] = astName.split('-');
|
|
472
492
|
const extendedMain = `${langMain}${codePart}`;
|
|
473
493
|
const extendedSub = `-${langSub}${codePart}`;
|
|
474
494
|
let extendedRest = '';
|
|
@@ -479,7 +499,7 @@ const matchLanguagePseudoClass = (leaf = {}, node = {}) => {
|
|
|
479
499
|
}
|
|
480
500
|
reg = new RegExp(`^${extendedMain}${extendedSub}${extendedRest}$`, 'i');
|
|
481
501
|
} else {
|
|
482
|
-
reg = new RegExp(`^${
|
|
502
|
+
reg = new RegExp(`^${astName}${codePart}$`, 'i');
|
|
483
503
|
}
|
|
484
504
|
if (lang) {
|
|
485
505
|
if (reg.test(lang)) {
|
|
@@ -502,55 +522,55 @@ const matchLanguagePseudoClass = (leaf = {}, node = {}) => {
|
|
|
502
522
|
|
|
503
523
|
/**
|
|
504
524
|
* match pseudo class selector
|
|
505
|
-
* @param {object}
|
|
506
|
-
* @param {object} node -
|
|
525
|
+
* @param {object} ast - AST
|
|
526
|
+
* @param {object} node - Element node
|
|
507
527
|
* @param {object} [refPoint] - reference point
|
|
508
528
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
509
529
|
*/
|
|
510
530
|
const matchPseudoClassSelector = (
|
|
511
|
-
|
|
531
|
+
ast = {},
|
|
512
532
|
node = {},
|
|
513
533
|
refPoint = {}
|
|
514
534
|
) => {
|
|
515
|
-
const { children:
|
|
535
|
+
const { children: astChildren, name: astName, type: astType } = ast;
|
|
516
536
|
const { nodeType, ownerDocument } = node;
|
|
517
537
|
const res = new Set();
|
|
518
|
-
if (
|
|
519
|
-
if (Array.isArray(
|
|
520
|
-
const [
|
|
538
|
+
if (astType === PSEUDO_CLASS_SELECTOR && nodeType === ELEMENT_NODE) {
|
|
539
|
+
if (Array.isArray(astChildren)) {
|
|
540
|
+
const [astChildAst] = astChildren;
|
|
521
541
|
// :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
|
|
522
|
-
if (REG_PSEUDO_NTH.test(
|
|
523
|
-
const arr = matchAnPlusB(
|
|
542
|
+
if (REG_PSEUDO_NTH.test(astName)) {
|
|
543
|
+
const arr = matchAnPlusB(astName, astChildAst, node);
|
|
524
544
|
if (arr.length) {
|
|
525
545
|
for (const i of arr) {
|
|
526
546
|
res.add(i);
|
|
527
547
|
}
|
|
528
548
|
}
|
|
529
549
|
} else {
|
|
530
|
-
switch (
|
|
550
|
+
switch (astName) {
|
|
531
551
|
case 'dir':
|
|
532
|
-
if (
|
|
552
|
+
if (astChildAst.name === node.dir) {
|
|
533
553
|
res.add(node);
|
|
534
554
|
}
|
|
535
555
|
break;
|
|
536
556
|
case 'lang':
|
|
537
|
-
if (matchLanguagePseudoClass(
|
|
557
|
+
if (matchLanguagePseudoClass(astChildAst, node)) {
|
|
538
558
|
res.add(node);
|
|
539
559
|
}
|
|
540
560
|
break;
|
|
541
561
|
case 'current':
|
|
542
562
|
case 'nth-col':
|
|
543
563
|
case 'nth-last-col':
|
|
544
|
-
console.warn(`Unsupported pseudo class ${
|
|
564
|
+
console.warn(`Unsupported pseudo class ${astName}`);
|
|
545
565
|
break;
|
|
546
566
|
default:
|
|
547
|
-
console.warn(`Unknown pseudo class ${
|
|
567
|
+
console.warn(`Unknown pseudo class ${astName}`);
|
|
548
568
|
}
|
|
549
569
|
}
|
|
550
570
|
} else {
|
|
551
571
|
const root = ownerDocument.documentElement;
|
|
552
572
|
const docURL = new URL(ownerDocument.URL);
|
|
553
|
-
switch (
|
|
573
|
+
switch (astName) {
|
|
554
574
|
case 'any-link':
|
|
555
575
|
case 'link':
|
|
556
576
|
// FIXME: what about namespaced href? e.g. xlink:href
|
|
@@ -576,6 +596,19 @@ const matchPseudoClassSelector = (
|
|
|
576
596
|
res.add(node);
|
|
577
597
|
}
|
|
578
598
|
break;
|
|
599
|
+
case 'target-within':
|
|
600
|
+
if (docURL.hash) {
|
|
601
|
+
const hash = docURL.hash.replace(/^#/, '');
|
|
602
|
+
let current = ownerDocument.getElementById(hash);
|
|
603
|
+
while (current) {
|
|
604
|
+
if (current === node) {
|
|
605
|
+
res.add(node);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
current = current.parentNode;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
579
612
|
case 'scope':
|
|
580
613
|
if (refPoint?.nodeType === ELEMENT_NODE) {
|
|
581
614
|
if (node === refPoint) {
|
|
@@ -590,6 +623,17 @@ const matchPseudoClassSelector = (
|
|
|
590
623
|
res.add(node);
|
|
591
624
|
}
|
|
592
625
|
break;
|
|
626
|
+
case 'focus-within': {
|
|
627
|
+
let current = ownerDocument.activeElement;
|
|
628
|
+
while (current) {
|
|
629
|
+
if (current === node) {
|
|
630
|
+
res.add(node);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
current = current.parentNode;
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
593
637
|
case 'open':
|
|
594
638
|
if (node.hasAttribute('open')) {
|
|
595
639
|
res.add(node);
|
|
@@ -693,7 +737,6 @@ const matchPseudoClassSelector = (
|
|
|
693
737
|
case 'default':
|
|
694
738
|
case 'empty':
|
|
695
739
|
case 'focus-visible':
|
|
696
|
-
case 'focus-within':
|
|
697
740
|
case 'fullscreen':
|
|
698
741
|
case 'future':
|
|
699
742
|
case 'hover':
|
|
@@ -712,15 +755,14 @@ const matchPseudoClassSelector = (
|
|
|
712
755
|
case 'read-write':
|
|
713
756
|
case 'seeking':
|
|
714
757
|
case 'stalled':
|
|
715
|
-
case 'target-within':
|
|
716
758
|
case 'user-invalid':
|
|
717
759
|
case 'user-valid':
|
|
718
760
|
case 'valid':
|
|
719
761
|
case 'volume-locked':
|
|
720
|
-
console.warn(`Unsupported pseudo class ${
|
|
762
|
+
console.warn(`Unsupported pseudo class ${astName}`);
|
|
721
763
|
break;
|
|
722
764
|
default:
|
|
723
|
-
console.warn(`Unknown pseudo class ${
|
|
765
|
+
console.warn(`Unknown pseudo class ${astName}`);
|
|
724
766
|
}
|
|
725
767
|
}
|
|
726
768
|
}
|
|
@@ -751,7 +793,7 @@ class Matcher {
|
|
|
751
793
|
|
|
752
794
|
/**
|
|
753
795
|
* create iterator
|
|
754
|
-
* @param {object} ast -
|
|
796
|
+
* @param {object} ast - AST
|
|
755
797
|
* @param {object} root - root node
|
|
756
798
|
* @returns {object} - iterator
|
|
757
799
|
*/
|
|
@@ -769,8 +811,8 @@ class Matcher {
|
|
|
769
811
|
|
|
770
812
|
/**
|
|
771
813
|
* parse ast and run
|
|
772
|
-
* @param {object} ast -
|
|
773
|
-
* @param {object} node -
|
|
814
|
+
* @param {object} ast - AST
|
|
815
|
+
* @param {object} node - Element node
|
|
774
816
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
775
817
|
*/
|
|
776
818
|
_parseAst(ast, node) {
|
|
@@ -791,8 +833,8 @@ class Matcher {
|
|
|
791
833
|
|
|
792
834
|
/**
|
|
793
835
|
* match adjacent leaves
|
|
794
|
-
* @param {Array.<object>} leaves - array of
|
|
795
|
-
* @param {object} node -
|
|
836
|
+
* @param {Array.<object>} leaves - array of AST leaves
|
|
837
|
+
* @param {object} node - Element node
|
|
796
838
|
* @returns {?object} - matched node
|
|
797
839
|
*/
|
|
798
840
|
_matchAdjacentLeaves(leaves, node) {
|
|
@@ -827,8 +869,8 @@ class Matcher {
|
|
|
827
869
|
|
|
828
870
|
/**
|
|
829
871
|
* match combinator
|
|
830
|
-
* @param {Array.<object>} leaves - array of
|
|
831
|
-
* @param {object} prevNode -
|
|
872
|
+
* @param {Array.<object>} leaves - array of AST leaves
|
|
873
|
+
* @param {object} prevNode - Element node
|
|
832
874
|
* @returns {Array.<object|undefined>} - matched nodes
|
|
833
875
|
*/
|
|
834
876
|
_matchCombinator(leaves, prevNode) {
|
|
@@ -853,10 +895,10 @@ class Matcher {
|
|
|
853
895
|
nextNode = iterator.nextNode();
|
|
854
896
|
}
|
|
855
897
|
while (items.length) {
|
|
856
|
-
const
|
|
898
|
+
const ast = items.shift();
|
|
857
899
|
if (nodes.size) {
|
|
858
900
|
nodes.forEach(node => {
|
|
859
|
-
const arr = this._match(
|
|
901
|
+
const arr = this._match(ast, node);
|
|
860
902
|
if (!arr.length) {
|
|
861
903
|
nodes.delete(node);
|
|
862
904
|
}
|
|
@@ -907,8 +949,8 @@ class Matcher {
|
|
|
907
949
|
|
|
908
950
|
/**
|
|
909
951
|
* match argument leaf
|
|
910
|
-
* @param {object} leaf -
|
|
911
|
-
* @param {object} node -
|
|
952
|
+
* @param {object} leaf - AST leaf
|
|
953
|
+
* @param {object} node - Element node
|
|
912
954
|
* @returns {Array.<object|undefined>} - matched nodes
|
|
913
955
|
*/
|
|
914
956
|
_matchArgumentLeaf(leaf, node) {
|
|
@@ -929,16 +971,16 @@ class Matcher {
|
|
|
929
971
|
|
|
930
972
|
/**
|
|
931
973
|
* match logical pseudo class functions - :is(), :has(), :not(), :where()
|
|
932
|
-
* @param {object}
|
|
933
|
-
* @param {object} node -
|
|
974
|
+
* @param {object} branch - AST branch
|
|
975
|
+
* @param {object} node - Element node
|
|
934
976
|
* @returns {?object} - matched node
|
|
935
977
|
*/
|
|
936
|
-
_matchLogicalPseudoFunc(
|
|
937
|
-
const ast = walkAst(
|
|
978
|
+
_matchLogicalPseudoFunc(branch, node) {
|
|
979
|
+
const ast = walkAst(branch);
|
|
938
980
|
let res;
|
|
939
981
|
if (ast.length) {
|
|
940
|
-
const { name:
|
|
941
|
-
switch (
|
|
982
|
+
const { name: branchName } = branch;
|
|
983
|
+
switch (branchName) {
|
|
942
984
|
// :has()
|
|
943
985
|
case 'has': {
|
|
944
986
|
let matched;
|
|
@@ -1019,7 +1061,7 @@ class Matcher {
|
|
|
1019
1061
|
/**
|
|
1020
1062
|
* match selector
|
|
1021
1063
|
* @param {Array.<object>} children - selector children
|
|
1022
|
-
* @param {object} node -
|
|
1064
|
+
* @param {object} node - Element node
|
|
1023
1065
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
1024
1066
|
*/
|
|
1025
1067
|
_matchSelector(children, node) {
|
|
@@ -1120,9 +1162,9 @@ class Matcher {
|
|
|
1120
1162
|
}
|
|
1121
1163
|
|
|
1122
1164
|
/**
|
|
1123
|
-
* match
|
|
1124
|
-
* @param {object}
|
|
1125
|
-
* @param {object}
|
|
1165
|
+
* match AST and node
|
|
1166
|
+
* @param {object} ast - AST
|
|
1167
|
+
* @param {object} node - Element node
|
|
1126
1168
|
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
1127
1169
|
*/
|
|
1128
1170
|
_match(ast = this.#ast, node = this.#node) {
|
package/src/js/parser.js
CHANGED
|
@@ -22,19 +22,19 @@ const parseSelector = selector => {
|
|
|
22
22
|
/**
|
|
23
23
|
* walk AST
|
|
24
24
|
* @param {object} ast - AST
|
|
25
|
-
* @returns {Array.<object|undefined>} -
|
|
25
|
+
* @returns {Array.<object|undefined>} - collection of AST branches
|
|
26
26
|
*/
|
|
27
27
|
const walkAst = (ast = {}) => {
|
|
28
28
|
const selectors = new Set();
|
|
29
29
|
const opt = {
|
|
30
|
-
enter:
|
|
31
|
-
if (
|
|
32
|
-
selectors.add(
|
|
30
|
+
enter: branch => {
|
|
31
|
+
if (branch.type === SELECTOR) {
|
|
32
|
+
selectors.add(branch.children);
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
|
-
leave:
|
|
35
|
+
leave: branch => {
|
|
36
36
|
let skip;
|
|
37
|
-
if (
|
|
37
|
+
if (branch.type === SELECTOR) {
|
|
38
38
|
skip = walkAst.skip;
|
|
39
39
|
}
|
|
40
40
|
return skip;
|
package/types/js/constant.d.ts
CHANGED
|
@@ -1,41 +1,13 @@
|
|
|
1
1
|
export const AN_PLUS_B: string;
|
|
2
|
-
export const AT_RULE: string;
|
|
3
|
-
export const AT_RULE_PRELUDE: string;
|
|
4
2
|
export const ATTRIBUTE_SELECTOR: string;
|
|
5
|
-
export const BLOCK: string;
|
|
6
|
-
export const BRACKETS: string;
|
|
7
|
-
export const CDC: string;
|
|
8
|
-
export const CDO: string;
|
|
9
3
|
export const CLASS_SELECTOR: string;
|
|
10
4
|
export const COMBINATOR: string;
|
|
11
|
-
export const COMMENT: string;
|
|
12
|
-
export const DECLARATION: string;
|
|
13
|
-
export const DECLARATION_LIST: string;
|
|
14
|
-
export const DIMENSION: string;
|
|
15
|
-
export const FUNCTION: string;
|
|
16
|
-
export const HASH: string;
|
|
17
5
|
export const ID_SELECTOR: string;
|
|
18
6
|
export const IDENTIFIER: string;
|
|
19
|
-
export const MEDIA_FEATURE: string;
|
|
20
|
-
export const MEDIA_QUERY: string;
|
|
21
|
-
export const MEDIA_QUERY_LIST: string;
|
|
22
|
-
export const NESTING_SELECTOR: string;
|
|
23
7
|
export const NTH: string;
|
|
24
|
-
export const NUMBER: string;
|
|
25
|
-
export const OPERATOR: string;
|
|
26
|
-
export const PARENTHESES: string;
|
|
27
|
-
export const PERCENTAGE: string;
|
|
28
8
|
export const PSEUDO_CLASS_SELECTOR: string;
|
|
29
|
-
export const PSEUDO_ELEMENT_SELECTOR: string;
|
|
30
|
-
export const RATIO: string;
|
|
31
9
|
export const RAW: string;
|
|
32
|
-
export const RULE: string;
|
|
33
10
|
export const SELECTOR: string;
|
|
34
11
|
export const SELECTOR_LIST: string;
|
|
35
12
|
export const STRING: string;
|
|
36
|
-
export const STYLE_SHEET: string;
|
|
37
13
|
export const TYPE_SELECTOR: string;
|
|
38
|
-
export const UNICODE_RANGE: string;
|
|
39
|
-
export const URL: string;
|
|
40
|
-
export const VALUE: string;
|
|
41
|
-
export const WHITESPACE: string;
|
package/types/js/matcher.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export class Matcher {
|
|
|
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>;
|
|
8
|
-
_matchLogicalPseudoFunc(
|
|
8
|
+
_matchLogicalPseudoFunc(branch: object, node: object): object | null;
|
|
9
9
|
_matchSelector(children: Array<object>, node: object): Array<object | undefined>;
|
|
10
10
|
_match(ast?: object, node?: object): Array<object | undefined>;
|
|
11
11
|
matches(): boolean;
|
|
@@ -24,10 +24,10 @@ export function collectNthOfType(node?: object, opt?: {
|
|
|
24
24
|
b: number;
|
|
25
25
|
reverse?: boolean;
|
|
26
26
|
}): Array<object | undefined>;
|
|
27
|
-
export function matchAnPlusB(
|
|
28
|
-
export function matchAttributeSelector(
|
|
29
|
-
export function matchClassSelector(
|
|
30
|
-
export function matchIdSelector(
|
|
31
|
-
export function matchLanguagePseudoClass(
|
|
32
|
-
export function matchPseudoClassSelector(
|
|
33
|
-
export function matchTypeSelector(
|
|
27
|
+
export function matchAnPlusB(nthName: string, ast?: object, node?: object): Array<object | undefined>;
|
|
28
|
+
export function matchAttributeSelector(ast?: object, node?: object): object | null;
|
|
29
|
+
export function matchClassSelector(ast?: object, node?: object): object | null;
|
|
30
|
+
export function matchIdSelector(ast?: object, node?: object): object | null;
|
|
31
|
+
export function matchLanguagePseudoClass(ast?: object, node?: object): object | null;
|
|
32
|
+
export function matchPseudoClassSelector(ast?: object, node?: object, refPoint?: object): Array<object | undefined>;
|
|
33
|
+
export function matchTypeSelector(ast?: object, node?: object): object | null;
|