Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added CSSOM (https://github.com/NV/CSSOM/issues#issue/10)

  • Loading branch information...
commit 7183a949a4802933b70b0d404347a8e5fd89db45 1 parent 905ee1a
@assaf authored
Showing with 764 additions and 0 deletions.
  1. +3 −0  .gitmodules
  2. +1 −0  cssom
  3. +760 −0 dep/cssom.js
View
3  .gitmodules
@@ -0,0 +1,3 @@
+[submodule "cssom"]
+ path = cssom
+ url = git://github.com/NV/CSSOM.git
1  cssom
@@ -0,0 +1 @@
+Subproject commit d0c8c58089af43393b5df3e383500c05cd5165c8
View
760 dep/cssom.js
@@ -0,0 +1,760 @@
+var CSSOM = {};
+
+
+/**
+ * @constructor
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
+ */
+CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration(){
+ this.length = 0;
+
+ // NON-STANDARD
+ this._importants = {};
+};
+
+
+CSSOM.CSSStyleDeclaration.prototype = {
+
+ constructor: CSSOM.CSSStyleDeclaration,
+
+ /**
+ *
+ * @param {string} name
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue
+ * @return {string} the value of the property if it has been explicitly set for this declaration block.
+ * Returns the empty string if the property has not been set.
+ */
+ getPropertyValue: function(name) {
+ return this[name] || ""
+ },
+
+ /**
+ *
+ * @param {string} name
+ * @param {string} value
+ * @param {string} [priority=null] "important" or null
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
+ */
+ setProperty: function(name, value, priority) {
+ if (this[name]) {
+ // Property already exist. Overwrite it.
+ var index = Array.prototype.indexOf.call(this, name);
+ if (index < 0) {
+ this[this.length] = name;
+ this.length++;
+ }
+ } else {
+ // New property.
+ this[this.length] = name;
+ this.length++;
+ }
+ this[name] = value;
+ this._importants[name] = priority;
+ },
+
+ /**
+ *
+ * @param {string} name
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty
+ * @return {string} the value of the property if it has been explicitly set for this declaration block.
+ * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
+ */
+ removeProperty: function(name) {
+ if (!(name in this)) {
+ return ""
+ }
+ var index = Array.prototype.indexOf.call(this, name);
+ if (index < 0) {
+ return ""
+ }
+ var prevValue = this[name];
+ this[name] = "";
+
+ // That's what WebKit and Opera do
+ Array.prototype.splice.call(this, index, 1);
+
+ // That's what Firefox does
+ //this[index] = ""
+
+ return prevValue
+ },
+
+ getPropertyCSSValue: function() {
+ //FIXME
+ },
+
+ /**
+ *
+ * @param {String} name
+ */
+ getPropertyPriority: function(name) {
+ return this._importants[name] || "";
+ },
+
+
+ /**
+ * element.style.overflow = "auto"
+ * element.style.getPropertyShorthand("overflow-x")
+ * -> "overflow"
+ */
+ getPropertyShorthand: function() {
+ //FIXME
+ },
+
+ isPropertyImplicit: function() {
+ //FIXME
+ },
+
+ // Doesn't work in IE < 9
+ get cssText(){
+ var properties = [];
+ for (var i=0, length=this.length; i < length; ++i) {
+ var name = this[i];
+ var value = this.getPropertyValue(name);
+ var priority = this.getPropertyPriority(name);
+ if (priority) {
+ priority = " !" + priority;
+ }
+ properties[i] = name + ": " + value + priority + ";";
+ }
+ return properties.join(" ")
+ }
+
+};
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule
+ */
+CSSOM.CSSRule = function CSSRule() {
+ this.parentRule = null;
+};
+
+CSSOM.CSSRule.STYLE_RULE = 1;
+CSSOM.CSSRule.IMPORT_RULE = 3;
+CSSOM.CSSRule.MEDIA_RULE = 4;
+CSSOM.CSSRule.FONT_FACE_RULE = 5;
+CSSOM.CSSRule.PAGE_RULE = 6;
+CSSOM.CSSRule.WEBKIT_KEYFRAMES_RULE = 8;
+CSSOM.CSSRule.WEBKIT_KEYFRAME_RULE = 9;
+
+// Obsolete in CSSOM http://dev.w3.org/csswg/cssom/
+//CSSOM.CSSRule.UNKNOWN_RULE = 0;
+//CSSOM.CSSRule.CHARSET_RULE = 2;
+
+// Never implemented
+//CSSOM.CSSRule.VARIABLES_RULE = 7;
+
+CSSOM.CSSRule.prototype = {
+ constructor: CSSOM.CSSRule
+ //FIXME
+};
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#cssstylerule
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
+ */
+CSSOM.CSSStyleRule = function CSSStyleRule() {
+ this.selectorText = "";
+ this.style = new CSSOM.CSSStyleDeclaration;
+};
+
+CSSOM.CSSStyleRule.prototype = new CSSOM.CSSRule;
+CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
+CSSOM.CSSStyleRule.prototype.type = 1;
+
+CSSOM.CSSStyleRule.prototype.__defineGetter__("cssText", function() {
+ var text;
+ if (this.selectorText) {
+ text = this.selectorText + " {" + this.style.cssText + "}";
+ } else {
+ text = "";
+ }
+ return text;
+});
+
+CSSOM.CSSStyleRule.prototype.__defineSetter__("cssText", function(cssText) {
+ var rule = CSSOM.CSSStyleRule.parse(cssText);
+ this.style = rule.style;
+ this.selectorText = rule.selectorText;
+});
+
+
+/**
+ * NON-STANDARD
+ * lightweight version of parse.js.
+ * @param {string} ruleText
+ * @return CSSStyleRule
+ */
+CSSOM.CSSStyleRule.parse = function(ruleText) {
+ var i = 0;
+ var state = "selector";
+ var index;
+ var j = i;
+ var buffer = "";
+
+ var SIGNIFICANT_WHITESPACE = {
+ "selector": true,
+ "value": true
+ };
+
+ var styleRule = new CSSOM.CSSStyleRule;
+ var selector, name, value, priority="";
+
+ for (var character; character = ruleText.charAt(i); i++) {
+
+ switch (character) {
+
+ case " ":
+ case "\t":
+ case "\r":
+ case "\n":
+ case "\f":
+ if (SIGNIFICANT_WHITESPACE[state]) {
+ // Squash 2 or more white-spaces in the row into 1
+ switch (ruleText.charAt(i - 1)) {
+ case " ":
+ case "\t":
+ case "\r":
+ case "\n":
+ case "\f":
+ break;
+ default:
+ buffer += " ";
+ break;
+ }
+ }
+ break;
+
+ // String
+ case '"':
+ j = i + 1;
+ index = ruleText.indexOf('"', j) + 1;
+ if (!index) {
+ throw '" is missing';
+ }
+ buffer += ruleText.slice(i, index);
+ i = index - 1;
+ break;
+
+ case "'":
+ j = i + 1;
+ index = ruleText.indexOf("'", j) + 1;
+ if (!index) {
+ throw "' is missing";
+ }
+ buffer += ruleText.slice(i, index);
+ i = index - 1;
+ break;
+
+ // Comment
+ case "/":
+ if (ruleText.charAt(i + 1) == "*") {
+ i += 2;
+ index = ruleText.indexOf("*/", i);
+ if (index == -1) {
+ throw SyntaxError("Missing */");
+ } else {
+ i = index + 1;
+ }
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case "{":
+ if (state == "selector") {
+ styleRule.selectorText = buffer.trim();
+ buffer = "";
+ state = "name";
+ }
+ break;
+
+ case ":":
+ if (state == "name") {
+ name = buffer.trim();
+ buffer = "";
+ state = "value";
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case "!":
+ if (state == "value" && ruleText.indexOf("!important", i) === i) {
+ priority = "important";
+ i += "important".length;
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case ";":
+ if (state == "value") {
+ styleRule.style.setProperty(name, buffer.trim(), priority);
+ priority = "";
+ buffer = "";
+ state = "name";
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case "}":
+ if (state == "value") {
+ styleRule.style.setProperty(name, buffer.trim(), priority);
+ priority = "";
+ buffer = "";
+ } else if (state == "name") {
+ break;
+ } else {
+ buffer += character;
+ }
+ state = "selector";
+ break;
+
+ default:
+ buffer += character;
+ break;
+
+ }
+ }
+
+ return styleRule;
+
+};
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#cssimportrule
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule
+ */
+CSSOM.CSSImportRule = function CSSImportRule() {
+ this.href = "";
+ this.media = new CSSOM.MediaList;
+ this.styleSheet = new CSSOM.CSSStyleSheet;
+};
+
+CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule;
+CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
+CSSOM.CSSImportRule.prototype.type = 3;
+CSSOM.CSSImportRule.prototype.__defineGetter__("cssText", function() {
+ return "@import url("+ this.href +") "+ this.media.mediaText +";"
+});
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#the-medialist-interface
+ */
+CSSOM.MediaList = function MediaList(){
+ this.length = 0;
+};
+
+CSSOM.MediaList.prototype = {
+
+ constructor: CSSOM.MediaList,
+
+ /**
+ * @return {string}
+ */
+ get mediaText() {
+ return Array.prototype.join.call(this, ", ");
+ },
+
+ /**
+ * @param {string} value
+ */
+ set mediaText(value) {
+ var values = value.split(",");
+ var length = this.length = values.length;
+ for (var i=0; i<length; i++) {
+ this[i] = values[i].trim();
+ }
+ },
+
+ /**
+ * @param {string} medium
+ */
+ appendMedium: function(medium) {
+ if (Array.prototype.indexOf.call(this, medium) == -1) {
+ this[this.length] = medium;
+ this.length++;
+ }
+ },
+
+ /**
+ * @param {string} medium
+ */
+ deleteMedium: function(medium) {
+ var index = Array.prototype.indexOf.call(this, medium);
+ if (index != -1) {
+ Array.prototype.splice.call(this, index, 1);
+ }
+ }
+
+};
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#cssmediarule
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule
+ */
+CSSOM.CSSMediaRule = function CSSMediaRule() {
+ this.media = new CSSOM.MediaList;
+ this.cssRules = [];
+};
+
+CSSOM.CSSMediaRule.prototype = new CSSOM.CSSRule;
+CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
+CSSOM.CSSMediaRule.prototype.type = 4;
+//FIXME
+//CSSOM.CSSMediaRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
+//CSSOM.CSSMediaRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
+
+// http://opensource.apple.com/source/WebCore/WebCore-658.28/css/CSSMediaRule.cpp
+CSSOM.CSSMediaRule.prototype.__defineGetter__("cssText", function() {
+ var cssTexts = [];
+ for (var i=0, length=this.cssRules.length; i < length; i++) {
+ cssTexts.push(this.cssRules[i].cssText);
+ }
+ return "@media " + this.media.mediaText + " {" + cssTexts.join("") + "}"
+});
+
+
+
+/**
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
+ */
+CSSOM.StyleSheet = function StyleSheet(){};
+
+
+
+/**
+ * @constructor
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
+ */
+CSSOM.CSSStyleSheet = function CSSStyleSheet() {
+ this.cssRules = [];
+};
+
+
+CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet;
+CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
+
+
+/**
+ * Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
+ *
+ * sheet = new Sheet("body {margin: 0}")
+ * sheet.toString()
+ * -> "body{margin:0;}"
+ * sheet.insertRule("img {border: none}", 0)
+ * -> 0
+ * sheet.toString()
+ * -> "img{border:none;}body{margin:0;}"
+ *
+ * @param {string} rule
+ * @param {number} index
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
+ * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
+ */
+CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
+ if (index < 0 || index > this.cssRules.length) {
+ throw new RangeError("INDEX_SIZE_ERR")
+ }
+ this.cssRules.splice(index, 0, CSSOM.CSSStyleRule.parse(rule));
+ return index
+};
+
+
+/**
+ * Used to delete a rule from the style sheet.
+ *
+ * sheet = new Sheet("img{border:none} body{margin:0}")
+ * sheet.toString()
+ * -> "img{border:none;}body{margin:0;}"
+ * sheet.deleteRule(0)
+ * sheet.toString()
+ * -> "body{margin:0;}"
+ *
+ * @param {number} index
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
+ * @return {number} The index within the style sheet's rule list of the rule to remove.
+ */
+CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
+ if (index < 0 || index >= this.cssRules.length) {
+ throw new RangeError("INDEX_SIZE_ERR");
+ }
+ this.cssRules.splice(index, 1);
+};
+
+
+/**
+ * NON-STANDARD
+ * @return {string} serialize stylesheet
+ */
+CSSOM.CSSStyleSheet.prototype.toString = function() {
+ var result = "";
+ var rules = this.cssRules;
+ for (var i=0; i<rules.length; i++) {
+ result += rules[i].cssText + "\n";
+ }
+ return result;
+};
+
+
+
+/**
+ * @param {string} token
+ * @param {Object} [options]
+ */
+CSSOM.parse = function parse(token, options) {
+
+ options = options || {};
+ var i = options.startIndex || 0;
+ var state = options.state || "selector";
+
+ var index;
+ var j = i;
+ var buffer = "";
+
+ var SIGNIFICANT_WHITESPACE = {
+ "selector": true,
+ "value": true,
+ "atRule": true,
+ "atBlock": true
+ };
+
+ var styleSheet = new CSSOM.CSSStyleSheet;
+
+ // @type CSSStyleSheet|CSSMediaRule
+ var currentScope = styleSheet;
+
+ var selector, name, value, priority="", styleRule, mediaRule;
+
+ for (var character; character = token.charAt(i); i++) {
+
+ switch (character) {
+
+ case " ":
+ case "\t":
+ case "\r":
+ case "\n":
+ case "\f":
+ if (SIGNIFICANT_WHITESPACE[state]) {
+ // Squash 2 or more white-spaces in the row into 1
+ switch (token.charAt(i - 1)) {
+ case " ":
+ case "\t":
+ case "\r":
+ case "\n":
+ case "\f":
+ break;
+ default:
+ buffer += " ";
+ break;
+ }
+ }
+ break;
+
+ // String
+ case '"':
+ j = i + 1;
+ index = token.indexOf('"', j) + 1;
+ if (!index) {
+ throw '" is missing';
+ }
+ buffer += token.slice(i, index);
+ i = index - 1;
+ break;
+
+ case "'":
+ j = i + 1;
+ index = token.indexOf("'", j) + 1;
+ if (!index) {
+ throw "' is missing";
+ }
+ buffer += token.slice(i, index);
+ i = index - 1;
+ break;
+
+ // Comment
+ case "/":
+ if (token.charAt(i + 1) == "*") {
+ i += 2;
+ index = token.indexOf("*/", i);
+ if (index == -1) {
+ throw SyntaxError("Missing */");
+ } else {
+ i = index + 1;
+ }
+ } else {
+ buffer += character;
+ }
+ break;
+
+ // At-rule
+ case "@":
+ if (token.indexOf("@media", i) == i) {
+ state = "atBlock";
+ i += "media".length;
+ buffer = "";
+ break;
+ } else if (state == "selector") {
+ state = "atRule";
+ }
+ buffer += character;
+ break;
+
+ case "{":
+ if (state == "selector" || state == "atRule") {
+ styleRule = new CSSOM.CSSStyleRule;
+ styleRule.selectorText = buffer.trim();
+ buffer = "";
+ state = "name";
+ } else if (state == "atBlock") {
+ mediaRule = new CSSOM.CSSMediaRule;
+ mediaRule.media.mediaText = buffer.trim();
+ currentScope = mediaRule;
+ buffer = "";
+ state = "selector";
+ }
+ break;
+
+ case ":":
+ if (state == "name") {
+ name = buffer.trim();
+ buffer = "";
+ state = "value";
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case "!":
+ if (state == "value" && token.indexOf("!important", i) === i) {
+ priority = "important";
+ i += "important".length;
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case ";":
+ if (state == "value") {
+ styleRule.style.setProperty(name, buffer.trim(), priority);
+ priority = "";
+ buffer = "";
+ state = "name";
+ } else if (state == "atRule") {
+ buffer = "";
+ state = "selector";
+ } else {
+ buffer += character;
+ }
+ break;
+
+ case "}":
+ if (state == "value") {
+ styleRule.style.setProperty(name, buffer.trim(), priority);
+ priority = "";
+ buffer = "";
+ currentScope.cssRules.push(styleRule);
+ } else if (state == "name") {
+ currentScope.cssRules.push(styleRule);
+ buffer = "";
+ } else if (state == "selector") {
+ // End of media rule.
+ // Nesting of media rules isn't supported
+ if (!mediaRule) {
+ throw "unexpected }";
+ }
+ styleSheet.cssRules.push(mediaRule);
+ currentScope = styleSheet;
+ buffer = "";
+ }
+ state = "selector";
+ break;
+
+ default:
+ buffer += character;
+ break;
+
+ }
+ }
+
+ return styleSheet;
+};
+
+
+
+/**
+ * Produces a deep copy of stylesheet — the instance variables of stylesheet are copied recursively.
+ * @param {CSSStyleSheet|CSSOM.CSSStyleSheet} stylesheet
+ * @nosideeffects
+ * @return {CSSOM.CSSStyleSheet}
+ */
+CSSOM.clone = function clone(stylesheet) {
+
+ var cloned = new CSSOM.CSSStyleSheet;
+
+ var rules = stylesheet.cssRules;
+ if (!rules) {
+ return cloned;
+ }
+
+ var RULE_TYPES = {
+ 1: CSSOM.CSSStyleRule,
+ 4: CSSOM.CSSMediaRule
+ //FIXME
+ //3: CSSOM.CSSImportRule,
+ //5: CSSOM.CSSFontFaceRule,
+ //6: CSSOM.CSSPageRule,
+ };
+
+ for (var i=0, rulesLength=rules.length; i < rulesLength; i++) {
+ var rule = rules[i];
+ var ruleClone = cloned.cssRules[i] = new RULE_TYPES[rule.type];
+
+ var style = rule.style;
+ if (style) {
+ var styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration;
+ for (var j=0, styleLength=style.length; j < styleLength; j++) {
+ var name = styleClone[j] = style[j];
+ styleClone[name] = style[name];
+ styleClone._importants[name] = style.getPropertyPriority(name);
+ }
+ styleClone.length = style.length;
+ }
+
+ if ("selectorText" in rule) {
+ ruleClone.selectorText = rule.selectorText;
+ }
+
+ if ("mediaText" in rule) {
+ ruleClone.mediaText = rule.mediaText;
+ }
+
+ if ("cssRules" in rule) {
+ rule.cssRules = clone(rule).cssRules;
+ }
+ }
+
+ return cloned;
+
+};
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.