@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 +182 -7
- package/lib/CSSNamespaceRule.js +77 -0
- package/lib/CSSStyleSheet.js +30 -3
- package/lib/index.js +1 -0
- package/lib/parse.js +86 -4
- package/package.json +1 -1
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
|
|
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
|
|
1001
|
-
throw new
|
|
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
|
-
|
|
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
|
package/lib/CSSStyleSheet.js
CHANGED
|
@@ -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
|
|
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
|
|
68
|
-
throw new
|
|
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
|
-
|
|
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;
|