@acemir/cssom 0.9.5 → 0.9.7
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 +143 -30
- package/lib/CSSCounterStyleRule.js +23 -0
- package/lib/index.js +1 -0
- package/lib/parse.js +130 -30
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -298,6 +298,20 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
|
|
|
298
298
|
};
|
|
299
299
|
|
|
300
300
|
|
|
301
|
+
/**
|
|
302
|
+
* @constructor
|
|
303
|
+
* @see https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface
|
|
304
|
+
*/
|
|
305
|
+
CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() {
|
|
306
|
+
CSSOM.CSSRule.call(this);
|
|
307
|
+
this.name = "";
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
CSSOM.CSSCounterStyleRule.prototype = new CSSOM.CSSRule();
|
|
311
|
+
CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule;
|
|
312
|
+
CSSOM.CSSCounterStyleRule.prototype.type = 11;
|
|
313
|
+
|
|
314
|
+
|
|
301
315
|
/**
|
|
302
316
|
* @constructor
|
|
303
317
|
* @see https://www.w3.org/TR/css-conditional-3/#the-cssconditionrule-interface
|
|
@@ -1607,6 +1621,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1607
1621
|
"atBlock": true,
|
|
1608
1622
|
"containerBlock": true,
|
|
1609
1623
|
"conditionBlock": true,
|
|
1624
|
+
"counterStyleBlock": true,
|
|
1610
1625
|
'documentRule-begin': true,
|
|
1611
1626
|
"layerBlock": true
|
|
1612
1627
|
};
|
|
@@ -1622,7 +1637,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1622
1637
|
var ancestorRules = [];
|
|
1623
1638
|
var prevScope;
|
|
1624
1639
|
|
|
1625
|
-
var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
|
|
1640
|
+
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
|
|
1626
1641
|
|
|
1627
1642
|
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
|
|
1628
1643
|
// Regex above is not ES5 compliant
|
|
@@ -1798,13 +1813,14 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1798
1813
|
}
|
|
1799
1814
|
|
|
1800
1815
|
/**
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1816
|
+
* Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
|
|
1817
|
+
* This function replaces the previous basicSelectorRegExp.
|
|
1818
|
+
*
|
|
1819
|
+
* This function matches:
|
|
1804
1820
|
* - Type selectors (e.g., `div`, `span`)
|
|
1805
1821
|
* - Universal selector (`*`)
|
|
1806
|
-
* - ID selectors (e.g., `#header
|
|
1807
|
-
* - Class selectors (e.g., `.container
|
|
1822
|
+
* - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
|
|
1823
|
+
* - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
|
|
1808
1824
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
1809
1825
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
1810
1826
|
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
@@ -1813,13 +1829,66 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1813
1829
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
1814
1830
|
* - Whitespace (descendant combinator)
|
|
1815
1831
|
*
|
|
1816
|
-
*
|
|
1817
|
-
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
1818
|
-
* but does not match full CSS selector groups separated by commas.
|
|
1832
|
+
* Unicode and escape sequences are allowed in identifiers.
|
|
1819
1833
|
*
|
|
1820
|
-
* @
|
|
1834
|
+
* @param {string} selector
|
|
1835
|
+
* @returns {boolean}
|
|
1821
1836
|
*/
|
|
1822
|
-
|
|
1837
|
+
function basicSelectorValidator(selector) {
|
|
1838
|
+
var length = selector.length;
|
|
1839
|
+
var i = 0;
|
|
1840
|
+
var stack = [];
|
|
1841
|
+
var inAttr = false;
|
|
1842
|
+
var inSingleQuote = false;
|
|
1843
|
+
var inDoubleQuote = false;
|
|
1844
|
+
|
|
1845
|
+
while (i < length) {
|
|
1846
|
+
var char = selector[i];
|
|
1847
|
+
|
|
1848
|
+
if (inSingleQuote) {
|
|
1849
|
+
if (char === "'" && selector[i - 1] !== "\\") {
|
|
1850
|
+
inSingleQuote = false;
|
|
1851
|
+
}
|
|
1852
|
+
} else if (inDoubleQuote) {
|
|
1853
|
+
if (char === '"' && selector[i - 1] !== "\\") {
|
|
1854
|
+
inDoubleQuote = false;
|
|
1855
|
+
}
|
|
1856
|
+
} else if (inAttr) {
|
|
1857
|
+
if (char === "]") {
|
|
1858
|
+
inAttr = false;
|
|
1859
|
+
} else if (char === "'") {
|
|
1860
|
+
inSingleQuote = true;
|
|
1861
|
+
} else if (char === '"') {
|
|
1862
|
+
inDoubleQuote = true;
|
|
1863
|
+
}
|
|
1864
|
+
} else {
|
|
1865
|
+
if (char === "[") {
|
|
1866
|
+
inAttr = true;
|
|
1867
|
+
} else if (char === "'") {
|
|
1868
|
+
inSingleQuote = true;
|
|
1869
|
+
} else if (char === '"') {
|
|
1870
|
+
inDoubleQuote = true;
|
|
1871
|
+
} else if (char === "(") {
|
|
1872
|
+
stack.push("(");
|
|
1873
|
+
} else if (char === ")") {
|
|
1874
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
1875
|
+
return false;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
i++;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// If any stack or quote/attr context remains, it's invalid
|
|
1883
|
+
if (stack.length || inAttr || inSingleQuote || inDoubleQuote) {
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
1888
|
+
// This is similar to the original, but without nested paren limitations
|
|
1889
|
+
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+)+$/;
|
|
1890
|
+
return looseSelectorRegExp.test(selector);
|
|
1891
|
+
}
|
|
1823
1892
|
|
|
1824
1893
|
/**
|
|
1825
1894
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -1902,40 +1971,62 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
1902
1971
|
* Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
|
|
1903
1972
|
*
|
|
1904
1973
|
* This function checks if the provided selector is valid according to the rules defined by
|
|
1905
|
-
* `
|
|
1974
|
+
* `basicSelectorValidator`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
|
|
1906
1975
|
* it recursively validates each nested selector using the same validation logic.
|
|
1907
1976
|
*
|
|
1908
1977
|
* @param {string} selector - The CSS selector string to validate.
|
|
1909
1978
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
1910
1979
|
*/
|
|
1980
|
+
|
|
1981
|
+
// Cache to store validated selectors
|
|
1982
|
+
var validatedSelectorsCache = new Map();
|
|
1983
|
+
|
|
1984
|
+
// Only pseudo-classes that accept selector lists should recurse
|
|
1985
|
+
var selectorListPseudoClasses = {
|
|
1986
|
+
'not': true,
|
|
1987
|
+
'is': true,
|
|
1988
|
+
'has': true,
|
|
1989
|
+
'where': true
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1911
1992
|
function validateSelector(selector) {
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
var selectorListPseudoClasses = {
|
|
1916
|
-
'not': true,
|
|
1917
|
-
'is': true,
|
|
1918
|
-
'has': true,
|
|
1919
|
-
'where': true
|
|
1920
|
-
};
|
|
1993
|
+
if (validatedSelectorsCache.has(selector)) {
|
|
1994
|
+
return validatedSelectorsCache.get(selector);
|
|
1995
|
+
}
|
|
1921
1996
|
|
|
1922
|
-
//
|
|
1997
|
+
// Use a non-global regex to find all pseudo-classes with arguments
|
|
1998
|
+
var pseudoClassMatches = [];
|
|
1923
1999
|
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
2000
|
+
var match;
|
|
1924
2001
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
1925
|
-
|
|
2002
|
+
pseudoClassMatches.push(match);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
for (var j = 0; j < pseudoClassMatches.length; j++) {
|
|
2006
|
+
var pseudoClass = pseudoClassMatches[j][1];
|
|
1926
2007
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1927
|
-
nestedSelectors = parseAndSplitNestedSelectors(
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
if (!
|
|
2008
|
+
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
2009
|
+
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
2010
|
+
var nestedSelector = nestedSelectors[i];
|
|
2011
|
+
if (!validatedSelectorsCache.has(nestedSelector)) {
|
|
2012
|
+
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
2013
|
+
validatedSelectorsCache.set(nestedSelector, nestedSelectorValidation);
|
|
2014
|
+
if (!nestedSelectorValidation) {
|
|
2015
|
+
validatedSelectorsCache.set(selector, false);
|
|
2016
|
+
return false;
|
|
2017
|
+
}
|
|
2018
|
+
} else if (!validatedSelectorsCache.get(nestedSelector)) {
|
|
2019
|
+
validatedSelectorsCache.set(selector, false);
|
|
1931
2020
|
return false;
|
|
1932
2021
|
}
|
|
1933
2022
|
}
|
|
1934
2023
|
}
|
|
1935
2024
|
}
|
|
1936
2025
|
|
|
1937
|
-
|
|
1938
|
-
|
|
2026
|
+
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
2027
|
+
validatedSelectorsCache.set(selector, basicSelectorValidation);
|
|
2028
|
+
|
|
2029
|
+
return basicSelectorValidation;
|
|
1939
2030
|
}
|
|
1940
2031
|
|
|
1941
2032
|
/**
|
|
@@ -2078,6 +2169,15 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2078
2169
|
});
|
|
2079
2170
|
buffer = "";
|
|
2080
2171
|
break;
|
|
2172
|
+
} else if (token.indexOf("@counter-style", i) === i) {
|
|
2173
|
+
validateAtRule("@counter-style", function(){
|
|
2174
|
+
state = "counterStyleBlock"
|
|
2175
|
+
counterStyleRule = new CSSOM.CSSCounterStyleRule();
|
|
2176
|
+
counterStyleRule.__starts = i;
|
|
2177
|
+
i += "counter-style".length;
|
|
2178
|
+
}, true);
|
|
2179
|
+
buffer = "";
|
|
2180
|
+
break;
|
|
2081
2181
|
} else if (token.indexOf("@layer", i) === i) {
|
|
2082
2182
|
validateAtRule("@layer", function(){
|
|
2083
2183
|
state = "layerBlock"
|
|
@@ -2087,7 +2187,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2087
2187
|
});
|
|
2088
2188
|
buffer = "";
|
|
2089
2189
|
break;
|
|
2090
|
-
}
|
|
2190
|
+
} else if (token.indexOf("@supports", i) === i) {
|
|
2091
2191
|
validateAtRule("@supports", function(){
|
|
2092
2192
|
state = "conditionBlock";
|
|
2093
2193
|
supportsRule = new CSSOM.CSSSupportsRule();
|
|
@@ -2204,6 +2304,12 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2204
2304
|
containerRule.parentStyleSheet = styleSheet;
|
|
2205
2305
|
buffer = "";
|
|
2206
2306
|
state = "before-selector";
|
|
2307
|
+
} else if (state === "counterStyleBlock") {
|
|
2308
|
+
// TODO: Validate counter-style name. At least that it cannot be empty nor multiple
|
|
2309
|
+
counterStyleRule.name = buffer.trim();
|
|
2310
|
+
currentScope = parentRule = counterStyleRule;
|
|
2311
|
+
counterStyleRule.parentStyleSheet = styleSheet;
|
|
2312
|
+
buffer = "";
|
|
2207
2313
|
} else if (state === "conditionBlock") {
|
|
2208
2314
|
supportsRule.conditionText = buffer.trim();
|
|
2209
2315
|
|
|
@@ -2438,6 +2544,13 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
2438
2544
|
break;
|
|
2439
2545
|
|
|
2440
2546
|
case "}":
|
|
2547
|
+
if (state === "counterStyleBlock") {
|
|
2548
|
+
// FIXME : Implement cssText get setter that parses the real implementation
|
|
2549
|
+
counterStyleRule.cssText = "@counter-style " + counterStyleRule.name + " { " + buffer.trim() + " }";
|
|
2550
|
+
buffer = "";
|
|
2551
|
+
state = "before-selector";
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2441
2554
|
switch (state) {
|
|
2442
2555
|
case "value":
|
|
2443
2556
|
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//.CommonJS
|
|
2
|
+
var CSSOM = {
|
|
3
|
+
CSSRule: require("./CSSRule").CSSRule
|
|
4
|
+
};
|
|
5
|
+
///CommonJS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @constructor
|
|
10
|
+
* @see https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface
|
|
11
|
+
*/
|
|
12
|
+
CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() {
|
|
13
|
+
CSSOM.CSSRule.call(this);
|
|
14
|
+
this.name = "";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
CSSOM.CSSCounterStyleRule.prototype = new CSSOM.CSSRule();
|
|
18
|
+
CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule;
|
|
19
|
+
CSSOM.CSSCounterStyleRule.prototype.type = 11;
|
|
20
|
+
|
|
21
|
+
//.CommonJS
|
|
22
|
+
exports.CSSCounterStyleRule = CSSOM.CSSCounterStyleRule;
|
|
23
|
+
///CommonJS
|
package/lib/index.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.CSSStyleDeclaration = require('./CSSStyleDeclaration').CSSStyleDeclarati
|
|
|
4
4
|
exports.CSSRule = require('./CSSRule').CSSRule;
|
|
5
5
|
exports.CSSNestedDeclarations = require('./CSSNestedDeclarations').CSSNestedDeclarations;
|
|
6
6
|
exports.CSSGroupingRule = require('./CSSGroupingRule').CSSGroupingRule;
|
|
7
|
+
exports.CSSCounterStyleRule = require('./CSSCounterStyleRule').CSSCounterStyleRule;
|
|
7
8
|
exports.CSSConditionRule = require('./CSSConditionRule').CSSConditionRule;
|
|
8
9
|
exports.CSSStyleRule = require('./CSSStyleRule').CSSStyleRule;
|
|
9
10
|
exports.MediaList = require('./MediaList').MediaList;
|
package/lib/parse.js
CHANGED
|
@@ -40,6 +40,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
40
40
|
"atBlock": true,
|
|
41
41
|
"containerBlock": true,
|
|
42
42
|
"conditionBlock": true,
|
|
43
|
+
"counterStyleBlock": true,
|
|
43
44
|
'documentRule-begin': true,
|
|
44
45
|
"layerBlock": true
|
|
45
46
|
};
|
|
@@ -55,7 +56,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
55
56
|
var ancestorRules = [];
|
|
56
57
|
var prevScope;
|
|
57
58
|
|
|
58
|
-
var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
|
|
59
|
+
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
|
|
59
60
|
|
|
60
61
|
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
|
|
61
62
|
// Regex above is not ES5 compliant
|
|
@@ -231,13 +232,14 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
235
|
+
* Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
|
|
236
|
+
* This function replaces the previous basicSelectorRegExp.
|
|
237
|
+
*
|
|
238
|
+
* This function matches:
|
|
237
239
|
* - Type selectors (e.g., `div`, `span`)
|
|
238
240
|
* - Universal selector (`*`)
|
|
239
|
-
* - ID selectors (e.g., `#header
|
|
240
|
-
* - Class selectors (e.g., `.container
|
|
241
|
+
* - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
|
|
242
|
+
* - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
|
|
241
243
|
* - Attribute selectors (e.g., `[type="text"]`)
|
|
242
244
|
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
243
245
|
* - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
|
|
@@ -246,13 +248,66 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
246
248
|
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
247
249
|
* - Whitespace (descendant combinator)
|
|
248
250
|
*
|
|
249
|
-
*
|
|
250
|
-
* possibly repeated and combined, including pseudo-classes with nested parentheses,
|
|
251
|
-
* but does not match full CSS selector groups separated by commas.
|
|
251
|
+
* Unicode and escape sequences are allowed in identifiers.
|
|
252
252
|
*
|
|
253
|
-
* @
|
|
253
|
+
* @param {string} selector
|
|
254
|
+
* @returns {boolean}
|
|
254
255
|
*/
|
|
255
|
-
|
|
256
|
+
function basicSelectorValidator(selector) {
|
|
257
|
+
var length = selector.length;
|
|
258
|
+
var i = 0;
|
|
259
|
+
var stack = [];
|
|
260
|
+
var inAttr = false;
|
|
261
|
+
var inSingleQuote = false;
|
|
262
|
+
var inDoubleQuote = false;
|
|
263
|
+
|
|
264
|
+
while (i < length) {
|
|
265
|
+
var char = selector[i];
|
|
266
|
+
|
|
267
|
+
if (inSingleQuote) {
|
|
268
|
+
if (char === "'" && selector[i - 1] !== "\\") {
|
|
269
|
+
inSingleQuote = false;
|
|
270
|
+
}
|
|
271
|
+
} else if (inDoubleQuote) {
|
|
272
|
+
if (char === '"' && selector[i - 1] !== "\\") {
|
|
273
|
+
inDoubleQuote = false;
|
|
274
|
+
}
|
|
275
|
+
} else if (inAttr) {
|
|
276
|
+
if (char === "]") {
|
|
277
|
+
inAttr = false;
|
|
278
|
+
} else if (char === "'") {
|
|
279
|
+
inSingleQuote = true;
|
|
280
|
+
} else if (char === '"') {
|
|
281
|
+
inDoubleQuote = true;
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
if (char === "[") {
|
|
285
|
+
inAttr = true;
|
|
286
|
+
} else if (char === "'") {
|
|
287
|
+
inSingleQuote = true;
|
|
288
|
+
} else if (char === '"') {
|
|
289
|
+
inDoubleQuote = true;
|
|
290
|
+
} else if (char === "(") {
|
|
291
|
+
stack.push("(");
|
|
292
|
+
} else if (char === ")") {
|
|
293
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
i++;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If any stack or quote/attr context remains, it's invalid
|
|
302
|
+
if (stack.length || inAttr || inSingleQuote || inDoubleQuote) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
307
|
+
// This is similar to the original, but without nested paren limitations
|
|
308
|
+
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+)+$/;
|
|
309
|
+
return looseSelectorRegExp.test(selector);
|
|
310
|
+
}
|
|
256
311
|
|
|
257
312
|
/**
|
|
258
313
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
@@ -335,40 +390,62 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
335
390
|
* Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
|
|
336
391
|
*
|
|
337
392
|
* This function checks if the provided selector is valid according to the rules defined by
|
|
338
|
-
* `
|
|
393
|
+
* `basicSelectorValidator`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
|
|
339
394
|
* it recursively validates each nested selector using the same validation logic.
|
|
340
395
|
*
|
|
341
396
|
* @param {string} selector - The CSS selector string to validate.
|
|
342
397
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
343
398
|
*/
|
|
344
|
-
function validateSelector(selector) {
|
|
345
|
-
var match, nestedSelectors, i;
|
|
346
399
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
400
|
+
// Cache to store validated selectors
|
|
401
|
+
var validatedSelectorsCache = new Map();
|
|
402
|
+
|
|
403
|
+
// Only pseudo-classes that accept selector lists should recurse
|
|
404
|
+
var selectorListPseudoClasses = {
|
|
405
|
+
'not': true,
|
|
406
|
+
'is': true,
|
|
407
|
+
'has': true,
|
|
408
|
+
'where': true
|
|
409
|
+
};
|
|
354
410
|
|
|
355
|
-
|
|
411
|
+
function validateSelector(selector) {
|
|
412
|
+
if (validatedSelectorsCache.has(selector)) {
|
|
413
|
+
return validatedSelectorsCache.get(selector);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Use a non-global regex to find all pseudo-classes with arguments
|
|
417
|
+
var pseudoClassMatches = [];
|
|
356
418
|
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
419
|
+
var match;
|
|
357
420
|
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
358
|
-
|
|
421
|
+
pseudoClassMatches.push(match);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
for (var j = 0; j < pseudoClassMatches.length; j++) {
|
|
425
|
+
var pseudoClass = pseudoClassMatches[j][1];
|
|
359
426
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
360
|
-
nestedSelectors = parseAndSplitNestedSelectors(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (!
|
|
427
|
+
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
428
|
+
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
429
|
+
var nestedSelector = nestedSelectors[i];
|
|
430
|
+
if (!validatedSelectorsCache.has(nestedSelector)) {
|
|
431
|
+
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
432
|
+
validatedSelectorsCache.set(nestedSelector, nestedSelectorValidation);
|
|
433
|
+
if (!nestedSelectorValidation) {
|
|
434
|
+
validatedSelectorsCache.set(selector, false);
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
} else if (!validatedSelectorsCache.get(nestedSelector)) {
|
|
438
|
+
validatedSelectorsCache.set(selector, false);
|
|
364
439
|
return false;
|
|
365
440
|
}
|
|
366
441
|
}
|
|
367
442
|
}
|
|
368
443
|
}
|
|
369
444
|
|
|
370
|
-
|
|
371
|
-
|
|
445
|
+
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
446
|
+
validatedSelectorsCache.set(selector, basicSelectorValidation);
|
|
447
|
+
|
|
448
|
+
return basicSelectorValidation;
|
|
372
449
|
}
|
|
373
450
|
|
|
374
451
|
/**
|
|
@@ -511,6 +588,15 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
511
588
|
});
|
|
512
589
|
buffer = "";
|
|
513
590
|
break;
|
|
591
|
+
} else if (token.indexOf("@counter-style", i) === i) {
|
|
592
|
+
validateAtRule("@counter-style", function(){
|
|
593
|
+
state = "counterStyleBlock"
|
|
594
|
+
counterStyleRule = new CSSOM.CSSCounterStyleRule();
|
|
595
|
+
counterStyleRule.__starts = i;
|
|
596
|
+
i += "counter-style".length;
|
|
597
|
+
}, true);
|
|
598
|
+
buffer = "";
|
|
599
|
+
break;
|
|
514
600
|
} else if (token.indexOf("@layer", i) === i) {
|
|
515
601
|
validateAtRule("@layer", function(){
|
|
516
602
|
state = "layerBlock"
|
|
@@ -520,7 +606,7 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
520
606
|
});
|
|
521
607
|
buffer = "";
|
|
522
608
|
break;
|
|
523
|
-
}
|
|
609
|
+
} else if (token.indexOf("@supports", i) === i) {
|
|
524
610
|
validateAtRule("@supports", function(){
|
|
525
611
|
state = "conditionBlock";
|
|
526
612
|
supportsRule = new CSSOM.CSSSupportsRule();
|
|
@@ -637,6 +723,12 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
637
723
|
containerRule.parentStyleSheet = styleSheet;
|
|
638
724
|
buffer = "";
|
|
639
725
|
state = "before-selector";
|
|
726
|
+
} else if (state === "counterStyleBlock") {
|
|
727
|
+
// TODO: Validate counter-style name. At least that it cannot be empty nor multiple
|
|
728
|
+
counterStyleRule.name = buffer.trim();
|
|
729
|
+
currentScope = parentRule = counterStyleRule;
|
|
730
|
+
counterStyleRule.parentStyleSheet = styleSheet;
|
|
731
|
+
buffer = "";
|
|
640
732
|
} else if (state === "conditionBlock") {
|
|
641
733
|
supportsRule.conditionText = buffer.trim();
|
|
642
734
|
|
|
@@ -871,6 +963,13 @@ CSSOM.parse = function parse(token, errorHandler) {
|
|
|
871
963
|
break;
|
|
872
964
|
|
|
873
965
|
case "}":
|
|
966
|
+
if (state === "counterStyleBlock") {
|
|
967
|
+
// FIXME : Implement cssText get setter that parses the real implementation
|
|
968
|
+
counterStyleRule.cssText = "@counter-style " + counterStyleRule.name + " { " + buffer.trim() + " }";
|
|
969
|
+
buffer = "";
|
|
970
|
+
state = "before-selector";
|
|
971
|
+
}
|
|
972
|
+
|
|
874
973
|
switch (state) {
|
|
875
974
|
case "value":
|
|
876
975
|
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
@@ -1076,6 +1175,7 @@ CSSOM.CSSNestedDeclarations = require("./CSSNestedDeclarations").CSSNestedDeclar
|
|
|
1076
1175
|
CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
|
|
1077
1176
|
CSSOM.CSSGroupingRule = require("./CSSGroupingRule").CSSGroupingRule;
|
|
1078
1177
|
CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
|
|
1178
|
+
CSSOM.CSSCounterStyleRule = require("./CSSCounterStyleRule").CSSCounterStyleRule;
|
|
1079
1179
|
CSSOM.CSSContainerRule = require("./CSSContainerRule").CSSContainerRule;
|
|
1080
1180
|
CSSOM.CSSConditionRule = require("./CSSConditionRule").CSSConditionRule;
|
|
1081
1181
|
CSSOM.CSSSupportsRule = require("./CSSSupportsRule").CSSSupportsRule;
|