@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 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
- * Regular expression to match a basic CSS selector.
1802
- *
1803
- * This regex matches the following selector components:
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
- * The pattern ensures that a string consists only of valid basic selector components,
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
- * @type {RegExp}
1834
+ * @param {string} selector
1835
+ * @returns {boolean}
1821
1836
  */
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+)+$/;
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
- * `basicSelectorRegExp`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
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
- var match, nestedSelectors, i;
1913
-
1914
- // Only pseudo-classes that accept selector lists should recurse
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
- // Reset regex lastIndex for global regex in ES5 loop
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
- var pseudoClass = match[1];
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(match[2]);
1928
- // Validate each nested selector
1929
- for (i = 0; i < nestedSelectors.length; i++) {
1930
- if (!validateSelector(nestedSelectors[i])) {
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
- // Allow "&" anywhere in the selector for nested selectors
1938
- return basicSelectorRegExp.test(selector);
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
- } else if (token.indexOf("@supports", i) === i) {
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
- * Regular expression to match a basic CSS selector.
235
- *
236
- * This regex matches the following selector components:
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
- * The pattern ensures that a string consists only of valid basic selector components,
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
- * @type {RegExp}
253
+ * @param {string} selector
254
+ * @returns {boolean}
254
255
  */
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+)+$/;
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
- * `basicSelectorRegExp`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
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
- // Only pseudo-classes that accept selector lists should recurse
348
- var selectorListPseudoClasses = {
349
- 'not': true,
350
- 'is': true,
351
- 'has': true,
352
- 'where': true
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
- // Reset regex lastIndex for global regex in ES5 loop
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
- var pseudoClass = match[1];
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(match[2]);
361
- // Validate each nested selector
362
- for (i = 0; i < nestedSelectors.length; i++) {
363
- if (!validateSelector(nestedSelectors[i])) {
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
- // Allow "&" anywhere in the selector for nested selectors
371
- return basicSelectorRegExp.test(selector);
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
- } else if (token.indexOf("@supports", i) === i) {
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;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "parser",
8
8
  "styleSheet"
9
9
  ],
10
- "version": "0.9.5",
10
+ "version": "0.9.7",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"