@acemir/cssom 0.9.4 → 0.9.6
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/build/CSSOM.js +105 -29
- package/lib/parse.js +105 -29
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -1574,7 +1574,7 @@ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
|
|
|
1574
1574
|
* @param {string} token
|
|
1575
1575
|
*/
|
|
1576
1576
|
CSSOM.parse = function parse(token, errorHandler) {
|
|
1577
|
-
errorHandler = errorHandler === undefined
|
|
1577
|
+
errorHandler = errorHandler === undefined ? (console && console.error) : errorHandler;
|
|
1578
1578
|
|
|
1579
1579
|
var i = 0;
|
|
1580
1580
|
|
|
@@ -1798,13 +1798,14 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1798
1798
|
}
|
|
1799
1799
|
|
|
1800
1800
|
/**
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1801
|
+
* Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
|
|
1802
|
+
* This function replaces the previous basicSelectorRegExp.
|
|
1803
|
+
*
|
|
1804
|
+
* This function matches:
|
|
1804
1805
|
* - Type selectors (e.g., `div`, `span`)
|
|
1805
1806
|
* - Universal selector (`*`)
|
|
1806
|
-
* - ID selectors (e.g., `#header
|
|
1807
|
-
* - Class selectors (e.g., `.container
|
|
1807
|
+
* - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
|
|
1808
|
+
* - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
|
|
1808
1809
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
1809
1810
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
1810
1811
|
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
@@ -1813,13 +1814,66 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1813
1814
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
1814
1815
|
* - Whitespace (descendant combinator)
|
|
1815
1816
|
*
|
|
1816
|
-
*
|
|
1817
|
-
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
1818
|
-
* but does not match full CSS selector groups separated by commas.
|
|
1817
|
+
* Unicode and escape sequences are allowed in identifiers.
|
|
1819
1818
|
*
|
|
1820
|
-
* @
|
|
1819
|
+
* @param {string} selector
|
|
1820
|
+
* @returns {boolean}
|
|
1821
1821
|
*/
|
|
1822
|
-
|
|
1822
|
+
function basicSelectorValidator(selector) {
|
|
1823
|
+
var length = selector.length;
|
|
1824
|
+
var i = 0;
|
|
1825
|
+
var stack = [];
|
|
1826
|
+
var inAttr = false;
|
|
1827
|
+
var inSingleQuote = false;
|
|
1828
|
+
var inDoubleQuote = false;
|
|
1829
|
+
|
|
1830
|
+
while (i < length) {
|
|
1831
|
+
var char = selector[i];
|
|
1832
|
+
|
|
1833
|
+
if (inSingleQuote) {
|
|
1834
|
+
if (char === "'" && selector[i - 1] !== "\\") {
|
|
1835
|
+
inSingleQuote = false;
|
|
1836
|
+
}
|
|
1837
|
+
} else if (inDoubleQuote) {
|
|
1838
|
+
if (char === '"' && selector[i - 1] !== "\\") {
|
|
1839
|
+
inDoubleQuote = false;
|
|
1840
|
+
}
|
|
1841
|
+
} else if (inAttr) {
|
|
1842
|
+
if (char === "]") {
|
|
1843
|
+
inAttr = false;
|
|
1844
|
+
} else if (char === "'") {
|
|
1845
|
+
inSingleQuote = true;
|
|
1846
|
+
} else if (char === '"') {
|
|
1847
|
+
inDoubleQuote = true;
|
|
1848
|
+
}
|
|
1849
|
+
} else {
|
|
1850
|
+
if (char === "[") {
|
|
1851
|
+
inAttr = true;
|
|
1852
|
+
} else if (char === "'") {
|
|
1853
|
+
inSingleQuote = true;
|
|
1854
|
+
} else if (char === '"') {
|
|
1855
|
+
inDoubleQuote = true;
|
|
1856
|
+
} else if (char === "(") {
|
|
1857
|
+
stack.push("(");
|
|
1858
|
+
} else if (char === ")") {
|
|
1859
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
i++;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// If any stack or quote/attr context remains, it's invalid
|
|
1868
|
+
if (stack.length || inAttr || inSingleQuote || inDoubleQuote) {
|
|
1869
|
+
return false;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
1873
|
+
// This is similar to the original, but without nested paren limitations
|
|
1874
|
+
var looseSelectorRegExp = /^([a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|\*|#[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\.[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+(?:\((.*)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
1875
|
+
return looseSelectorRegExp.test(selector);
|
|
1876
|
+
}
|
|
1823
1877
|
|
|
1824
1878
|
/**
|
|
1825
1879
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -1902,40 +1956,62 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1902
1956
|
* Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
|
|
1903
1957
|
*
|
|
1904
1958
|
* This function checks if the provided selector is valid according to the rules defined by
|
|
1905
|
-
* `
|
|
1959
|
+
* `basicSelectorValidator`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
|
|
1906
1960
|
* it recursively validates each nested selector using the same validation logic.
|
|
1907
1961
|
*
|
|
1908
1962
|
* @param {string} selector - The CSS selector string to validate.
|
|
1909
1963
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
1910
1964
|
*/
|
|
1965
|
+
|
|
1966
|
+
// Cache to store validated selectors
|
|
1967
|
+
var validatedSelectorsCache = new Map();
|
|
1968
|
+
|
|
1969
|
+
// Only pseudo-classes that accept selector lists should recurse
|
|
1970
|
+
var selectorListPseudoClasses = {
|
|
1971
|
+
'not': true,
|
|
1972
|
+
'is': true,
|
|
1973
|
+
'has': true,
|
|
1974
|
+
'where': true
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1911
1977
|
function validateSelector(selector) {
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
var selectorListPseudoClasses = {
|
|
1916
|
-
'not': true,
|
|
1917
|
-
'is': true,
|
|
1918
|
-
'has': true,
|
|
1919
|
-
'where': true
|
|
1920
|
-
};
|
|
1978
|
+
if (validatedSelectorsCache.has(selector)) {
|
|
1979
|
+
return validatedSelectorsCache.get(selector);
|
|
1980
|
+
}
|
|
1921
1981
|
|
|
1922
|
-
//
|
|
1982
|
+
// Use a non-global regex to find all pseudo-classes with arguments
|
|
1983
|
+
var pseudoClassMatches = [];
|
|
1923
1984
|
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
1985
|
+
var match;
|
|
1924
1986
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
1925
|
-
|
|
1987
|
+
pseudoClassMatches.push(match);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
for (var j = 0; j < pseudoClassMatches.length; j++) {
|
|
1991
|
+
var pseudoClass = pseudoClassMatches[j][1];
|
|
1926
1992
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1927
|
-
nestedSelectors = parseAndSplitNestedSelectors(
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
if (!
|
|
1993
|
+
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
1994
|
+
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
1995
|
+
var nestedSelector = nestedSelectors[i];
|
|
1996
|
+
if (!validatedSelectorsCache.has(nestedSelector)) {
|
|
1997
|
+
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
1998
|
+
validatedSelectorsCache.set(nestedSelector, nestedSelectorValidation);
|
|
1999
|
+
if (!nestedSelectorValidation) {
|
|
2000
|
+
validatedSelectorsCache.set(selector, false);
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
} else if (!validatedSelectorsCache.get(nestedSelector)) {
|
|
2004
|
+
validatedSelectorsCache.set(selector, false);
|
|
1931
2005
|
return false;
|
|
1932
2006
|
}
|
|
1933
2007
|
}
|
|
1934
2008
|
}
|
|
1935
2009
|
}
|
|
1936
2010
|
|
|
1937
|
-
|
|
1938
|
-
|
|
2011
|
+
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
2012
|
+
validatedSelectorsCache.set(selector, basicSelectorValidation);
|
|
2013
|
+
|
|
2014
|
+
return basicSelectorValidation;
|
|
1939
2015
|
}
|
|
1940
2016
|
|
|
1941
2017
|
/**
|
package/lib/parse.js
CHANGED
|
@@ -7,7 +7,7 @@ var CSSOM = {};
|
|
|
7
7
|
* @param {string} token
|
|
8
8
|
*/
|
|
9
9
|
CSSOM.parse = function parse(token, errorHandler) {
|
|
10
|
-
errorHandler = errorHandler === undefined
|
|
10
|
+
errorHandler = errorHandler === undefined ? (console && console.error) : errorHandler;
|
|
11
11
|
|
|
12
12
|
var i = 0;
|
|
13
13
|
|
|
@@ -231,13 +231,14 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
234
|
+
* Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
|
|
235
|
+
* This function replaces the previous basicSelectorRegExp.
|
|
236
|
+
*
|
|
237
|
+
* This function matches:
|
|
237
238
|
* - Type selectors (e.g., `div`, `span`)
|
|
238
239
|
* - Universal selector (`*`)
|
|
239
|
-
* - ID selectors (e.g., `#header
|
|
240
|
-
* - Class selectors (e.g., `.container
|
|
240
|
+
* - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
|
|
241
|
+
* - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
|
|
241
242
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
242
243
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
243
244
|
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
@@ -246,13 +247,66 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
246
247
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
247
248
|
* - Whitespace (descendant combinator)
|
|
248
249
|
*
|
|
249
|
-
*
|
|
250
|
-
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
251
|
-
* but does not match full CSS selector groups separated by commas.
|
|
250
|
+
* Unicode and escape sequences are allowed in identifiers.
|
|
252
251
|
*
|
|
253
|
-
* @
|
|
252
|
+
* @param {string} selector
|
|
253
|
+
* @returns {boolean}
|
|
254
254
|
*/
|
|
255
|
-
|
|
255
|
+
function basicSelectorValidator(selector) {
|
|
256
|
+
var length = selector.length;
|
|
257
|
+
var i = 0;
|
|
258
|
+
var stack = [];
|
|
259
|
+
var inAttr = false;
|
|
260
|
+
var inSingleQuote = false;
|
|
261
|
+
var inDoubleQuote = false;
|
|
262
|
+
|
|
263
|
+
while (i < length) {
|
|
264
|
+
var char = selector[i];
|
|
265
|
+
|
|
266
|
+
if (inSingleQuote) {
|
|
267
|
+
if (char === "'" && selector[i - 1] !== "\\") {
|
|
268
|
+
inSingleQuote = false;
|
|
269
|
+
}
|
|
270
|
+
} else if (inDoubleQuote) {
|
|
271
|
+
if (char === '"' && selector[i - 1] !== "\\") {
|
|
272
|
+
inDoubleQuote = false;
|
|
273
|
+
}
|
|
274
|
+
} else if (inAttr) {
|
|
275
|
+
if (char === "]") {
|
|
276
|
+
inAttr = false;
|
|
277
|
+
} else if (char === "'") {
|
|
278
|
+
inSingleQuote = true;
|
|
279
|
+
} else if (char === '"') {
|
|
280
|
+
inDoubleQuote = true;
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
if (char === "[") {
|
|
284
|
+
inAttr = true;
|
|
285
|
+
} else if (char === "'") {
|
|
286
|
+
inSingleQuote = true;
|
|
287
|
+
} else if (char === '"') {
|
|
288
|
+
inDoubleQuote = true;
|
|
289
|
+
} else if (char === "(") {
|
|
290
|
+
stack.push("(");
|
|
291
|
+
} else if (char === ")") {
|
|
292
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
i++;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// If any stack or quote/attr context remains, it's invalid
|
|
301
|
+
if (stack.length || inAttr || inSingleQuote || inDoubleQuote) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
306
|
+
// This is similar to the original, but without nested paren limitations
|
|
307
|
+
var looseSelectorRegExp = /^([a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|\*|#[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\.[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+(?:\((.*)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
308
|
+
return looseSelectorRegExp.test(selector);
|
|
309
|
+
}
|
|
256
310
|
|
|
257
311
|
/**
|
|
258
312
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -335,40 +389,62 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
335
389
|
* Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
|
|
336
390
|
*
|
|
337
391
|
* This function checks if the provided selector is valid according to the rules defined by
|
|
338
|
-
* `
|
|
392
|
+
* `basicSelectorValidator`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
|
|
339
393
|
* it recursively validates each nested selector using the same validation logic.
|
|
340
394
|
*
|
|
341
395
|
* @param {string} selector - The CSS selector string to validate.
|
|
342
396
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
343
397
|
*/
|
|
344
|
-
function validateSelector(selector) {
|
|
345
|
-
var match, nestedSelectors, i;
|
|
346
398
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
399
|
+
// Cache to store validated selectors
|
|
400
|
+
var validatedSelectorsCache = new Map();
|
|
401
|
+
|
|
402
|
+
// Only pseudo-classes that accept selector lists should recurse
|
|
403
|
+
var selectorListPseudoClasses = {
|
|
404
|
+
'not': true,
|
|
405
|
+
'is': true,
|
|
406
|
+
'has': true,
|
|
407
|
+
'where': true
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
function validateSelector(selector) {
|
|
411
|
+
if (validatedSelectorsCache.has(selector)) {
|
|
412
|
+
return validatedSelectorsCache.get(selector);
|
|
413
|
+
}
|
|
354
414
|
|
|
355
|
-
//
|
|
415
|
+
// Use a non-global regex to find all pseudo-classes with arguments
|
|
416
|
+
var pseudoClassMatches = [];
|
|
356
417
|
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
418
|
+
var match;
|
|
357
419
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
358
|
-
|
|
420
|
+
pseudoClassMatches.push(match);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (var j = 0; j < pseudoClassMatches.length; j++) {
|
|
424
|
+
var pseudoClass = pseudoClassMatches[j][1];
|
|
359
425
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
360
|
-
nestedSelectors = parseAndSplitNestedSelectors(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (!
|
|
426
|
+
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
427
|
+
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
428
|
+
var nestedSelector = nestedSelectors[i];
|
|
429
|
+
if (!validatedSelectorsCache.has(nestedSelector)) {
|
|
430
|
+
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
431
|
+
validatedSelectorsCache.set(nestedSelector, nestedSelectorValidation);
|
|
432
|
+
if (!nestedSelectorValidation) {
|
|
433
|
+
validatedSelectorsCache.set(selector, false);
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
} else if (!validatedSelectorsCache.get(nestedSelector)) {
|
|
437
|
+
validatedSelectorsCache.set(selector, false);
|
|
364
438
|
return false;
|
|
365
439
|
}
|
|
366
440
|
}
|
|
367
441
|
}
|
|
368
442
|
}
|
|
369
443
|
|
|
370
|
-
|
|
371
|
-
|
|
444
|
+
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
445
|
+
validatedSelectorsCache.set(selector, basicSelectorValidation);
|
|
446
|
+
|
|
447
|
+
return basicSelectorValidation;
|
|
372
448
|
}
|
|
373
449
|
|
|
374
450
|
/**
|