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.

Files changed (59) hide show
  1. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js +1 -1
  2. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js.map +1 -1
  3. package/lib/nodes/document/Document.d.ts.map +1 -1
  4. package/lib/nodes/document/Document.js +3 -0
  5. package/lib/nodes/document/Document.js.map +1 -1
  6. package/lib/nodes/document-fragment/DocumentFragment.d.ts.map +1 -1
  7. package/lib/nodes/document-fragment/DocumentFragment.js +3 -0
  8. package/lib/nodes/document-fragment/DocumentFragment.js.map +1 -1
  9. package/lib/nodes/element/Element.d.ts.map +1 -1
  10. package/lib/nodes/element/Element.js +5 -15
  11. package/lib/nodes/element/Element.js.map +1 -1
  12. package/lib/nodes/element/ElementUtility.d.ts.map +1 -1
  13. package/lib/nodes/element/ElementUtility.js +18 -22
  14. package/lib/nodes/element/ElementUtility.js.map +1 -1
  15. package/lib/nodes/node/Node.d.ts +0 -8
  16. package/lib/nodes/node/Node.d.ts.map +1 -1
  17. package/lib/nodes/node/Node.js +4 -23
  18. package/lib/nodes/node/Node.js.map +1 -1
  19. package/lib/nodes/node/NodeUtility.d.ts.map +1 -1
  20. package/lib/nodes/node/NodeUtility.js +3 -4
  21. package/lib/nodes/node/NodeUtility.js.map +1 -1
  22. package/lib/query-selector/ISelectorAttribute.d.ts +6 -0
  23. package/lib/query-selector/ISelectorAttribute.d.ts.map +1 -0
  24. package/lib/query-selector/ISelectorAttribute.js +3 -0
  25. package/lib/query-selector/ISelectorAttribute.js.map +1 -0
  26. package/lib/query-selector/ISelectorMatch.d.ts +4 -0
  27. package/lib/query-selector/ISelectorMatch.d.ts.map +1 -0
  28. package/lib/query-selector/ISelectorMatch.js +3 -0
  29. package/lib/query-selector/ISelectorMatch.js.map +1 -0
  30. package/lib/query-selector/QuerySelector.d.ts +21 -31
  31. package/lib/query-selector/QuerySelector.d.ts.map +1 -1
  32. package/lib/query-selector/QuerySelector.js +123 -131
  33. package/lib/query-selector/QuerySelector.js.map +1 -1
  34. package/lib/query-selector/SelectorCombinatorEnum.d.ts +7 -0
  35. package/lib/query-selector/SelectorCombinatorEnum.d.ts.map +1 -0
  36. package/lib/query-selector/SelectorCombinatorEnum.js +10 -0
  37. package/lib/query-selector/SelectorCombinatorEnum.js.map +1 -0
  38. package/lib/query-selector/SelectorItem.d.ts +41 -56
  39. package/lib/query-selector/SelectorItem.d.ts.map +1 -1
  40. package/lib/query-selector/SelectorItem.js +194 -220
  41. package/lib/query-selector/SelectorItem.js.map +1 -1
  42. package/lib/query-selector/SelectorParser.d.ts +21 -0
  43. package/lib/query-selector/SelectorParser.d.ts.map +1 -0
  44. package/lib/query-selector/SelectorParser.js +154 -0
  45. package/lib/query-selector/SelectorParser.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/css/declaration/utilities/CSSStyleDeclarationElementStyle.ts +2 -2
  48. package/src/nodes/document/Document.ts +6 -0
  49. package/src/nodes/document-fragment/DocumentFragment.ts +6 -0
  50. package/src/nodes/element/Element.ts +9 -17
  51. package/src/nodes/element/ElementUtility.ts +20 -25
  52. package/src/nodes/node/Node.ts +6 -25
  53. package/src/nodes/node/NodeUtility.ts +3 -8
  54. package/src/query-selector/ISelectorAttribute.ts +5 -0
  55. package/src/query-selector/ISelectorMatch.ts +3 -0
  56. package/src/query-selector/QuerySelector.ts +187 -170
  57. package/src/query-selector/SelectorCombinatorEnum.ts +7 -0
  58. package/src/query-selector/SelectorItem.ts +238 -264
  59. 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
