@acemir/cssom 0.9.10 → 0.9.12

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
@@ -847,6 +847,73 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
847
847
 
848
848
 
849
849
 
850
+ /**
851
+ * @constructor
852
+ * @see https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface
853
+ */
854
+ CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
855
+ CSSOM.CSSRule.call(this);
856
+ this.prefix = "";
857
+ this.namespaceURI = "";
858
+ this.styleSheet = new CSSOM.CSSStyleSheet();
859
+ };
860
+
861
+ CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
862
+ CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
863
+ CSSOM.CSSNamespaceRule.prototype.type = 10;
864
+
865
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
866
+ get: function() {
867
+ return "@namespace" + (this.prefix && " " + this.prefix) + " url(" + this.namespaceURI + ");";
868
+ },
869
+ set: function(cssText) {
870
+ // Reset prefix and namespaceURI
871
+ this.prefix = "";
872
+ this.namespaceURI = "";
873
+
874
+ // Remove @namespace and trim
875
+ var text = cssText.trim();
876
+ if (text.indexOf('@namespace') === 0) {
877
+ text = text.slice('@namespace'.length).trim();
878
+ }
879
+
880
+ // Remove trailing semicolon if present
881
+ if (text.charAt(text.length - 1) === ';') {
882
+ text = text.slice(0, -1).trim();
883
+ }
884
+
885
+ // Regex to match valid namespace syntax:
886
+ // 1. [optional prefix] url("...") or [optional prefix] url('...') or [optional prefix] url() or [optional prefix] url(unquoted)
887
+ // 2. [optional prefix] "..." or [optional prefix] '...'
888
+ // The prefix must be a valid CSS identifier (letters, digits, hyphens, underscores, starting with letter or underscore)
889
+ var re = /^(?:([a-zA-Z_][a-zA-Z0-9_-]*)\s+)?(?:url\(\s*(?:(['"])(.*?)\2\s*|([^)]*?))\s*\)|(['"])(.*?)\5)$/;
890
+ var match = text.match(re);
891
+
892
+ if (match) {
893
+ // If prefix is present
894
+ if (match[1]) {
895
+ this.prefix = match[1];
896
+ }
897
+ // If url(...) form with quotes
898
+ if (typeof match[3] !== "undefined") {
899
+ this.namespaceURI = match[3];
900
+ }
901
+ // If url(...) form without quotes
902
+ else if (typeof match[4] !== "undefined") {
903
+ this.namespaceURI = match[4].trim();
904
+ }
905
+ // If quoted string form
906
+ else if (typeof match[6] !== "undefined") {
907
+ this.namespaceURI = match[6];
908
+ }
909
+ } else {
910
+ throw new DOMException("Invalid @namespace rule", "InvalidStateError");
911
+ }
912
+ }
913
+ });
914
+
915
+
916
+
850
917
  /**
851
918
  * @constructor
852
919
  * @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
@@ -970,13 +1037,25 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
970
1037
  * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
971
1038
  */
972
1039
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1040
+ if (rule === undefined && index === undefined) {
1041
+ throw new TypeError("Failed to execute 'insertRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
1042
+ }
973
1043
  if (index === void 0) {
974
1044
  index = 0;
975
1045
  }
976
1046
  if (index < 0 || index > this.cssRules.length) {
977
1047
  throw new RangeError("INDEX_SIZE_ERR");
978
1048
  }
979
- var cssRule = CSSOM.parse(rule).cssRules[0];
1049
+ var ruleToParse = String(rule);
1050
+ var parsedSheet = CSSOM.parse(ruleToParse);
1051
+ if (parsedSheet.cssRules.length !== 1) {
1052
+ var domExceptionName = "SyntaxError";
1053
+ if (ruleToParse.trimStart().startsWith('@namespace')) {
1054
+ domExceptionName = "InvalidStateError";
1055
+ }
1056
+ throw new DOMException("Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to parse the rule '" + ruleToParse + "'.", domExceptionName);
1057
+ }
1058
+ var cssRule = parsedSheet.cssRules[0];
980
1059
  cssRule.parentStyleSheet = this;
981
1060
  this.cssRules.splice(index, 0, cssRule);
982
1061
  return index;
@@ -997,8 +1076,23 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
997
1076
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
998
1077
  */
999
1078
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1000
- if (index < 0 || index >= this.cssRules.length) {
1001
- throw new RangeError("INDEX_SIZE_ERR");
1079
+ if (index === undefined) {
1080
+ throw new TypeError("Failed to execute 'deleteRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
1081
+ }
1082
+ index = Number(index);
1083
+ if (index < 0) {
1084
+ index = 4294967296 + index;
1085
+ }
1086
+ if (index >= this.cssRules.length) {
1087
+ throw new DOMException("Failed to execute 'deleteRule' on 'CSSStyleSheet': The index provided (" + index + ") is larger than the maximum index (" + this.cssRules.length + ").", "IndexSizeError");
1088
+ }
1089
+ if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
1090
+ var shouldContinue = this.cssRules.every(function (rule) {
1091
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1092
+ });
1093
+ if (!shouldContinue) {
1094
+ throw new DOMException("Failed to execute 'deleteRule' on 'CSSStyleSheet': Deleting a CSSNamespaceRule is not allowed when there is rules other than @import, @layer statement, or @namespace.", "InvalidStateError");
1095
+ }
1002
1096
  }
1003
1097
  this.cssRules.splice(index, 1);
1004
1098
  };
@@ -1618,6 +1712,8 @@ CSSOM.parse = function parse(token, errorHandler) {
1618
1712
  "atRule": true,
1619
1713
  "importRule-begin": true,
1620
1714
  "importRule": true,
1715
+ "namespaceRule-begin": true,
1716
+ "namespaceRule": true,
1621
1717
  "atBlock": true,
1622
1718
  "containerBlock": true,
1623
1719
  "conditionBlock": true,
@@ -1637,7 +1733,10 @@ CSSOM.parse = function parse(token, errorHandler) {
1637
1733
  var ancestorRules = [];
1638
1734
  var prevScope;
1639
1735
 
1640
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
1736
+ var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
1737
+
1738
+ // Track defined namespace prefixes for validation
1739
+ var definedNamespacePrefixes = {};
1641
1740
 
1642
1741
  var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
1643
1742
  // Regex above is not ES5 compliant
@@ -1648,7 +1747,7 @@ CSSOM.parse = function parse(token, errorHandler) {
1648
1747
  var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
1649
1748
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
1650
1749
  var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
1651
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
1750
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
1652
1751
 
1653
1752
  /**
1654
1753
  * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
@@ -1819,6 +1918,7 @@ CSSOM.parse = function parse(token, errorHandler) {
1819
1918
  * This function matches:
1820
1919
  * - Type selectors (e.g., `div`, `span`)
1821
1920
  * - Universal selector (`*`)
1921
+ * - Namespace selectors (e.g., `*|div`, `custom|div`, `|div`)
1822
1922
  * - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
1823
1923
  * - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
1824
1924
  * - Attribute selectors (e.g., `[type="text"]`)
@@ -1886,7 +1986,8 @@ CSSOM.parse = function parse(token, errorHandler) {
1886
1986
 
1887
1987
  // Fallback to a loose regexp for the overall selector structure (without deep paren matching)
1888
1988
  // 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+)+$/;
1989
+ // Modified to support namespace selectors: *|element, prefix|element, |element
1990
+ var looseSelectorRegExp = /^((?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|(?:(?:\*|[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
1991
  return looseSelectorRegExp.test(selector);
1891
1992
  }
1892
1993
 
@@ -2029,6 +2130,30 @@ CSSOM.parse = function parse(token, errorHandler) {
2029
2130
  return basicSelectorValidation;
2030
2131
  }
2031
2132
 
2133
+ /**
2134
+ * Validates namespace selectors by checking if the namespace prefix is defined.
2135
+ *
2136
+ * @param {string} selector - The CSS selector to validate
2137
+ * @returns {boolean} Returns true if the namespace is valid, false otherwise
2138
+ */
2139
+ function validateNamespaceSelector(selector) {
2140
+ // Check if selector contains a namespace prefix
2141
+ var pipeIndex = selector.indexOf('|');
2142
+ if (pipeIndex === -1) {
2143
+ return true; // No namespace, always valid
2144
+ }
2145
+
2146
+ var namespacePrefix = selector.substring(0, pipeIndex);
2147
+
2148
+ // Universal namespace (*|) and default namespace (|) are always valid
2149
+ if (namespacePrefix === '*' || namespacePrefix === '') {
2150
+ return true;
2151
+ }
2152
+
2153
+ // Check if the custom namespace prefix is defined
2154
+ return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
2155
+ }
2156
+
2032
2157
  /**
2033
2158
  * Checks if a given CSS selector text is valid by splitting it by commas
2034
2159
  * and validating each individual selector using the `validateSelector` function.
@@ -2050,7 +2175,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2050
2175
  var selectors = parseAndSplitNestedSelectors(selectorText);
2051
2176
  for (var i = 0; i < selectors.length; i++) {
2052
2177
  var processedSelectors = selectors[i].trim();
2053
- if (!validateSelector(processedSelectors)) {
2178
+ if (!validateSelector(processedSelectors) || !validateNamespaceSelector(processedSelectors)) {
2054
2179
  return false;
2055
2180
  }
2056
2181
  }
@@ -2063,6 +2188,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2063
2188
  if (i === endingIndex) {
2064
2189
  switch (state) {
2065
2190
  case "importRule":
2191
+ case "namespaceRule":
2066
2192
  case "layerBlock":
2067
2193
  token += ";"
2068
2194
  }
@@ -2104,6 +2230,12 @@ CSSOM.parse = function parse(token, errorHandler) {
2104
2230
  token += ';'
2105
2231
  }
2106
2232
  break;
2233
+ case 'namespaceRule-begin':
2234
+ state = 'namespaceRule';
2235
+ if (i === endingIndex) {
2236
+ token += ';'
2237
+ }
2238
+ break;
2107
2239
  }
2108
2240
  break;
2109
2241
 
@@ -2127,6 +2259,9 @@ CSSOM.parse = function parse(token, errorHandler) {
2127
2259
  case 'importRule-begin':
2128
2260
  state = 'importRule';
2129
2261
  break;
2262
+ case 'namespaceRule-begin':
2263
+ state = 'namespaceRule';
2264
+ break;
2130
2265
  }
2131
2266
  break;
2132
2267
 
@@ -2147,6 +2282,10 @@ CSSOM.parse = function parse(token, errorHandler) {
2147
2282
  buffer += " ";
2148
2283
  state = "importRule";
2149
2284
  }
2285
+ if (state === "namespaceRule-begin") {
2286
+ buffer += " ";
2287
+ state = "namespaceRule";
2288
+ }
2150
2289
  break;
2151
2290
 
2152
2291
  // At-rule
@@ -2231,6 +2370,14 @@ CSSOM.parse = function parse(token, errorHandler) {
2231
2370
  buffer += "@import";
2232
2371
  }, true);
2233
2372
  break;
2373
+ } else if (token.indexOf("@namespace", i) === i) {
2374
+ buffer = "";
2375
+ validateAtRule("@namespace", function(){
2376
+ state = "namespaceRule-begin";
2377
+ i += "namespace".length;
2378
+ buffer += "@namespace";
2379
+ }, true);
2380
+ break;
2234
2381
  } else if (token.indexOf("@font-face", i) === i) {
2235
2382
  buffer = "";
2236
2383
  validateAtRule("@font-face", function(){
@@ -2534,6 +2681,31 @@ CSSOM.parse = function parse(token, errorHandler) {
2534
2681
  buffer = "";
2535
2682
  state = "before-selector";
2536
2683
  break;
2684
+ case "namespaceRule":
2685
+ var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.every(function (rule) {
2686
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
2687
+ });
2688
+ if (isValid) {
2689
+ try {
2690
+ // Validate namespace syntax before creating the rule
2691
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
2692
+ testNamespaceRule.cssText = buffer + character;
2693
+
2694
+ namespaceRule = testNamespaceRule;
2695
+ namespaceRule.parentStyleSheet = namespaceRule.styleSheet.parentStyleSheet = styleSheet;
2696
+ styleSheet.cssRules.push(namespaceRule);
2697
+
2698
+ // Track the namespace prefix for validation
2699
+ if (namespaceRule.prefix) {
2700
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
2701
+ }
2702
+ } catch(e) {
2703
+ parseError(e.message);
2704
+ }
2705
+ }
2706
+ buffer = "";
2707
+ state = "before-selector";
2708
+ break;
2537
2709
  case "layerBlock":
2538
2710
  var nameListStr = buffer.trim().split(",").map(function (name) {
2539
2711
  return name.trim();
@@ -2755,6 +2927,9 @@ CSSOM.parse = function parse(token, errorHandler) {
2755
2927
  case "importRule-begin":
2756
2928
  state = "importRule";
2757
2929
  break;
2930
+ case "namespaceRule-begin":
2931
+ state = "namespaceRule";
2932
+ break;
2758
2933
  }
2759
2934
  buffer += character;
2760
2935
  break;
@@ -0,0 +1,77 @@
1
+ //.CommonJS
2
+ var CSSOM = {
3
+ CSSRule: require("./CSSRule").CSSRule,
4
+ CSSStyleSheet: require("./CSSStyleSheet").CSSStyleSheet
5
+ };
6
+ ///CommonJS
7
+
8
+
9
+ /**
10
+ * @constructor
11
+ * @see https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface
12
+ */
13
+ CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
14
+ CSSOM.CSSRule.call(this);
15
+ this.prefix = "";
16
+ this.namespaceURI = "";
17
+ this.styleSheet = new CSSOM.CSSStyleSheet();
18
+ };
19
+
20
+ CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
21
+ CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
22
+ CSSOM.CSSNamespaceRule.prototype.type = 10;
23
+
24
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
25
+ get: function() {
26
+ return "@namespace" + (this.prefix && " " + this.prefix) + " url(" + this.namespaceURI + ");";
27
+ },
28
+ set: function(cssText) {
29
+ // Reset prefix and namespaceURI
30
+ this.prefix = "";
31
+ this.namespaceURI = "";
32
+
33
+ // Remove @namespace and trim
34
+ var text = cssText.trim();
35
+ if (text.indexOf('@namespace') === 0) {
36
+ text = text.slice('@namespace'.length).trim();
37
+ }
38
+
39
+ // Remove trailing semicolon if present
40
+ if (text.charAt(text.length - 1) === ';') {
41
+ text = text.slice(0, -1).trim();
42
+ }
43
+
44
+ // Regex to match valid namespace syntax:
45
+ // 1. [optional prefix] url("...") or [optional prefix] url('...') or [optional prefix] url() or [optional prefix] url(unquoted)
46
+ // 2. [optional prefix] "..." or [optional prefix] '...'
47
+ // The prefix must be a valid CSS identifier (letters, digits, hyphens, underscores, starting with letter or underscore)
48
+ var re = /^(?:([a-zA-Z_][a-zA-Z0-9_-]*)\s+)?(?:url\(\s*(?:(['"])(.*?)\2\s*|([^)]*?))\s*\)|(['"])(.*?)\5)$/;
49
+ var match = text.match(re);
50
+
51
+ if (match) {
52
+ // If prefix is present
53
+ if (match[1]) {
54
+ this.prefix = match[1];
55
+ }
56
+ // If url(...) form with quotes
57
+ if (typeof match[3] !== "undefined") {
58
+ this.namespaceURI = match[3];
59
+ }
60
+ // If url(...) form without quotes
61
+ else if (typeof match[4] !== "undefined") {
62
+ this.namespaceURI = match[4].trim();
63
+ }
64
+ // If quoted string form
65
+ else if (typeof match[6] !== "undefined") {
66
+ this.namespaceURI = match[6];
67
+ }
68
+ } else {
69
+ throw new DOMException("Invalid @namespace rule", "InvalidStateError");
70
+ }
71
+ }
72
+ });
73
+
74
+
75
+ //.CommonJS
76
+ exports.CSSNamespaceRule = CSSOM.CSSNamespaceRule;
77
+ ///CommonJS
@@ -37,13 +37,25 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
37
37
  * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
38
38
  */
39
39
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
40
+ if (rule === undefined && index === undefined) {
41
+ throw new TypeError("Failed to execute 'insertRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
42
+ }
40
43
  if (index === void 0) {
41
44
  index = 0;
42
45
  }
43
46
  if (index < 0 || index > this.cssRules.length) {
44
47
  throw new RangeError("INDEX_SIZE_ERR");
45
48
  }
46
- var cssRule = CSSOM.parse(rule).cssRules[0];
49
+ var ruleToParse = String(rule);
50
+ var parsedSheet = CSSOM.parse(ruleToParse);
51
+ if (parsedSheet.cssRules.length !== 1) {
52
+ var domExceptionName = "SyntaxError";
53
+ if (ruleToParse.trimStart().startsWith('@namespace')) {
54
+ domExceptionName = "InvalidStateError";
55
+ }
56
+ throw new DOMException("Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to parse the rule '" + ruleToParse + "'.", domExceptionName);
57
+ }
58
+ var cssRule = parsedSheet.cssRules[0];
47
59
  cssRule.parentStyleSheet = this;
48
60
  this.cssRules.splice(index, 0, cssRule);
49
61
  return index;
@@ -64,8 +76,23 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
64
76
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
65
77
  */
66
78
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
67
- if (index < 0 || index >= this.cssRules.length) {
68
- throw new RangeError("INDEX_SIZE_ERR");
79
+ if (index === undefined) {
80
+ throw new TypeError("Failed to execute 'deleteRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
81
+ }
82
+ index = Number(index);
83
+ if (index < 0) {
84
+ index = 4294967296 + index;
85
+ }
86
+ if (index >= this.cssRules.length) {
87
+ throw new DOMException("Failed to execute 'deleteRule' on 'CSSStyleSheet': The index provided (" + index + ") is larger than the maximum index (" + this.cssRules.length + ").", "IndexSizeError");
88
+ }
89
+ if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
90
+ var shouldContinue = this.cssRules.every(function (rule) {
91
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
92
+ });
93
+ if (!shouldContinue) {
94
+ throw new DOMException("Failed to execute 'deleteRule' on 'CSSStyleSheet': Deleting a CSSNamespaceRule is not allowed when there is rules other than @import, @layer statement, or @namespace.", "InvalidStateError");
95
+ }
69
96
  }
70
97
  this.cssRules.splice(index, 1);
71
98
  };
package/lib/index.js CHANGED
@@ -12,6 +12,7 @@ exports.CSSMediaRule = require('./CSSMediaRule').CSSMediaRule;
12
12
  exports.CSSContainerRule = require('./CSSContainerRule').CSSContainerRule;
13
13
  exports.CSSSupportsRule = require('./CSSSupportsRule').CSSSupportsRule;
14
14
  exports.CSSImportRule = require('./CSSImportRule').CSSImportRule;
15
+ exports.CSSNamespaceRule = require('./CSSNamespaceRule').CSSNamespaceRule;
15
16
  exports.CSSFontFaceRule = require('./CSSFontFaceRule').CSSFontFaceRule;
16
17
  exports.CSSHostRule = require('./CSSHostRule').CSSHostRule;
17
18
  exports.CSSStartingStyleRule = require('./CSSStartingStyleRule').CSSStartingStyleRule;
package/lib/parse.js CHANGED
@@ -37,6 +37,8 @@ CSSOM.parse = function parse(token, errorHandler) {
37
37
  "atRule": true,
38
38
  "importRule-begin": true,
39
39
  "importRule": true,
40
+ "namespaceRule-begin": true,
41
+ "namespaceRule": true,
40
42
  "atBlock": true,
41
43
  "containerBlock": true,
42
44
  "conditionBlock": true,
@@ -56,7 +58,10 @@ CSSOM.parse = function parse(token, errorHandler) {
56
58
  var ancestorRules = [];
57
59
  var prevScope;
58
60
 
59
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
61
+ var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
62
+
63
+ // Track defined namespace prefixes for validation
64
+ var definedNamespacePrefixes = {};
60
65
 
61
66
  var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
62
67
  // Regex above is not ES5 compliant
@@ -67,7 +72,7 @@ CSSOM.parse = function parse(token, errorHandler) {
67
72
  var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
68
73
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
69
74
  var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
70
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
75
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
71
76
 
72
77
  /**
73
78
  * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
@@ -238,6 +243,7 @@ CSSOM.parse = function parse(token, errorHandler) {
238
243
  * This function matches:
239
244
  * - Type selectors (e.g., `div`, `span`)
240
245
  * - Universal selector (`*`)
246
+ * - Namespace selectors (e.g., `*|div`, `custom|div`, `|div`)
241
247
  * - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
242
248
  * - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
243
249
  * - Attribute selectors (e.g., `[type="text"]`)
@@ -305,7 +311,8 @@ CSSOM.parse = function parse(token, errorHandler) {
305
311
 
306
312
  // Fallback to a loose regexp for the overall selector structure (without deep paren matching)
307
313
  // 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+)+$/;
314
+ // Modified to support namespace selectors: *|element, prefix|element, |element
315
+ var looseSelectorRegExp = /^((?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|(?:(?:\*|[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
316
  return looseSelectorRegExp.test(selector);
310
317
  }
311
318
 
@@ -448,6 +455,30 @@ CSSOM.parse = function parse(token, errorHandler) {
448
455
  return basicSelectorValidation;
449
456
  }
450
457
 
458
+ /**
459
+ * Validates namespace selectors by checking if the namespace prefix is defined.
460
+ *
461
+ * @param {string} selector - The CSS selector to validate
462
+ * @returns {boolean} Returns true if the namespace is valid, false otherwise
463
+ */
464
+ function validateNamespaceSelector(selector) {
465
+ // Check if selector contains a namespace prefix
466
+ var pipeIndex = selector.indexOf('|');
467
+ if (pipeIndex === -1) {
468
+ return true; // No namespace, always valid
469
+ }
470
+
471
+ var namespacePrefix = selector.substring(0, pipeIndex);
472
+
473
+ // Universal namespace (*|) and default namespace (|) are always valid
474
+ if (namespacePrefix === '*' || namespacePrefix === '') {
475
+ return true;
476
+ }
477
+
478
+ // Check if the custom namespace prefix is defined
479
+ return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
480
+ }
481
+
451
482
  /**
452
483
  * Checks if a given CSS selector text is valid by splitting it by commas
453
484
  * and validating each individual selector using the `validateSelector` function.
@@ -469,7 +500,7 @@ CSSOM.parse = function parse(token, errorHandler) {
469
500
  var selectors = parseAndSplitNestedSelectors(selectorText);
470
501
  for (var i = 0; i < selectors.length; i++) {
471
502
  var processedSelectors = selectors[i].trim();
472
- if (!validateSelector(processedSelectors)) {
503
+ if (!validateSelector(processedSelectors) || !validateNamespaceSelector(processedSelectors)) {
473
504
  return false;
474
505
  }
475
506
  }
@@ -482,6 +513,7 @@ CSSOM.parse = function parse(token, errorHandler) {
482
513
  if (i === endingIndex) {
483
514
  switch (state) {
484
515
  case "importRule":
516
+ case "namespaceRule":
485
517
  case "layerBlock":
486
518
  token += ";"
487
519
  }
@@ -523,6 +555,12 @@ CSSOM.parse = function parse(token, errorHandler) {
523
555
  token += ';'
524
556
  }
525
557
  break;
558
+ case 'namespaceRule-begin':
559
+ state = 'namespaceRule';
560
+ if (i === endingIndex) {
561
+ token += ';'
562
+ }
563
+ break;
526
564
  }
527
565
  break;
528
566
 
@@ -546,6 +584,9 @@ CSSOM.parse = function parse(token, errorHandler) {
546
584
  case 'importRule-begin':
547
585
  state = 'importRule';
548
586
  break;
587
+ case 'namespaceRule-begin':
588
+ state = 'namespaceRule';
589
+ break;
549
590
  }
550
591
  break;
551
592
 
@@ -566,6 +607,10 @@ CSSOM.parse = function parse(token, errorHandler) {
566
607
  buffer += " ";
567
608
  state = "importRule";
568
609
  }
610
+ if (state === "namespaceRule-begin") {
611
+ buffer += " ";
612
+ state = "namespaceRule";
613
+ }
569
614
  break;
570
615
 
571
616
  // At-rule
@@ -650,6 +695,14 @@ CSSOM.parse = function parse(token, errorHandler) {
650
695
  buffer += "@import";
651
696
  }, true);
652
697
  break;
698
+ } else if (token.indexOf("@namespace", i) === i) {
699
+ buffer = "";
700
+ validateAtRule("@namespace", function(){
701
+ state = "namespaceRule-begin";
702
+ i += "namespace".length;
703
+ buffer += "@namespace";
704
+ }, true);
705
+ break;
653
706
  } else if (token.indexOf("@font-face", i) === i) {
654
707
  buffer = "";
655
708
  validateAtRule("@font-face", function(){
@@ -953,6 +1006,31 @@ CSSOM.parse = function parse(token, errorHandler) {
953
1006
  buffer = "";
954
1007
  state = "before-selector";
955
1008
  break;
1009
+ case "namespaceRule":
1010
+ var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.every(function (rule) {
1011
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1012
+ });
1013
+ if (isValid) {
1014
+ try {
1015
+ // Validate namespace syntax before creating the rule
1016
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
1017
+ testNamespaceRule.cssText = buffer + character;
1018
+
1019
+ namespaceRule = testNamespaceRule;
1020
+ namespaceRule.parentStyleSheet = namespaceRule.styleSheet.parentStyleSheet = styleSheet;
1021
+ styleSheet.cssRules.push(namespaceRule);
1022
+
1023
+ // Track the namespace prefix for validation
1024
+ if (namespaceRule.prefix) {
1025
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
1026
+ }
1027
+ } catch(e) {
1028
+ parseError(e.message);
1029
+ }
1030
+ }
1031
+ buffer = "";
1032
+ state = "before-selector";
1033
+ break;
956
1034
  case "layerBlock":
957
1035
  var nameListStr = buffer.trim().split(",").map(function (name) {
958
1036
  return name.trim();
@@ -1174,6 +1252,9 @@ CSSOM.parse = function parse(token, errorHandler) {
1174
1252
  case "importRule-begin":
1175
1253
  state = "importRule";
1176
1254
  break;
1255
+ case "namespaceRule-begin":
1256
+ state = "namespaceRule";
1257
+ break;
1177
1258
  }
1178
1259
  buffer += character;
1179
1260
  break;
@@ -1191,6 +1272,7 @@ CSSOM.CSSStyleSheet = require("./CSSStyleSheet").CSSStyleSheet;
1191
1272
  CSSOM.CSSStyleRule = require("./CSSStyleRule").CSSStyleRule;
1192
1273
  CSSOM.CSSNestedDeclarations = require("./CSSNestedDeclarations").CSSNestedDeclarations;
1193
1274
  CSSOM.CSSImportRule = require("./CSSImportRule").CSSImportRule;
1275
+ CSSOM.CSSNamespaceRule = require("./CSSNamespaceRule").CSSNamespaceRule;
1194
1276
  CSSOM.CSSGroupingRule = require("./CSSGroupingRule").CSSGroupingRule;
1195
1277
  CSSOM.CSSMediaRule = require("./CSSMediaRule").CSSMediaRule;
1196
1278
  CSSOM.CSSCounterStyleRule = require("./CSSCounterStyleRule").CSSCounterStyleRule;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "parser",
8
8
  "styleSheet"
9
9
  ],
10
- "version": "0.9.10",
10
+ "version": "0.9.12",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"