@acemir/cssom 0.9.2 → 0.9.4
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 +40 -25
- package/lib/parse.js +40 -25
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -1807,16 +1807,19 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1807
1807
|
* - Class selectors (e.g., `.container`)
|
|
1808
1808
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
1809
1809
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
1810
|
+
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
1811
|
+
* such as `:has(.sel:nth-child(3n))`
|
|
1810
1812
|
* - The parent selector (`&`)
|
|
1811
1813
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
1812
1814
|
* - Whitespace (descendant combinator)
|
|
1813
1815
|
*
|
|
1814
1816
|
* The pattern ensures that a string consists only of valid basic selector components,
|
|
1815
|
-
* possibly repeated and combined,
|
|
1817
|
+
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
1818
|
+
* but does not match full CSS selector groups separated by commas.
|
|
1816
1819
|
*
|
|
1817
1820
|
* @type {RegExp}
|
|
1818
1821
|
*/
|
|
1819
|
-
var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]
|
|
1822
|
+
var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_-]+(?:\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
1820
1823
|
|
|
1821
1824
|
/**
|
|
1822
1825
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -1825,16 +1828,17 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1825
1828
|
*
|
|
1826
1829
|
* Capture groups:
|
|
1827
1830
|
* 1. The pseudo-class name (letters and hyphens).
|
|
1828
|
-
* 2. The argument inside the parentheses (
|
|
1831
|
+
* 2. The argument inside the parentheses (can contain nested parentheses, quoted strings, and other characters.).
|
|
1829
1832
|
*
|
|
1830
1833
|
* Global flag (`g`) is used to find all matches in the input string.
|
|
1831
1834
|
*
|
|
1832
|
-
* Example
|
|
1833
|
-
* -
|
|
1834
|
-
* -
|
|
1835
|
+
* Example matches:
|
|
1836
|
+
* - :nth-child(2n+1)
|
|
1837
|
+
* - :has(.sel:nth-child(3n))
|
|
1838
|
+
* - :not(".foo, .bar")
|
|
1835
1839
|
* @type {RegExp}
|
|
1836
1840
|
*/
|
|
1837
|
-
var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(([^)]*)\)/g;
|
|
1841
|
+
var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\)/g;
|
|
1838
1842
|
|
|
1839
1843
|
/**
|
|
1840
1844
|
* Parses a CSS selector string and splits it into parts, handling nested parentheses.
|
|
@@ -1846,30 +1850,42 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1846
1850
|
* @param {string} selector - The CSS selector string to parse.
|
|
1847
1851
|
* @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
|
|
1848
1852
|
*/
|
|
1849
|
-
function
|
|
1853
|
+
function parseAndSplitNestedSelectors(selector) {
|
|
1850
1854
|
var depth = 0;
|
|
1851
1855
|
var buffer = "";
|
|
1852
1856
|
var parts = [];
|
|
1857
|
+
var inSingleQuote = false;
|
|
1858
|
+
var inDoubleQuote = false;
|
|
1853
1859
|
var i, char;
|
|
1854
1860
|
|
|
1855
1861
|
for (i = 0; i < selector.length; i++) {
|
|
1856
1862
|
char = selector.charAt(i);
|
|
1857
1863
|
|
|
1858
|
-
if (char === '
|
|
1859
|
-
|
|
1864
|
+
if (char === "'" && !inDoubleQuote) {
|
|
1865
|
+
inSingleQuote = !inSingleQuote;
|
|
1860
1866
|
buffer += char;
|
|
1861
|
-
} else if (char === '
|
|
1862
|
-
|
|
1867
|
+
} else if (char === '"' && !inSingleQuote) {
|
|
1868
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1863
1869
|
buffer += char;
|
|
1864
|
-
|
|
1865
|
-
|
|
1870
|
+
} else if (!inSingleQuote && !inDoubleQuote) {
|
|
1871
|
+
if (char === '(') {
|
|
1872
|
+
depth++;
|
|
1873
|
+
buffer += char;
|
|
1874
|
+
} else if (char === ')') {
|
|
1875
|
+
depth--;
|
|
1876
|
+
buffer += char;
|
|
1877
|
+
if (depth === 0) {
|
|
1878
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1879
|
+
buffer = "";
|
|
1880
|
+
}
|
|
1881
|
+
} else if (char === ',' && depth === 0) {
|
|
1882
|
+
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
1883
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1884
|
+
}
|
|
1866
1885
|
buffer = "";
|
|
1886
|
+
} else {
|
|
1887
|
+
buffer += char;
|
|
1867
1888
|
}
|
|
1868
|
-
} else if (char === ',' && depth === 0) {
|
|
1869
|
-
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
1870
|
-
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1871
|
-
}
|
|
1872
|
-
buffer = "";
|
|
1873
1889
|
} else {
|
|
1874
1890
|
buffer += char;
|
|
1875
1891
|
}
|
|
@@ -1908,7 +1924,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1908
1924
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
1909
1925
|
var pseudoClass = match[1];
|
|
1910
1926
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1911
|
-
nestedSelectors =
|
|
1927
|
+
nestedSelectors = parseAndSplitNestedSelectors(match[2]);
|
|
1912
1928
|
// Validate each nested selector
|
|
1913
1929
|
for (i = 0; i < nestedSelectors.length; i++) {
|
|
1914
1930
|
if (!validateSelector(nestedSelectors[i])) {
|
|
@@ -1931,9 +1947,10 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1931
1947
|
*/
|
|
1932
1948
|
function isValidSelectorText(selectorText) {
|
|
1933
1949
|
// Split selectorText by commas and validate each part
|
|
1934
|
-
var selectors = selectorText
|
|
1950
|
+
var selectors = parseAndSplitNestedSelectors(selectorText);
|
|
1935
1951
|
for (var i = 0; i < selectors.length; i++) {
|
|
1936
|
-
|
|
1952
|
+
var processedSelectors = selectors[i].replace(/^\s+|\s+$/g, "");
|
|
1953
|
+
if (!validateSelector(processedSelectors)) {
|
|
1937
1954
|
return false;
|
|
1938
1955
|
}
|
|
1939
1956
|
}
|
|
@@ -2159,7 +2176,6 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2159
2176
|
}
|
|
2160
2177
|
|
|
2161
2178
|
currentScope = parentRule = styleRule;
|
|
2162
|
-
console.log('sel out', buffer);
|
|
2163
2179
|
styleRule.selectorText = buffer.trim();
|
|
2164
2180
|
styleRule.style.__starts = i;
|
|
2165
2181
|
styleRule.parentStyleSheet = styleSheet;
|
|
@@ -2289,9 +2305,8 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2289
2305
|
}
|
|
2290
2306
|
|
|
2291
2307
|
styleRule = new CSSOM.CSSStyleRule();
|
|
2292
|
-
console.log('sel in', buffer);
|
|
2293
2308
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
2294
|
-
styleRule.selectorText =
|
|
2309
|
+
styleRule.selectorText = parseAndSplitNestedSelectors(buffer.trim()).map(function(sel) {
|
|
2295
2310
|
return sel.indexOf('&') === -1 ? '& ' + sel : sel;
|
|
2296
2311
|
}).join(', ');
|
|
2297
2312
|
styleRule.style.__starts = i - buffer.length;
|
package/lib/parse.js
CHANGED
|
@@ -240,16 +240,19 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
240
240
|
* - Class selectors (e.g., `.container`)
|
|
241
241
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
242
242
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
243
|
+
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
244
|
+
* such as `:has(.sel:nth-child(3n))`
|
|
243
245
|
* - The parent selector (`&`)
|
|
244
246
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
245
247
|
* - Whitespace (descendant combinator)
|
|
246
248
|
*
|
|
247
249
|
* The pattern ensures that a string consists only of valid basic selector components,
|
|
248
|
-
* possibly repeated and combined,
|
|
250
|
+
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
251
|
+
* but does not match full CSS selector groups separated by commas.
|
|
249
252
|
*
|
|
250
253
|
* @type {RegExp}
|
|
251
254
|
*/
|
|
252
|
-
var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]
|
|
255
|
+
var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_-]+(?:\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
253
256
|
|
|
254
257
|
/**
|
|
255
258
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -258,16 +261,17 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
258
261
|
*
|
|
259
262
|
* Capture groups:
|
|
260
263
|
* 1. The pseudo-class name (letters and hyphens).
|
|
261
|
-
* 2. The argument inside the parentheses (
|
|
264
|
+
* 2. The argument inside the parentheses (can contain nested parentheses, quoted strings, and other characters.).
|
|
262
265
|
*
|
|
263
266
|
* Global flag (`g`) is used to find all matches in the input string.
|
|
264
267
|
*
|
|
265
|
-
* Example
|
|
266
|
-
* -
|
|
267
|
-
* -
|
|
268
|
+
* Example matches:
|
|
269
|
+
* - :nth-child(2n+1)
|
|
270
|
+
* - :has(.sel:nth-child(3n))
|
|
271
|
+
* - :not(".foo, .bar")
|
|
268
272
|
* @type {RegExp}
|
|
269
273
|
*/
|
|
270
|
-
var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(([^)]*)\)/g;
|
|
274
|
+
var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\)/g;
|
|
271
275
|
|
|
272
276
|
/**
|
|
273
277
|
* Parses a CSS selector string and splits it into parts, handling nested parentheses.
|
|
@@ -279,30 +283,42 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
279
283
|
* @param {string} selector - The CSS selector string to parse.
|
|
280
284
|
* @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
|
|
281
285
|
*/
|
|
282
|
-
function
|
|
286
|
+
function parseAndSplitNestedSelectors(selector) {
|
|
283
287
|
var depth = 0;
|
|
284
288
|
var buffer = "";
|
|
285
289
|
var parts = [];
|
|
290
|
+
var inSingleQuote = false;
|
|
291
|
+
var inDoubleQuote = false;
|
|
286
292
|
var i, char;
|
|
287
293
|
|
|
288
294
|
for (i = 0; i < selector.length; i++) {
|
|
289
295
|
char = selector.charAt(i);
|
|
290
296
|
|
|
291
|
-
if (char === '
|
|
292
|
-
|
|
297
|
+
if (char === "'" && !inDoubleQuote) {
|
|
298
|
+
inSingleQuote = !inSingleQuote;
|
|
293
299
|
buffer += char;
|
|
294
|
-
} else if (char === '
|
|
295
|
-
|
|
300
|
+
} else if (char === '"' && !inSingleQuote) {
|
|
301
|
+
inDoubleQuote = !inDoubleQuote;
|
|
296
302
|
buffer += char;
|
|
297
|
-
|
|
298
|
-
|
|
303
|
+
} else if (!inSingleQuote && !inDoubleQuote) {
|
|
304
|
+
if (char === '(') {
|
|
305
|
+
depth++;
|
|
306
|
+
buffer += char;
|
|
307
|
+
} else if (char === ')') {
|
|
308
|
+
depth--;
|
|
309
|
+
buffer += char;
|
|
310
|
+
if (depth === 0) {
|
|
311
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
312
|
+
buffer = "";
|
|
313
|
+
}
|
|
314
|
+
} else if (char === ',' && depth === 0) {
|
|
315
|
+
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
316
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
317
|
+
}
|
|
299
318
|
buffer = "";
|
|
319
|
+
} else {
|
|
320
|
+
buffer += char;
|
|
300
321
|
}
|
|
301
|
-
} else if (char === ',' && depth === 0) {
|
|
302
|
-
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
303
|
-
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
304
|
-
}
|
|
305
|
-
buffer = "";
|
|
306
322
|
} else {
|
|
307
323
|
buffer += char;
|
|
308
324
|
}
|
|
@@ -341,7 +357,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
341
357
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
342
358
|
var pseudoClass = match[1];
|
|
343
359
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
344
|
-
nestedSelectors =
|
|
360
|
+
nestedSelectors = parseAndSplitNestedSelectors(match[2]);
|
|
345
361
|
// Validate each nested selector
|
|
346
362
|
for (i = 0; i < nestedSelectors.length; i++) {
|
|
347
363
|
if (!validateSelector(nestedSelectors[i])) {
|
|
@@ -364,9 +380,10 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
364
380
|
*/
|
|
365
381
|
function isValidSelectorText(selectorText) {
|
|
366
382
|
// Split selectorText by commas and validate each part
|
|
367
|
-
var selectors = selectorText
|
|
383
|
+
var selectors = parseAndSplitNestedSelectors(selectorText);
|
|
368
384
|
for (var i = 0; i < selectors.length; i++) {
|
|
369
|
-
|
|
385
|
+
var processedSelectors = selectors[i].replace(/^\s+|\s+$/g, "");
|
|
386
|
+
if (!validateSelector(processedSelectors)) {
|
|
370
387
|
return false;
|
|
371
388
|
}
|
|
372
389
|
}
|
|
@@ -592,7 +609,6 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
592
609
|
}
|
|
593
610
|
|
|
594
611
|
currentScope = parentRule = styleRule;
|
|
595
|
-
console.log('sel out', buffer);
|
|
596
612
|
styleRule.selectorText = buffer.trim();
|
|
597
613
|
styleRule.style.__starts = i;
|
|
598
614
|
styleRule.parentStyleSheet = styleSheet;
|
|
@@ -722,9 +738,8 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
722
738
|
}
|
|
723
739
|
|
|
724
740
|
styleRule = new CSSOM.CSSStyleRule();
|
|
725
|
-
console.log('sel in', buffer);
|
|
726
741
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
727
|
-
styleRule.selectorText =
|
|
742
|
+
styleRule.selectorText = parseAndSplitNestedSelectors(buffer.trim()).map(function(sel) {
|
|
728
743
|
return sel.indexOf('&') === -1 ? '& ' + sel : sel;
|
|
729
744
|
}).join(', ');
|
|
730
745
|
styleRule.style.__starts = i - buffer.length;
|