happy-dom 9.9.1 → 9.10.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.
Potentially problematic release.
This version of happy-dom might be problematic. Click here for more details.
- package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js +1 -1
- package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js.map +1 -1
- package/lib/nodes/document/Document.d.ts.map +1 -1
- package/lib/nodes/document/Document.js +3 -0
- package/lib/nodes/document/Document.js.map +1 -1
- package/lib/nodes/document-fragment/DocumentFragment.d.ts.map +1 -1
- package/lib/nodes/document-fragment/DocumentFragment.js +3 -0
- package/lib/nodes/document-fragment/DocumentFragment.js.map +1 -1
- package/lib/nodes/element/Element.d.ts.map +1 -1
- package/lib/nodes/element/Element.js +5 -15
- package/lib/nodes/element/Element.js.map +1 -1
- package/lib/nodes/element/ElementUtility.d.ts.map +1 -1
- package/lib/nodes/element/ElementUtility.js +18 -22
- package/lib/nodes/element/ElementUtility.js.map +1 -1
- package/lib/nodes/node/Node.d.ts +0 -8
- package/lib/nodes/node/Node.d.ts.map +1 -1
- package/lib/nodes/node/Node.js +4 -23
- package/lib/nodes/node/Node.js.map +1 -1
- package/lib/nodes/node/NodeUtility.d.ts.map +1 -1
- package/lib/nodes/node/NodeUtility.js +3 -4
- package/lib/nodes/node/NodeUtility.js.map +1 -1
- package/lib/query-selector/ISelectorAttribute.d.ts +6 -0
- package/lib/query-selector/ISelectorAttribute.d.ts.map +1 -0
- package/lib/query-selector/ISelectorAttribute.js +3 -0
- package/lib/query-selector/ISelectorAttribute.js.map +1 -0
- package/lib/query-selector/ISelectorMatch.d.ts +4 -0
- package/lib/query-selector/ISelectorMatch.d.ts.map +1 -0
- package/lib/query-selector/ISelectorMatch.js +3 -0
- package/lib/query-selector/ISelectorMatch.js.map +1 -0
- package/lib/query-selector/QuerySelector.d.ts +21 -31
- package/lib/query-selector/QuerySelector.d.ts.map +1 -1
- package/lib/query-selector/QuerySelector.js +123 -131
- package/lib/query-selector/QuerySelector.js.map +1 -1
- package/lib/query-selector/SelectorCombinatorEnum.d.ts +7 -0
- package/lib/query-selector/SelectorCombinatorEnum.d.ts.map +1 -0
- package/lib/query-selector/SelectorCombinatorEnum.js +10 -0
- package/lib/query-selector/SelectorCombinatorEnum.js.map +1 -0
- package/lib/query-selector/SelectorItem.d.ts +41 -56
- package/lib/query-selector/SelectorItem.d.ts.map +1 -1
- package/lib/query-selector/SelectorItem.js +194 -220
- package/lib/query-selector/SelectorItem.js.map +1 -1
- package/lib/query-selector/SelectorParser.d.ts +21 -0
- package/lib/query-selector/SelectorParser.d.ts.map +1 -0
- package/lib/query-selector/SelectorParser.js +154 -0
- package/lib/query-selector/SelectorParser.js.map +1 -0
- package/package.json +1 -1
- package/src/css/declaration/utilities/CSSStyleDeclarationElementStyle.ts +2 -2
- package/src/nodes/document/Document.ts +6 -0
- package/src/nodes/document-fragment/DocumentFragment.ts +6 -0
- package/src/nodes/element/Element.ts +9 -17
- package/src/nodes/element/ElementUtility.ts +20 -25
- package/src/nodes/node/Node.ts +6 -25
- package/src/nodes/node/NodeUtility.ts +3 -8
- package/src/query-selector/ISelectorAttribute.ts +5 -0
- package/src/query-selector/ISelectorMatch.ts +3 -0
- package/src/query-selector/QuerySelector.ts +187 -170
- package/src/query-selector/SelectorCombinatorEnum.ts +7 -0
- package/src/query-selector/SelectorItem.ts +238 -264
- package/src/query-selector/SelectorParser.ts +148 -0
@@ -2,53 +2,55 @@ import DOMException from '../exception/DOMException';
|
|
2
2
|
import IElement from '../nodes/element/IElement';
|
3
3
|
import Element from '../nodes/element/Element';
|
4
4
|
import IHTMLInputElement from '../nodes/html-input-element/IHTMLInputElement';
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
const PSUEDO_REGEXP =
|
10
|
-
/(?<!\\):([a-zA-Z-]+)\(([0-9n+-]+|odd|even)\)|(?<!\\):not\(([^)]+)\)|(?<!\\):([a-zA-Z-]+)/g;
|
11
|
-
const CLASS_REGEXP = /\.(([a-zA-Z0-9-_$]|\\.)+)/g;
|
12
|
-
const TAG_NAME_REGEXP = /^[a-zA-Z0-9-]+/;
|
13
|
-
const ID_REGEXP = /(?<!\\)#[A-Za-z][-A-Za-z0-9_]*/g;
|
14
|
-
const CSS_ESCAPE_REGEXP = /(?<!\\):/g;
|
15
|
-
const CSS_ESCAPE_CHAR_REGEXP = /\\/g;
|
5
|
+
import SelectorCombinatorEnum from './SelectorCombinatorEnum';
|
6
|
+
import ISelectorAttribute from './ISelectorAttribute';
|
7
|
+
import SelectorParser from './SelectorParser';
|
8
|
+
import ISelectorMatch from './ISelectorMatch';
|
16
9
|
|
17
10
|
/**
|
18
11
|
* Selector item.
|
19
12
|
*/
|
20
13
|
export default class SelectorItem {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
private id: string;
|
14
|
+
public all: string | null;
|
15
|
+
public tagName: string | null;
|
16
|
+
public id: string | null;
|
17
|
+
public classNames: string[] | null;
|
18
|
+
public attributes: ISelectorAttribute[] | null;
|
19
|
+
public pseudoClass: string | null;
|
20
|
+
public pseudoArguments: string | null;
|
21
|
+
public combinator: SelectorCombinatorEnum;
|
30
22
|
|
31
23
|
/**
|
32
24
|
* Constructor.
|
33
25
|
*
|
34
|
-
* @param
|
26
|
+
* @param [options] Options.
|
27
|
+
* @param [options.combinator] Combinator.
|
28
|
+
* @param [options.all] All.
|
29
|
+
* @param [options.tagName] Tag name.
|
30
|
+
* @param [options.id] ID.
|
31
|
+
* @param [options.classNames] Class names.
|
32
|
+
* @param [options.attributes] Attributes.
|
33
|
+
* @param [options.pseudoClass] Pseudo class.
|
34
|
+
* @param [options.pseudoArguments] Pseudo arguments.
|
35
35
|
*/
|
36
|
-
constructor(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
this.tagName =
|
48
|
-
this.
|
49
|
-
this.
|
50
|
-
this.
|
51
|
-
this.
|
36
|
+
constructor(options?: {
|
37
|
+
all?: string;
|
38
|
+
tagName?: string;
|
39
|
+
id?: string;
|
40
|
+
classNames?: string[];
|
41
|
+
attributes?: ISelectorAttribute[];
|
42
|
+
pseudoClass?: string;
|
43
|
+
pseudoArguments?: string;
|
44
|
+
combinator?: SelectorCombinatorEnum;
|
45
|
+
}) {
|
46
|
+
this.all = options?.all || null;
|
47
|
+
this.tagName = options?.tagName || null;
|
48
|
+
this.id = options?.id || null;
|
49
|
+
this.classNames = options?.classNames || null;
|
50
|
+
this.attributes = options?.attributes || null;
|
51
|
+
this.pseudoClass = options?.pseudoClass || null;
|
52
|
+
this.pseudoArguments = options?.pseudoArguments || null;
|
53
|
+
this.combinator = options?.combinator || SelectorCombinatorEnum.descendant;
|
52
54
|
}
|
53
55
|
|
54
56
|
/**
|
@@ -57,176 +59,97 @@ export default class SelectorItem {
|
|
57
59
|
* @param element HTML element.
|
58
60
|
* @returns Result.
|
59
61
|
*/
|
60
|
-
public match(element: IElement):
|
61
|
-
const selector = this.selector;
|
62
|
-
|
62
|
+
public match(element: IElement): ISelectorMatch | null {
|
63
63
|
let priorityWeight = 0;
|
64
64
|
|
65
|
-
//
|
66
|
-
if (this.
|
67
|
-
|
65
|
+
// Tag name match
|
66
|
+
if (this.tagName) {
|
67
|
+
if (this.tagName !== element.tagName) {
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
priorityWeight += 1;
|
68
71
|
}
|
69
72
|
|
70
73
|
// ID Match
|
71
|
-
if (this.
|
72
|
-
priorityWeight += 100;
|
73
|
-
|
74
|
+
if (this.id) {
|
74
75
|
if (this.id !== element.id) {
|
75
|
-
return
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
// Tag name match
|
80
|
-
if (this.isTagName) {
|
81
|
-
priorityWeight += 1;
|
82
|
-
|
83
|
-
if (this.tagName !== element.tagName) {
|
84
|
-
return { priorityWeight: 0, matches: false };
|
76
|
+
return null;
|
85
77
|
}
|
78
|
+
priorityWeight += 100;
|
86
79
|
}
|
87
80
|
|
88
81
|
// Class match
|
89
|
-
if (this.
|
90
|
-
const result = this.
|
91
|
-
|
92
|
-
|
93
|
-
return { priorityWeight: 0, matches: false };
|
82
|
+
if (this.classNames) {
|
83
|
+
const result = this.matchClass(element);
|
84
|
+
if (!result) {
|
85
|
+
return null;
|
94
86
|
}
|
95
|
-
|
96
|
-
|
97
|
-
// Pseudo match
|
98
|
-
if (this.isPseudo && !this.matchesPsuedo(element, selector)) {
|
99
|
-
return { priorityWeight: 0, matches: false };
|
87
|
+
priorityWeight += result.priorityWeight;
|
100
88
|
}
|
101
89
|
|
102
90
|
// Attribute match
|
103
|
-
if (this.
|
104
|
-
const result = this.
|
105
|
-
|
106
|
-
|
107
|
-
return { priorityWeight: 0, matches: false };
|
91
|
+
if (this.attributes) {
|
92
|
+
const result = this.matchAttributes(element);
|
93
|
+
if (!result) {
|
94
|
+
return null;
|
108
95
|
}
|
96
|
+
priorityWeight += result.priorityWeight;
|
109
97
|
}
|
110
98
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
/**
|
115
|
-
* Matches a psuedo selector.
|
116
|
-
*
|
117
|
-
* @param element Element.
|
118
|
-
* @param selector Selector.
|
119
|
-
* @returns True if it is a match.
|
120
|
-
*/
|
121
|
-
private matchesPsuedo(element: IElement, selector: string): boolean {
|
122
|
-
const regexp = new RegExp(PSUEDO_REGEXP, 'g');
|
123
|
-
let match: RegExpMatchArray;
|
124
|
-
|
125
|
-
while ((match = regexp.exec(selector))) {
|
126
|
-
const isNotClass = match[3] && match[3].trim()[0] === '.';
|
127
|
-
if (match[1] && !this.matchesNthChild(element, match[1], match[2])) {
|
128
|
-
return false;
|
129
|
-
} else if (match[3]) {
|
130
|
-
if (isNotClass && this.matchesClass(element, match[3]).matches) {
|
131
|
-
return false;
|
132
|
-
}
|
133
|
-
if (
|
134
|
-
!isNotClass &&
|
135
|
-
match[3].includes('[') &&
|
136
|
-
this.matchesAttribute(element, match[3]).matches
|
137
|
-
) {
|
138
|
-
return false;
|
139
|
-
}
|
140
|
-
if (!isNotClass && element.tagName.toLowerCase() === match[3]) {
|
141
|
-
return false;
|
142
|
-
}
|
143
|
-
} else if (match[4] && !this.matchesPsuedoExpression(element, match[4])) {
|
144
|
-
return false;
|
145
|
-
}
|
99
|
+
// Pseudo match
|
100
|
+
if (this.pseudoClass && !this.matchPsuedo(element)) {
|
101
|
+
return null;
|
146
102
|
}
|
147
103
|
|
148
|
-
return
|
104
|
+
return { priorityWeight };
|
149
105
|
}
|
150
106
|
|
151
107
|
/**
|
152
|
-
* Matches a
|
108
|
+
* Matches a psuedo selector.
|
153
109
|
*
|
154
110
|
* @param element Element.
|
155
|
-
* @
|
156
|
-
* @param place Place.
|
157
|
-
* @returns True if it is a match.
|
111
|
+
* @returns Result.
|
158
112
|
*/
|
159
|
-
private
|
160
|
-
|
113
|
+
private matchPsuedo(element: IElement): boolean {
|
114
|
+
const parent = <IElement>element.parentNode;
|
161
115
|
|
162
|
-
|
116
|
+
// Validation
|
117
|
+
switch (this.pseudoClass) {
|
118
|
+
case 'not':
|
119
|
+
case 'nth-child':
|
163
120
|
case 'nth-of-type':
|
164
|
-
children = children.filter((child) => child.tagName === element.tagName);
|
165
|
-
break;
|
166
121
|
case 'nth-last-child':
|
167
|
-
children = children.reverse();
|
168
|
-
break;
|
169
122
|
case 'nth-last-of-type':
|
170
|
-
|
171
|
-
|
172
|
-
}
|
173
|
-
|
174
|
-
if (place === 'odd') {
|
175
|
-
const index = children.indexOf(element);
|
176
|
-
return index !== -1 && (index + 1) % 2 !== 0;
|
177
|
-
} else if (place === 'even') {
|
178
|
-
const index = children.indexOf(element);
|
179
|
-
return index !== -1 && (index + 1) % 2 === 0;
|
180
|
-
} else if (place.includes('n')) {
|
181
|
-
const [a, b] = place.replace(/ /g, '').split('n');
|
182
|
-
const childIndex = children.indexOf(element);
|
183
|
-
const aNumber = a !== '' ? Number(a) : 1;
|
184
|
-
const bNumber = b !== undefined ? Number(b) : 0;
|
185
|
-
if (isNaN(aNumber) || isNaN(bNumber)) {
|
186
|
-
throw new DOMException(`The selector "${this.selector}" is not valid.`);
|
187
|
-
}
|
188
|
-
|
189
|
-
for (let i = 0, max = children.length; i <= max; i += aNumber) {
|
190
|
-
if (childIndex === i + bNumber - 1) {
|
191
|
-
return true;
|
123
|
+
if (!this.pseudoArguments) {
|
124
|
+
throw new DOMException(`The selector "${this.getSelectorString()}" is not valid.`);
|
192
125
|
}
|
193
|
-
|
194
|
-
|
195
|
-
return false;
|
196
|
-
}
|
197
|
-
|
198
|
-
const number = Number(place);
|
199
|
-
|
200
|
-
if (isNaN(number)) {
|
201
|
-
throw new DOMException(`The selector "${this.selector}" is not valid.`);
|
126
|
+
break;
|
202
127
|
}
|
203
128
|
|
204
|
-
|
205
|
-
}
|
206
|
-
|
207
|
-
/**
|
208
|
-
* Matches a psuedo selector expression.
|
209
|
-
*
|
210
|
-
* @param element Element.
|
211
|
-
* @param psuedo Psuedo name.
|
212
|
-
* @returns True if it is a match.
|
213
|
-
*/
|
214
|
-
private matchesPsuedoExpression(element: IElement, psuedo: string): boolean {
|
215
|
-
const parent = <IElement>element.parentNode;
|
216
|
-
|
129
|
+
// Check if parent exists
|
217
130
|
if (!parent) {
|
218
|
-
|
131
|
+
switch (this.pseudoClass) {
|
132
|
+
case 'first-child':
|
133
|
+
case 'last-child':
|
134
|
+
case 'only-child':
|
135
|
+
case 'first-of-type':
|
136
|
+
case 'last-of-type':
|
137
|
+
case 'only-of-type':
|
138
|
+
case 'nth-child':
|
139
|
+
case 'nth-of-type':
|
140
|
+
case 'nth-last-child':
|
141
|
+
case 'nth-last-of-type':
|
142
|
+
return false;
|
143
|
+
}
|
219
144
|
}
|
220
145
|
|
221
|
-
switch (
|
146
|
+
switch (this.pseudoClass) {
|
222
147
|
case 'first-child':
|
223
148
|
return parent.children[0] === element;
|
224
149
|
case 'last-child':
|
225
|
-
|
226
|
-
return lastChildChildren[lastChildChildren.length - 1] === element;
|
150
|
+
return parent.children.length && parent.children[parent.children.length - 1] === element;
|
227
151
|
case 'only-child':
|
228
|
-
|
229
|
-
return onlyChildChildren.length === 1 && onlyChildChildren[0] === element;
|
152
|
+
return parent.children.length === 1 && parent.children[0] === element;
|
230
153
|
case 'first-of-type':
|
231
154
|
for (const child of parent.children) {
|
232
155
|
if (child.tagName === element.tagName) {
|
@@ -255,139 +178,190 @@ export default class SelectorItem {
|
|
255
178
|
return isFound;
|
256
179
|
case 'checked':
|
257
180
|
return element.tagName === 'INPUT' && (<IHTMLInputElement>element).checked;
|
181
|
+
case 'empty':
|
182
|
+
return !element.children.length;
|
183
|
+
case 'root':
|
184
|
+
return element.tagName === 'HTML';
|
185
|
+
case 'not':
|
186
|
+
return !SelectorParser.getSelectorItem(this.pseudoArguments).match(element);
|
187
|
+
case 'nth-child':
|
188
|
+
return this.matchNthChild(element, parent.children, this.pseudoArguments);
|
189
|
+
case 'nth-of-type':
|
190
|
+
if (!element.parentNode) {
|
191
|
+
return false;
|
192
|
+
}
|
193
|
+
return this.matchNthChild(
|
194
|
+
element,
|
195
|
+
parent.children.filter((child) => child.tagName === element.tagName),
|
196
|
+
this.pseudoArguments
|
197
|
+
);
|
198
|
+
case 'nth-last-child':
|
199
|
+
return this.matchNthChild(element, parent.children.reverse(), this.pseudoArguments);
|
200
|
+
case 'nth-last-of-type':
|
201
|
+
return this.matchNthChild(
|
202
|
+
element,
|
203
|
+
parent.children.filter((child) => child.tagName === element.tagName).reverse(),
|
204
|
+
this.pseudoArguments
|
205
|
+
);
|
258
206
|
}
|
259
|
-
|
260
|
-
return false;
|
261
207
|
}
|
262
208
|
|
263
209
|
/**
|
264
|
-
* Matches
|
210
|
+
* Matches a nth-child selector.
|
265
211
|
*
|
266
212
|
* @param element Element.
|
267
|
-
* @param
|
268
|
-
* @
|
213
|
+
* @param parentChildren Parent children.
|
214
|
+
* @param placement Placement.
|
215
|
+
* @returns True if it is a match.
|
269
216
|
*/
|
270
|
-
private
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
217
|
+
private matchNthChild(element: IElement, parentChildren: IElement[], placement: string): boolean {
|
218
|
+
if (placement === 'odd') {
|
219
|
+
const index = parentChildren.indexOf(element);
|
220
|
+
return index !== -1 && (index + 1) % 2 !== 0;
|
221
|
+
} else if (placement === 'even') {
|
222
|
+
const index = parentChildren.indexOf(element);
|
223
|
+
return index !== -1 && (index + 1) % 2 === 0;
|
224
|
+
} else if (placement.includes('n')) {
|
225
|
+
const [a, b] = placement.replace(/ /g, '').split('n');
|
226
|
+
const childIndex = parentChildren.indexOf(element);
|
227
|
+
const aNumber = a !== '' ? Number(a) : 1;
|
228
|
+
const bNumber = b !== undefined ? Number(b) : 0;
|
229
|
+
if (isNaN(aNumber) || isNaN(bNumber)) {
|
230
|
+
throw new DOMException(`The selector "${this.getSelectorString()}" is not valid.`);
|
231
|
+
}
|
277
232
|
|
278
|
-
|
279
|
-
|
233
|
+
for (let i = 0, max = parentChildren.length; i <= max; i += aNumber) {
|
234
|
+
if (childIndex === i + bNumber - 1) {
|
235
|
+
return true;
|
236
|
+
}
|
237
|
+
}
|
280
238
|
|
281
|
-
|
239
|
+
return false;
|
240
|
+
}
|
282
241
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
) {
|
288
|
-
return { priorityWeight: 0, matches: false };
|
289
|
-
}
|
242
|
+
const number = Number(placement);
|
243
|
+
|
244
|
+
if (isNaN(number)) {
|
245
|
+
throw new DOMException(`The selector "${this.getSelectorString()}" is not valid.`);
|
290
246
|
}
|
291
247
|
|
292
|
-
return
|
248
|
+
return parentChildren[number - 1] === element;
|
293
249
|
}
|
294
250
|
|
295
251
|
/**
|
296
|
-
* Matches
|
252
|
+
* Matches attribute.
|
297
253
|
*
|
298
254
|
* @param element Element.
|
299
|
-
* @param selector Selector.
|
300
255
|
* @returns Result.
|
301
256
|
*/
|
302
|
-
private
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
const classList = element.className.split(' ');
|
308
|
-
const classSelector = selector.split(CSS_ESCAPE_REGEXP)[0];
|
257
|
+
private matchAttributes(element: IElement): ISelectorMatch | null {
|
258
|
+
if (!this.attributes) {
|
259
|
+
return null;
|
260
|
+
}
|
261
|
+
|
309
262
|
let priorityWeight = 0;
|
310
|
-
let match: RegExpMatchArray;
|
311
263
|
|
312
|
-
|
264
|
+
for (const attribute of this.attributes) {
|
265
|
+
const elementAttribute = (<Element>element)._attributes[attribute.name];
|
266
|
+
|
267
|
+
if (!elementAttribute) {
|
268
|
+
return null;
|
269
|
+
}
|
270
|
+
|
313
271
|
priorityWeight += 10;
|
314
|
-
|
315
|
-
|
272
|
+
|
273
|
+
if (attribute.value !== null) {
|
274
|
+
if (!elementAttribute.value) {
|
275
|
+
return null;
|
276
|
+
}
|
277
|
+
|
278
|
+
switch (attribute.operator) {
|
279
|
+
// [attribute~="value"] - Contains a specified word.
|
280
|
+
case null:
|
281
|
+
if (attribute.value !== elementAttribute.value) {
|
282
|
+
return null;
|
283
|
+
}
|
284
|
+
break;
|
285
|
+
// [attribute~="value"] - Contains a specified word.
|
286
|
+
case '~':
|
287
|
+
if (!elementAttribute.value.split(' ').includes(attribute.value)) {
|
288
|
+
return null;
|
289
|
+
}
|
290
|
+
break;
|
291
|
+
// [attribute|="value"] - Starts with the specified word.
|
292
|
+
case '|':
|
293
|
+
if (!new RegExp(`^${attribute.value}[- ]`).test(elementAttribute.value)) {
|
294
|
+
return null;
|
295
|
+
}
|
296
|
+
break;
|
297
|
+
// [attribute^="value"] - Begins with a specified value.
|
298
|
+
case '^':
|
299
|
+
if (!elementAttribute.value.startsWith(attribute.value)) {
|
300
|
+
return null;
|
301
|
+
}
|
302
|
+
break;
|
303
|
+
// [attribute$="value"] - Ends with a specified value.
|
304
|
+
case '$':
|
305
|
+
if (!elementAttribute.value.endsWith(attribute.value)) {
|
306
|
+
return null;
|
307
|
+
}
|
308
|
+
break;
|
309
|
+
// [attribute*="value"] - Contains a specified value.
|
310
|
+
case '*':
|
311
|
+
if (!elementAttribute.value.includes(attribute.value)) {
|
312
|
+
return null;
|
313
|
+
}
|
314
|
+
break;
|
315
|
+
}
|
316
316
|
}
|
317
317
|
}
|
318
318
|
|
319
|
-
return { priorityWeight
|
319
|
+
return { priorityWeight };
|
320
320
|
}
|
321
321
|
|
322
322
|
/**
|
323
|
-
* Matches
|
323
|
+
* Matches class.
|
324
324
|
*
|
325
325
|
* @param element Element.
|
326
|
-
* @
|
327
|
-
* @returns True if it is a match.
|
326
|
+
* @returns Result.
|
328
327
|
*/
|
329
|
-
private
|
330
|
-
if (
|
331
|
-
|
328
|
+
private matchClass(element: IElement): ISelectorMatch | null {
|
329
|
+
if (!this.classNames) {
|
330
|
+
return null;
|
332
331
|
}
|
333
332
|
|
334
|
-
|
333
|
+
const classList = element.className.split(' ');
|
334
|
+
let priorityWeight = 0;
|
335
|
+
|
336
|
+
for (const className of this.classNames) {
|
337
|
+
if (!classList.includes(className)) {
|
338
|
+
return null;
|
339
|
+
}
|
340
|
+
priorityWeight += 10;
|
341
|
+
}
|
342
|
+
|
343
|
+
return { priorityWeight };
|
335
344
|
}
|
336
345
|
|
337
|
-
/** .
|
338
|
-
*
|
339
|
-
* Matches attribute name and value.
|
340
|
-
*
|
341
|
-
* @param element Element.
|
342
|
-
* @param attributeName Attribute name.
|
343
|
-
* @param attributeValue Attribute value.
|
344
|
-
* @param [matchType] Match type.
|
345
|
-
* @returns True if it is a match.
|
346
|
-
*/
|
347
346
|
/**
|
347
|
+
* Returns the selector string.
|
348
348
|
*
|
349
|
-
* @
|
350
|
-
* @param attributeName
|
351
|
-
* @param attributeValue
|
352
|
-
* @param matchType
|
349
|
+
* @returns Selector string.
|
353
350
|
*/
|
354
|
-
private
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
}
|
370
|
-
|
371
|
-
if (matchType) {
|
372
|
-
switch (matchType) {
|
373
|
-
// [attribute~="value"] - Contains a specified word.
|
374
|
-
case '~':
|
375
|
-
return attribute.value && attribute.value.split(' ').includes(value);
|
376
|
-
// [attribute|="value"] - Starts with the specified word.
|
377
|
-
case '|':
|
378
|
-
return attribute && attribute.value && new RegExp(`^${value}[- ]`).test(attribute.value);
|
379
|
-
// [attribute^="value"] - Begins with a specified value.
|
380
|
-
case '^':
|
381
|
-
return attribute && attribute.value && attribute.value.startsWith(value);
|
382
|
-
// [attribute$="value"] - Ends with a specified value.
|
383
|
-
case '$':
|
384
|
-
return attribute && attribute.value && attribute.value.endsWith(value);
|
385
|
-
// [attribute*="value"] - Contains a specified value.
|
386
|
-
case '*':
|
387
|
-
return attribute && attribute.value && attribute.value.includes(value);
|
388
|
-
}
|
389
|
-
}
|
390
|
-
|
391
|
-
return attribute && attribute.value === value;
|
351
|
+
private getSelectorString(): string {
|
352
|
+
return `${this.all || ''}${this.tagName || ''}${this.id ? `#${this.id}` : ''}${
|
353
|
+
this.classNames ? `.${this.classNames.join('.')}` : ''
|
354
|
+
}${
|
355
|
+
this.attributes
|
356
|
+
? this.attributes
|
357
|
+
.map(
|
358
|
+
(attribute) =>
|
359
|
+
`[${attribute.name}${
|
360
|
+
attribute.value ? `${attribute.operator || ''}="${attribute.value}"` : ''
|
361
|
+
}]`
|
362
|
+
)
|
363
|
+
.join('')
|
364
|
+
: ''
|
365
|
+
}${this.pseudoClass || ''}${this.pseudoArguments ? `(${this.pseudoArguments})` : ''}`;
|
392
366
|
}
|
393
367
|
}
|