@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 +178 -7
- package/lib/CSSNamespaceRule.js +73 -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,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
|
|
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
|
|
1001
|
-
throw new
|
|
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
|
-
|
|
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
|
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;
|