@acemir/cssom 0.9.10 → 0.9.11

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,69 @@ 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()
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*)?\)|(['"])(.*?)\4)$/;
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
898
+ if (typeof match[3] !== "undefined") {
899
+ this.namespaceURI = match[3];
900
+ }
901
+ // If quoted string form
902
+ else if (typeof match[5] !== "undefined") {
903
+ this.namespaceURI = match[5];
904
+ }
905
+ } else {
906
+ throw new DOMException("Invalid @namespace rule", "InvalidStateError");
907
+ }
908
+ }
909
+ });
910
+
911
+
912
+
850
913
  /**
851
914
  * @constructor
852
915
  * @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
@@ -970,13 +1033,25 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
970
1033
  * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
971
1034
  */
972
1035
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1036
+ if (rule === undefined && index === undefined) {
1037
+ throw new TypeError("Failed to execute 'insertRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
1038
+ }
973
1039
  if (index === void 0) {
974
1040
  index = 0;
975
1041
  }
976
1042
  if (index < 0 || index > this.cssRules.length) {
977
1043
  throw new RangeError("INDEX_SIZE_ERR");
978
1044
  }
979
- var cssRule = CSSOM.parse(rule).cssRules[0];
1045
+ var ruleToParse = String(rule);
1046
+ var parsedSheet = CSSOM.parse(ruleToParse);
1047
+ if (parsedSheet.cssRules.length !== 1) {
1048
+ var domExceptionName = "SyntaxError";
1049
+ if (ruleToParse.trimStart().startsWith('@namespace')) {
1050
+ domExceptionName = "InvalidStateError";
1051
+ }
1052
+ throw new DOMException("Failed to execute 'insertRule' on 'CSSStyleSheet': Failed to parse the rule '" + ruleToParse + "'.", domExceptionName);
1053
+ }
1054
+ var cssRule = parsedSheet.cssRules[0];
980
1055
  cssRule.parentStyleSheet = this;
981
1056
  this.cssRules.splice(index, 0, cssRule);
982
1057
  return index;
@@ -997,8 +1072,23 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
997
1072
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
998
1073
  */
999
1074
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1000
- if (index < 0 || index >= this.cssRules.length) {
1001
- throw new RangeError("INDEX_SIZE_ERR");
1075
+ if (index === undefined) {
1076
+ throw new TypeError("Failed to execute 'deleteRule' on 'CSSStyleSheet': 1 argument required, but only 0 present.")
1077
+ }
1078
+ index = Number(index);
1079
+ if (index < 0) {
1080
+ index = 4294967296 + index;
1081
+ }
1082
+ if (index >= this.cssRules.length) {
1083
+ throw new DOMException("Failed to execute 'deleteRule' on 'CSSStyleSheet': The index provided (" + index + ") is larger than the maximum index (" + this.cssRules.length + ").", "IndexSizeError");
1084
+ }
1085
+ if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
1086
+ var shouldContinue = this.cssRules.every(function (rule) {
1087
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1088
+ });
1089
+ if (!shouldContinue) {
1090
+ 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");
1091
+ }
1002
1092
  }
1003
1093
  this.cssRules.splice(index, 1);
1004
1094
  };