- const ATTRIBUTE_REGEXP =
7
- /\[([a-zA-Z0-9-_]+)\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1})[ ]*=[ ]*["']{0,1}([^"']+)["']{0,1}\]/g;
8
- const ATTRIBUTE_NAME_REGEXP = /[^a-zA-Z0-9-_$]/;
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
- private isAll: boolean;
22
- private isID: boolean;
23
- private isAttribute: boolean;
24
- private isPseudo: boolean;
25
- private isClass: boolean;
26
- private isTagName: boolean;
27
- private tagName = null;
28
- private selector: string;
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 selector Selector.
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(selector: string) {
37
- const baseSelector = selector.replace(new RegExp(PSUEDO_REGEXP, 'g'), '');
38
- const idMatch = !this.isAll && selector.includes('#') ? baseSelector.match(ID_REGEXP) : null;
39
-
40
- this.isAll = baseSelector === '*';
41
- this.isID = !!idMatch;
42
- this.isAttribute = !this.isAll && baseSelector.includes('[');
43
- // If baseSelector !== selector then some psuedo selector was replaced above
44
- this.isPseudo = !this.isAll && baseSelector !== selector;
45
- this.isClass =
46
- !this.isAll && new RegExp(CLASS_REGEXP, 'g').test(baseSelector) && !this.isAttribute;
47
- this.tagName = !this.isAll ? baseSelector.match(TAG_NAME_REGEXP) : null;
48
- this.tagName = this.tagName ? this.tagName[0].toUpperCase() : null;
49
- this.isTagName = this.tagName !== null;
50
- this.selector = selector;
51
- this.id = idMatch ? idMatch[0].replace('#', '') : null;
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): { priorityWeight: number; matches: boolean } {
61
- const selector = this.selector;
62
-
62
+ public match(element: IElement): ISelectorMatch | null {
63
63
  let priorityWeight = 0;
64
64
 
65
- // Is all (*)
66
- if (this.isAll) {
67
- return { priorityWeight: 0, matches: true };
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.isID) {
72
- priorityWeight += 100;
73
-
74
+ if (this.id) {
74
75
  if (this.id !== element.id) {
75
- return { priorityWeight: 0, matches: false };
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.isClass) {
90
- const result = this.matchesClass(element, selector);
91
- priorityWeight += result.priorityWeight;
92
- if (!result.matches) {
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.isAttribute) {
104
- const result = this.matchesAttribute(element, selector);
105
- priorityWeight += result.priorityWeight;
106
- if (!result.matches) {
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
- return { priorityWeight, matches: true };
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 true;
104
+ return { priorityWeight };
149
105
  }
150
106
 
151
107
  /**
152
- * Matches a nth-child selector.
108
+ * Matches a psuedo selector.
153
109
  *
154
110
  * @param element Element.
155
- * @param psuedo Psuedo name.
156
- * @param place Place.
157
- * @returns True if it is a match.
111
+ * @returns Result.
158
112
  */
159
- private matchesNthChild(element: IElement, psuedo: string, place: string): boolean {
160
- let children = element.parentNode ? (<IElement>element.parentNode).children : [];
113
+ private matchPsuedo(element: IElement): boolean {
114
+ const parent = <IElement>element.parentNode;
161
115
 
162
- switch (psuedo.toLowerCase()) {
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
- children = children.filter((child) => child.tagName === element.tagName).reverse();
171
- break;
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
- return children[number - 1] === element;
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
- return false;
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 (psuedo.toLowerCase()) {
146
+ switch (this.pseudoClass) {
222
147
  case 'first-child':
223
148
  return parent.children[0] === element;
224
149
  case 'last-child':
225
- const lastChildChildren = parent.children;
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
- const onlyChildChildren = parent.children;
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 attribute.
210
+ * Matches a nth-child selector.
265
211
  *
266
212
  * @param element Element.
267
- * @param selector Selector.
268
- * @returns Result.
213
+ * @param parentChildren Parent children.
214
+ * @param placement Placement.
215
+ * @returns True if it is a match.
269
216
  */
270
- private matchesAttribute(
271
- element: IElement,
272
- selector: string
273
- ): { priorityWeight: number; matches: boolean } {
274
- const regexp = new RegExp(ATTRIBUTE_REGEXP, 'g');
275
- let match: RegExpMatchArray;
276
- let priorityWeight = 0;
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
- while ((match = regexp.exec(selector))) {
279
- const isPsuedo = match.index > 0 && selector[match.index - 1] === '(';
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
- priorityWeight += 10;
239
+ return false;
240
+ }
282
241
 
283
- if (
284
- !isPsuedo &&
285
- ((match[1] && !this.matchesAttributeName(element, match[1])) ||
286
- (match[2] && !this.matchesAttributeNameAndValue(element, match[2], match[4], match[3])))
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 { priorityWeight, matches: true };
248
+ return parentChildren[number - 1] === element;
293
249
  }
294
250
 
295
251
  /**
296
- * Matches class.
252
+ * Matches attribute.
297
253
  *
298
254
  * @param element Element.
299
- * @param selector Selector.
300
255
  * @returns Result.
301
256
  */
302
- private matchesClass(
303
- element: IElement,
304
- selector: string
305
- ): { priorityWeight: number; matches: boolean } {
306
- const regexp = new RegExp(CLASS_REGEXP, 'g');
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
- while ((match = regexp.exec(classSelector))) {
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
- if (!classList.includes(match[1].replace(CSS_ESCAPE_CHAR_REGEXP, ''))) {
315
- return { priorityWeight: 0, matches: false };
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, matches: true };
319
+ return { priorityWeight };
320
320
  }
321
321
 
322
322
  /**
323
- * Matches attribute name only.
323
+ * Matches class.
324
324
  *
325
325
  * @param element Element.
326
- * @param attributeName Attribute name.
327
- * @returns True if it is a match.
326
+ * @returns Result.
328
327
  */
329
- private matchesAttributeName(element: IElement, attributeName: string): boolean {
330
- if (ATTRIBUTE_NAME_REGEXP.test(attributeName)) {
331
- throw new DOMException(`The selector "${this.selector}" is not valid.`);
328
+ private matchClass(element: IElement): ISelectorMatch | null {
329
+ if (!this.classNames) {
330
+ return null;
332
331
  }
333
332
 
334
- return !!(<Element>element)._attributes[attributeName.toLowerCase()];
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
- * @param element
350
- * @param attributeName
351
- * @param attributeValue
352
- * @param matchType
349
+ * @returns Selector string.
353
350
  */
354
- private matchesAttributeNameAndValue(
355
- element: IElement,
356
- attributeName: string,
357
- attributeValue: string,
358
- matchType: string = null
359
- ): boolean {
360
- const attribute = (<Element>element)._attributes[attributeName.toLowerCase()];
361
- const value = attributeValue;
362
-
363
- if (ATTRIBUTE_NAME_REGEXP.test(attributeName)) {
364
- throw new DOMException(`The selector "${this.selector}" is not valid.`);
365
- }
366
-
367
- if (!attribute) {
368
- return false;
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
  }