@@ -1618,6 +1708,8 @@ CSSOM.parse = function parse(token, errorHandler) {
1618
1708
  "atRule": true,
1619
1709
  "importRule-begin": true,
1620
1710
  "importRule": true,
1711
+ "namespaceRule-begin": true,
1712
+ "namespaceRule": true,
1621
1713
  "atBlock": true,
1622
1714
  "containerBlock": true,
1623
1715
  "conditionBlock": true,
@@ -1637,7 +1729,10 @@ CSSOM.parse = function parse(token, errorHandler) {
1637
1729
  var ancestorRules = [];
1638
1730
  var prevScope;
1639
1731
 
1640
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
1732
+ var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
1733
+
1734
+ // Track defined namespace prefixes for validation
1735
+ var definedNamespacePrefixes = {};
1641
1736
 
1642
1737
  var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
1643
1738
  // Regex above is not ES5 compliant
@@ -1648,7 +1743,7 @@ CSSOM.parse = function parse(token, errorHandler) {
1648
1743
  var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
1649
1744
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
1650
1745
  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
1746
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
1652
1747
 
1653
1748
  /**
1654
1749
  * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
@@ -1819,6 +1914,7 @@ CSSOM.parse = function parse(token, errorHandler) {
1819
1914
  * This function matches:
1820
1915
  * - Type selectors (e.g., `div`, `span`)
1821
1916
  * - Universal selector (`*`)
1917
+ * - Namespace selectors (e.g., `*|div`, `custom|div`, `|div`)
1822
1918
  * - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
1823
1919
  * - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
1824
1920
  * - Attribute selectors (e.g., `[type="text"]`)
@@ -1886,7 +1982,8 @@ CSSOM.parse = function parse(token, errorHandler) {
1886
1982
 
1887
1983
  // Fallback to a loose regexp for the overall selector structure (without deep paren matching)
1888
1984
  // 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+)+$/;
1985
+ // Modified to support namespace selectors: *|element, prefix|element, |element
1986
+ 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
1987
  return looseSelectorRegExp.test(selector);
1891
1988
  }
1892
1989
 
@@ -2029,6 +2126,30 @@ CSSOM.parse = function parse(token, errorHandler) {
2029
2126
  return basicSelectorValidation;
2030
2127
  }
2031
2128
 
2129
+ /**
2130
+ * Validates namespace selectors by checking if the namespace prefix is defined.
2131
+ *
2132
+ * @param {string} selector - The CSS selector to validate
2133
+ * @returns {boolean} Returns true if the namespace is valid, false otherwise
2134
+ */
2135
+ function validateNamespaceSelector(selector) {
2136
+ // Check if selector contains a namespace prefix
2137
+ var pipeIndex = selector.indexOf('|');
2138
+ if (pipeIndex === -1) {
2139
+ return true; // No namespace, always valid
2140
+ }
2141
+
2142
+ var namespacePrefix = selector.substring(0, pipeIndex);
2143
+
2144
+ // Universal namespace (*|) and default namespace (|) are always valid
2145
+ if (namespacePrefix === '*' || namespacePrefix === '') {
2146
+ return true;
2147
+ }
2148
+
2149
+ // Check if the custom namespace prefix is defined
2150
+ return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
2151
+ }
2152
+
2032
2153
  /**
2033
2154
  * Checks if a given CSS selector text is valid by splitting it by commas
2034
2155
  * and validating each individual selector using the `validateSelector` function.
@@ -2050,7 +2171,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2050
2171
  var selectors = parseAndSplitNestedSelectors(selectorText);
2051
2172
  for (var i = 0; i < selectors.length; i++) {
2052
2173
  var processedSelectors = selectors[i].trim();
2053
- if (!validateSelector(processedSelectors)) {
2174
+ if (!validateSelector(processedSelectors) || !validateNamespaceSelector(processedSelectors)) {
2054
2175
  return false;
2055
2176
  }
2056
2177
  }
@@ -2063,6 +2184,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2063
2184
  if (i === endingIndex) {
2064
2185
  switch (state) {
2065
2186
  case "importRule":
2187
+ case "namespaceRule":
2066
2188
  case "layerBlock":
2067
2189
  token += ";"
2068
2190
  }
@@ -2104,6 +2226,12 @@ CSSOM.parse = function parse(token, errorHandler) {
2104
2226
  token += ';'
2105
2227
  }
2106
2228
  break;
2229
+ case 'namespaceRule-begin':
2230
+ state = 'namespaceRule';
2231
+ if (i === endingIndex) {
2232
+ token += ';'
2233
+ }
2234
+ break;
2107
2235
  }
2108
2236
  break;
2109
2237
 
@@ -2127,6 +2255,9 @@ CSSOM.parse = function parse(token, errorHandler) {
2127
2255
  case 'importRule-begin':
2128
2256
  state = 'importRule';
2129
2257
  break;
2258
+ case 'namespaceRule-begin':
2259
+ state = 'namespaceRule';
2260
+ break;
2130
2261
  }
2131
2262
  break;
2132
2263
 
@@ -2147,6 +2278,10 @@ CSSOM.parse = function parse(token, errorHandler) {
2147
2278
  buffer += " ";
2148
2279
  state = "importRule";
2149
2280
  }
2281
+ if (state === "namespaceRule-begin") {
2282
+ buffer += " ";
2283
+ state = "namespaceRule";
2284
+ }
2150
2285
  break;
2151
2286
 
2152
2287
  // At-rule
@@ -2231,6 +2366,14 @@ CSSOM.parse = function parse(token, errorHandler) {
2231
2366
  buffer += "@import";
2232
2367
  }, true);
2233
2368
  break;
2369
+ } else if (token.indexOf("@namespace", i) === i) {
2370
+ buffer = "";
2371
+ validateAtRule("@namespace", function(){
2372
+ state = "namespaceRule-begin";
2373
+ i += "namespace".length;
2374
+ buffer += "@namespace";
2375
+ }, true);
2376
+ break;
2234
2377
  } else if (token.indexOf("@font-face", i) === i) {
2235
2378
  buffer = "";
2236
2379
  validateAtRule("@font-face", function(){
@@ -2534,6 +2677,31 @@ CSSOM.parse = function parse(token, errorHandler) {
2534
2677
  buffer = "";
2535
2678
  state = "before-selector";
2536
2679
  break;
2680
+ case "namespaceRule":
2681
+ var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.every(function (rule) {
2682
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
2683
+ });
2684
+ if (isValid) {
2685
+ try {
2686
+ // Validate namespace syntax before creating the rule
2687
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
2688
+ testNamespaceRule.cssText = buffer + character;
2689
+
2690
+ namespaceRule = testNamespaceRule;
2691
+ namespaceRule.parentStyleSheet = namespaceRule.styleSheet.parentStyleSheet = styleSheet;
2692
+ styleSheet.cssRules.push(namespaceRule);
2693
+
2694
+ // Track the namespace prefix for validation
2695
+ if (namespaceRule.prefix) {
2696
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
2697
+ }
2698
+ } catch(e) {
2699
+ parseError(e.message);
2700
+ }
2701
+ }
2702
+ buffer = "";
2703
+ state = "before-selector";
2704
+ break;
2537
2705
  case "layerBlock":
2538
2706
  var nameListStr = buffer.trim().split(",").map(function (name) {
2539
2707
  return name.trim();
@@ -2755,6 +2923,9 @@ CSSOM.parse = function parse(token, errorHandler) {
2755
2923
  case "importRule-begin":
2756
2924
  state = "importRule";
2757
2925
  break;
2926
+ case "namespaceRule-begin":
2927
+ state = "namespaceRule";
2928
+ break;
2758
2929
  }
2759
2930
  buffer += character;
2760
2931
  break;
@@ -0,0 +1,73 @@
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()
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*)?\)|(['"])(.*?)\4)$/;
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
57
+ if (typeof match[3] !== "undefined") {
58
+ this.namespaceURI = match[3];
59
+ }
60
+ // If quoted string form
61
+ else if (typeof match[5] !== "undefined") {
62
+ this.namespaceURI = match[5];
63
+ }
64
+ } else {
65
+ throw new DOMException("Invalid @namespace rule", "InvalidStateError");
66
+ }
67
+ }
68
+ });
69
+
70
+
71
+ //.CommonJS
72
+ exports.CSSNamespaceRule = CSSOM.CSSNamespaceRule;
73
+ ///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.11",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"