[svn r54] CSS variables, unknown at-rules bug trunk
authordglazman
Mon, 22 Mar 2010 10:31:48 -0500
branchtrunk
changeset 50 3fd45aa53043
parent 49 4b91d1dc3197
child 51 aab73e6dcbc7
[svn r54] CSS variables, unknown at-rules bug
cssParser.js
demo.xhtml
--- a/cssParser.js	Wed Mar 17 05:14:11 2010 -0500
+++ b/cssParser.js	Mon Mar 22 10:31:48 2010 -0500
@@ -827,18 +827,21 @@
   addUnknownRule: function(aSheet, aString) {
     var rule = new jscsspErrorRule();
     rule.parsedCssText = aString;
+    rule.parentStyleSheet = aSheet;
     aSheet.cssRules.push(rule);
   },
 
   addWhitespace: function(aSheet, aString) {
     var rule = new jscsspWhitespace();
     rule.parsedCssText = aString;
+    rule.parentStyleSheet = aSheet;
     aSheet.cssRules.push(rule);
   },
 
   addComment: function(aSheet, aString) {
     var rule = new jscsspComment();
     rule.parsedCssText = aString;
+    rule.parentStyleSheet = aSheet;
     aSheet.cssRules.push(rule);
   },
 
@@ -857,6 +860,7 @@
           var rule = new jscsspCharsetRule();
           rule.encoding = encoding;
           rule.parsedCssText = s;
+          rule.parentStyleSheet = aSheet;
           aSheet.cssRules.push(rule);
           return true;
         }
@@ -910,6 +914,7 @@
         rule.parsedCssText = s;
         rule.href = href;
         rule.media = media;
+        rule.parentStyleSheet = aSheet;
         aSheet.cssRules.push(rule);
         return true;
       }
@@ -919,6 +924,74 @@
     return false;
   },
 
+  parseVariablesRule: function(token, aSheet) {
+    var s = token.value;
+    var declarations = [];
+    var valid = false;
+    this.mScanner.preserveState();
+    token = this.getToken(true, true);
+    var media = [];
+    var foundMedia = false;
+    while (token.isNotNull()) {
+      if (token.isIdent()) {
+        foundMedia = true;
+        s += " " + token.value;
+        media.push(token.value);
+        token = this.getToken(true, true);
+        if (token.isSymbol(",")) {
+          s += ",";
+        } else {
+          if (token.isSymbol("{"))
+            this.ungetToken();
+          else {
+            // error...
+            token = null;
+            break;
+          }
+        }
+      } else if (token.isSymbol("{"))
+        break;
+      else if (foundMedia) {
+        token = null;
+        // not a media list
+        break;
+      }
+      token = this.getToken(true, true);
+    }
+
+    if (token.isSymbol("{")) {
+      s += " {";
+      token = this.getToken(true, true);
+      while (true) {
+        if (!token.isNotNull()) {
+          valid = true;
+          break;
+        }
+        if (token.isSymbol("}")) {
+          s += "}";
+          valid = true;
+          break;
+        } else {
+          var d = this.parseDeclaration(token, declarations, true, false, aSheet);
+          s += ((d && declarations.length) ? " " : "") + d;
+        }
+        token = this.getToken(true, false);
+      }
+    }
+    if (valid) {
+      this.mScanner.forgetState();
+      var rule = new jscsspVariablesRule();
+      rule.parsedCssText = s;
+      rule.declarations = declarations;
+      rule.media = media;
+      rule.parentStyleSheet = aSheet;
+      aSheet.cssRules.push(rule)
+      return true;
+    }
+    this.mScanner.restoreState();
+    return false;
+  },
+
   parseNamespaceRule: function(aToken, aSheet) {
     var s = aToken.value;
     var valid = false;
@@ -958,6 +1031,7 @@
           rule.parsedCssText = s;
           rule.prefix = prefix;
           rule.url = url;
+          rule.parentStyleSheet = aSheet;
           aSheet.cssRules.push(rule);
           return true;
         }
@@ -986,7 +1060,7 @@
             valid = true;
             break;
           } else {
-            var d = this.parseDeclaration(token, descriptors, false);
+            var d = this.parseDeclaration(token, descriptors, false, false, aSheet);
             s += ((d && descriptors.length) ? " " : "") + d;
           }
           token = this.getToken(true, false);
@@ -998,6 +1072,7 @@
       var rule = new jscsspFontFaceRule();
       rule.parsedCssText = s;
       rule.descriptors = descriptors;
+      rule.parentStyleSheet = aSheet;
       aSheet.cssRules.push(rule)
       return true;
     }
@@ -1031,7 +1106,7 @@
             valid = true;
             break;
           } else {
-            var d = this.parseDeclaration(token, declarations, true);
+            var d = this.parseDeclaration(token, declarations, true, true, aSheet);
             s += ((d && declarations.length) ? " " : "") + d;
           }
           token = this.getToken(true, false);
@@ -1044,6 +1119,7 @@
       rule.parsedCssText = s;
       rule.pageSelector = pageSelector;
       rule.declarations = declarations;
+      rule.parentStyleSheet = aSheet;
       aSheet.cssRules.push(rule)
       return true;
     }
@@ -1051,7 +1127,7 @@
     return false;
   },
 
-  parseDefaultPropertyValue: function(token, aDecl, aAcceptPriority, descriptor) {
+  parseDefaultPropertyValue: function(token, aDecl, aAcceptPriority, descriptor, aSheet) {
     var valueText = "";
     var blocks = [];
     var foundPriority = false;
@@ -1073,7 +1149,8 @@
         }
         else {
           valueText = this.kINHERIT;
-          values.push(this.kINHERIT);
+          var value = new jscsspVariable(kJscsspINHERIT_VALUE, aSheet);
+          values.push(value);
           token = this.getToken(true, true);
           break;
         }
@@ -1098,25 +1175,52 @@
       // probably a |values: []| field holding dimensions, percentages
       // functions, idents, numbers and symbols, in that order.
       if (token.isFunction()) {
-        var fn = token.value;
-        token = this.getToken(false, true);
-        var arg = this.parseFunctionArgument(token);
-        if (arg) {
-          valueText += fn + arg; 
-          values.push(fn + arg);
+        if (token.isFunction("var(")) {
+          token = this.getToken(true, true);
+          if (token.isIdent()) {
+            var name = token.value;
+            token = this.getToken(true, true);
+            if (token.isSymbol(")")) {
+              var value = new jscsspVariable(kJscsspVARIABLE_VALUE, aSheet);
+              valueText += "var(" + name + ")";
+              value.name = name;
+              values.push(value);
+            }
+            else
+              return "";
+          }
+          else
+            return "";
+        }
+        else {
+	        var fn = token.value;
+	        token = this.getToken(false, true);
+	        var arg = this.parseFunctionArgument(token);
+	        if (arg) {
+	          valueText += fn + arg;
+	          var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
+	          value.value = valueText;
+	          values.push(value);
+	        }
+	        else
+	          return "";
+        }
+      }
+      else if (token.isSymbol("#")) {
+        var color = this.parseColor(token);
+        if (color) {
+          valueText += color;
+          var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
+          value.value = color;
+          values.push(value);
         }
         else
           return "";
       }
-      else if (token.isSymbol("#")) {
-        var color = this.parseColor(token);
-        if (color)
-          values.push(color);
-        else
-          return "";
-      }
       else if (!token.isWhiteSpace() && !token.isSymbol(",")) {
-        values.push(token.value);
+        var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, aSheet);
+        value.value = token.value;
+        values.push(value);
         valueText += token.value;
       }
       else
@@ -1200,10 +1304,10 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray(aProperty + "-top", [top], top));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray(aProperty + "-right", [right], right));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray(aProperty + "-bottom", [bottom], bottom));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray(aProperty + "-left", [left], left));
+    aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-top", top));
+    aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-right", right));
+    aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-bottom", bottom));
+    aDecl.push(this._createJscsspDeclarationFromValue(aProperty + "-left", left));
    return top + " " + right + " " + bottom + " " + left;
   },
 
@@ -1275,10 +1379,10 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-top-color", [top], top));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-right-color", [right], right));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-bottom-color", [bottom], bottom));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-left-color", [left], left));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-top-color", top));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-right-color", right));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-color", bottom));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-left-color", left));
     return top + " " + right + " " + bottom + " " + left;
   },
 
@@ -1337,8 +1441,8 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("cue-before", [before], before));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("cue-after", [after], after));
+    aDecl.push(this._createJscsspDeclarationFromValue("cue-before", before));
+    aDecl.push(this._createJscsspDeclarationFromValue("cue-after", after));
     return before + " " + after;
   },
 
@@ -1391,8 +1495,8 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("pause-before", [before], before));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("pause-after", [after], after));
+    aDecl.push(this._createJscsspDeclarationFromValue("pause-before", before));
+    aDecl.push(this._createJscsspDeclarationFromValue("pause-after", after));
     return before + " " + after;
   },
 
@@ -1462,10 +1566,10 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-top-width", [top], top));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-right-width", [right], right));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-bottom-width", [bottom], bottom));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-left-width", [left], left));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-top-width", top));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-right-width", right));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-width", bottom));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-left-width", left));
     return top + " " + right + " " + bottom + " " + left;
   },
 
@@ -1533,10 +1637,10 @@
         return "";
     }
     this.mScanner.forgetState();
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-top-style", [top], top));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-right-style", [right], right));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-bottom-style", [bottom], bottom));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("border-left-style", [left], left));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-top-style", top));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-right-style", right));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-bottom-style", bottom));
+    aDecl.push(this._createJscsspDeclarationFromValue("border-left-style", left));
     return top + " " + right + " " + bottom + " " + left;
   },
 
@@ -1595,9 +1699,9 @@
     bColor = bColor ? bColor : "-moz-initial";
 
     function addPropertyToDecl(aSelf, aDecl, property, w, s, c) {
-      aDecl.push(aSelf._createJscsspDeclarationFromValuesArray(property + "-width", [w], w));
-      aDecl.push(aSelf._createJscsspDeclarationFromValuesArray(property + "-style", [s], s));
-      aDecl.push(aSelf._createJscsspDeclarationFromValuesArray(property + "-color", [c], c));
+      aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-width", w));
+      aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-style", s));
+      aDecl.push(aSelf._createJscsspDeclarationFromValue(property + "-color", c));
     }
 
     if (aProperty == "border") {
@@ -1717,11 +1821,11 @@
     bgAttachment = bgAttachment ? bgAttachment : "scroll";
     bgPosition = bgPosition ? bgPosition : "top left";
 
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("background-color", [bgColor], bgColor));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("background-image", [bgImage], bgImage));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("background-repeat", [bgRepeat], bgRepeat));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("background-attachment", [bgAttachment], bgAttachment));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("background-position", [bgPosition], bgPosition));
+    aDecl.push(this._createJscsspDeclarationFromValue("background-color", bgColor));
+    aDecl.push(this._createJscsspDeclarationFromValue("background-image", bgImage));
+    aDecl.push(this._createJscsspDeclarationFromValue("background-repeat", bgRepeat));
+    aDecl.push(this._createJscsspDeclarationFromValue("background-attachment", bgAttachment));
+    aDecl.push(this._createJscsspDeclarationFromValue("background-position", bgPosition));
     return bgColor + " " + bgImage + " " + bgRepeat + " " + bgAttachment + " " + bgPosition;
   },
 
@@ -1784,9 +1888,9 @@
     lImage = lImage ? lImage : "none";
     lPosition = lPosition ? lPosition : "outside";
 
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("list-style-type", [lType], lType));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("list-style-position", [lPosition], lPosition));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("list-style-image", [lImage], lImage));
+    aDecl.push(this._createJscsspDeclarationFromValue("list-style-type", lType));
+    aDecl.push(this._createJscsspDeclarationFromValue("list-style-position", lPosition));
+    aDecl.push(this._createJscsspDeclarationFromValue("list-style-image", lImage));
     return lType + " " + lPosition + " " + lImage;
   },
 
@@ -1904,12 +2008,16 @@
                 break;
               }
               else if (token.isIdent() && token.value in kFamily) {
-                fFamilyValues.push(token.value);
+                var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
+                value.value = token.value;
+                fFamilyValues.push(value);
                 fFamily += token.value;
                 break;
               }
               else if (token.isString() || token.isIdent()) {
-                fFamilyValues.push(token.value);
+                var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
+                value.value = token.value;
+                fFamilyValues.push(value);
                 fFamily += token.value;
                 lastWasComma = false;
               }
@@ -1936,7 +2044,7 @@
     // create the declarations
     this.mScanner.forgetState();
     if (fSystem) {
-      aDecl.push(this._createJscsspDeclarationFromValuesArray("font", [fSystem], fSystem));
+      aDecl.push(this._createJscsspDeclarationFromValue("font", fSystem));
       return fSystem;
     }
     fStyle = fStyle ? fStyle : "normal";
@@ -1946,11 +2054,11 @@
     fLineHeight = fLineHeight ? fLineHeight : "normal";
     fFamily = fFamily ? fFamily : "-moz-initial";
 
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("font-style", [fStyle], fStyle));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("font-variant", [fVariant], fVariant));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("font-weight", [fWeight], fWeight));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("font-size", [fSize], fSize));
-    aDecl.push(this._createJscsspDeclarationFromValuesArray("line-height", [fLineHeight], fLineHeight));
+    aDecl.push(this._createJscsspDeclarationFromValue("font-style", fStyle));
+    aDecl.push(this._createJscsspDeclarationFromValue("font-variant", fVariant));
+    aDecl.push(this._createJscsspDeclarationFromValue("font-weight", fWeight));
+    aDecl.push(this._createJscsspDeclarationFromValue("font-size", fSize));
+    aDecl.push(this._createJscsspDeclarationFromValue("line-height", fLineHeight));
     aDecl.push(this._createJscsspDeclarationFromValuesArray("font-family", fFamilyValues, fFamily));
     return fStyle + " " + fVariant + " " + fWeight + " " + fSize + "/" + fLineHeight + " " + fFamily;
   },
@@ -1964,6 +2072,18 @@
     return decl;
   },
 
+  _createJscsspDeclarationFromValue: function(property, valueText)
+  {
+    var decl = new jscsspDeclaration();
+    decl.property = property;
+    var value = new jscsspVariable(kJscsspPRIMITIVE_VALUE, null);
+    value.value = valueText;
+    decl.values = [value];
+    decl.valueText = valueText;
+    decl.parsedCssText = property + ": " + valueText + ";";
+    return decl;
+  },
+
   _createJscsspDeclarationFromValuesArray: function(property, values, valueText)
   {
     var decl = new jscsspDeclaration();
@@ -2143,7 +2263,7 @@
     return color;
   },
 
-  parseDeclaration: function(aToken, aDecl, aAcceptPriority) {
+  parseDeclaration: function(aToken, aDecl, aAcceptPriority, aExpandShorthands, aSheet) {
     this.mScanner.preserveState();
     var blocks = [];
     if (aToken.isIdent()) {
@@ -2154,47 +2274,50 @@
 
         var value = "";
         var declarations = [];
-        switch (descriptor) {
-          case "background":
-            value = this.parseBackgroundShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "margin":
-          case "padding":
-            value = this.parseMarginOrPaddingShorthand(token, declarations, aAcceptPriority, descriptor);
-            break;
-          case "border-color":
-            value = this.parseBorderColorShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "border-style":
-            value = this.parseBorderStyleShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "border-width":
-            value = this.parseBorderWidthShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "border-top":
-          case "border-right":
-          case "border-bottom":
-          case "border-left":
-          case "border":
-          case "outline":
-            value = this.parseBorderEdgeOrOutlineShorthand(token, declarations, aAcceptPriority, descriptor);
-            break;
-          case "cue":
-            value = this.parseCueShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "pause":
-            value = this.parsePauseShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "font":
-            value = this.parseFontShorthand(token, declarations, aAcceptPriority);
-            break;
-          case "list-style":
-            value = this.parseListStyleShorthand(token, declarations, aAcceptPriority);
-            break;
-          default:
-            value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor);
-            break;
-        }
+        if (aExpandShorthands)
+	        switch (descriptor) {
+	          case "background":
+	            value = this.parseBackgroundShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "margin":
+	          case "padding":
+	            value = this.parseMarginOrPaddingShorthand(token, declarations, aAcceptPriority, descriptor);
+	            break;
+	          case "border-color":
+	            value = this.parseBorderColorShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "border-style":
+	            value = this.parseBorderStyleShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "border-width":
+	            value = this.parseBorderWidthShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "border-top":
+	          case "border-right":
+	          case "border-bottom":
+	          case "border-left":
+	          case "border":
+	          case "outline":
+	            value = this.parseBorderEdgeOrOutlineShorthand(token, declarations, aAcceptPriority, descriptor);
+	            break;
+	          case "cue":
+	            value = this.parseCueShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "pause":
+	            value = this.parsePauseShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "font":
+	            value = this.parseFontShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          case "list-style":
+	            value = this.parseListStyleShorthand(token, declarations, aAcceptPriority);
+	            break;
+	          default:
+	            value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet);
+	            break;
+	        }
+        else
+          value = this.parseDefaultPropertyValue(token, declarations, aAcceptPriority, descriptor, aSheet);
         token = this.currentToken;
         if (value) // no error above
         {
@@ -2312,7 +2435,7 @@
           valid = true;
           break;
         } else {
-          var r = this.parseStyleRule(token, mediaRule);
+          var r = this.parseStyleRule(token, mediaRule, true);
           if (r)
             s += r;
         }
@@ -2340,7 +2463,8 @@
     return str;
   },
 
-  parseStyleRule: function(aToken, aCssRules) {
+  parseStyleRule: function(aToken, aOwner, aIsInsideMediaRule)
+  {
     // first let's see if we have a selector here...
     var selector = this.parseSelector(aToken, false);
     var valid = false;
@@ -2362,7 +2486,7 @@
             valid = true;
             break;
           } else {
-            var d = this.parseDeclaration(token, declarations, true);
+            var d = this.parseDeclaration(token, declarations, true, true, aOwner);
             s += ((d && declarations.length) ? " " : "") + d;
           }
           token = this.getToken(true, false);
@@ -2374,7 +2498,11 @@
       rule.parsedCssText = s;
       rule.declarations = declarations;
       rule.mSelectorText = selector;
-      aCssRules.cssRules.push(rule);
+      if (aIsInsideMediaRule)
+        rule.parentRule = aOwner;
+      else
+        rule.parentStyleSheet = aOwner;
+      aOwner.cssRules.push(rule);
       return s;
     }
     return "";
@@ -2615,14 +2743,21 @@
       }
 
       else if (token.isAtRule()) {
-        if (token.isAtRule("@import")) {
+        if (token.isAtRule("@variables")) {
+          if (!foundImportRules && !foundStyleRules)
+            this.parseVariablesRule(token, sheet);
+          else
+            this.addUnknownAtRule(sheet, token.value);
+        }
+        else if (token.isAtRule("@import")) {
           // @import rules MUST occur before all style and namespace
           // rules
           if (!foundStyleRules && !foundNameSpaceRules)
             foundImportRules = this.parseImportRule(token, sheet);
           else
             this.addUnknownAtRule(sheet, token.value);
-        } else if (token.isAtRule("@namespace")) {
+        }
+        else if (token.isAtRule("@namespace")) {
           // @namespace rules MUST occur before all style rule and
           // after all @import rules
           if (!foundStyleRules)
@@ -2630,27 +2765,32 @@
                 sheet);
           else
             this.addUnknownAtRule(sheet, token.value);
-        } else if (token.isAtRule("@font-face")) {
+        }
+        else if (token.isAtRule("@font-face")) {
           if (this.parseFontFaceRule(token, sheet))
             foundStyleRules = true;
           else
             this.addUnknownAtRule(sheet, token.value);
-        } else if (token.isAtRule("@page")) {
+        }
+        else if (token.isAtRule("@page")) {
           if (this.parsePageRule(token, sheet))
             foundStyleRules = true;
           else
             this.addUnknownAtRule(sheet, token.value);
-        } else if (token.isAtRule("@media")) {
+        }
+        else if (token.isAtRule("@media")) {
           if (this.parseMediaRule(token, sheet))
             foundStyleRules = true;
           else
             this.addUnknownAtRule(sheet, token.value);
         }
+        else
+          this.addUnknownAtRule(sheet, token.value);
       }
 
       else // plain style rules
       {
-        this.parseStyleRule(token, sheet);
+        this.parseStyleRule(token, sheet, false);
         foundStyleRules = true;
       }
       token = this.getToken(false);
@@ -2798,6 +2938,7 @@
 const kJscsspMEDIA_RULE     = 4;
 const kJscsspFONT_FACE_RULE = 5;
 const kJscsspPAGE_RULE      = 6;
+const kJscsspVARIABLES_RULE = 7;
 
 const kJscsspNAMESPACE_RULE = 100;
 const kJscsspCOMMENT        = 101;
@@ -2810,6 +2951,7 @@
 function jscsspStylesheet()
 {
   this.cssRules = [];
+  this.variables = {};
 }
 
 jscsspStylesheet.prototype = {
@@ -2836,6 +2978,24 @@
     for (var i = 0; i < this.cssRules.length; i++)
       rv += this.cssRules[i].cssText + "\n";
     return rv;
+  },
+
+  resolveVariables: function(aMedium) {
+    for (var i = 0; i < this.cssRules.length; i++)
+    {
+      var rule = this.cssRules[i];
+      if (rule.type == kJscsspSTYLE_RULE || rule.type == kJscsspIMPORT_RULE)
+        break;
+      else if (rule.type == kJscsspVARIABLES_RULE &&
+               (!rule.media.length || rule.media.indexOf(aMedium) != -1)) {
+        for (var j = 0; j < rule.declarations.length; j++) {
+          var valueText = "";
+          for (var k = 0; k < rule.declarations[j].values.length; k++)
+            valueText += (k ? " " : "") + rule.declarations[j].values[k].value;
+          this.variables[rule.declarations[j].property] = valueText;
+        }
+      }
+    }
   }
 };
 
@@ -2846,6 +3006,8 @@
   this.type = kJscsspCHARSET_RULE;
   this.encoding = null;
   this.parsedCssText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspCharsetRule.prototype = {
@@ -2876,6 +3038,8 @@
 {
   this.type = kJscsspUNKNOWN_RULE;
   this.parsedCssText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspErrorRule.prototype = {
@@ -2890,6 +3054,8 @@
 {
   this.type = kJscsspCOMMENT;
   this.parsedCssText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspComment.prototype = {
@@ -2913,6 +3079,8 @@
 {
   this.type = kJscsspWHITE_SPACE;
   this.parsedCssText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 /* kJscsspIMPORT_RULE */
@@ -2923,6 +3091,8 @@
   this.parsedCssText = null;
   this.href = null;
   this.media = []; 
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspImportRule.prototype = {
@@ -2958,6 +3128,8 @@
   this.parsedCssText = null;
   this.prefix = null;
   this.url = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspNamespaceRule.prototype = {
@@ -2994,6 +3166,8 @@
   this.valueText = null;
   this.priority = null;
   this.parsedCssText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspDeclaration.prototype = {
@@ -3004,18 +3178,21 @@
   },
 
   get cssText() {
-    return this.property + ": "
-                    + this.values.join(
-                         (this.property in this.kCOMMA_SEPARATED) ? ", " : " ")
-                    + (this.priority ? " !important" : "")
-                    + ";";
+    var rv = this.property + ": ";
+    var separator = (this.property in this.kCOMMA_SEPARATED) ? ", " : " ";
+    for (var i = 0; i < this.values.length; i++)
+      if (this.values[i].cssText != null)
+        rv += (i ? separator : "") + this.values[i].cssText;
+      else
+        return null;
+    return rv + (this.priority ? " !important" : "") + ";";
   },
 
   set cssText(val) {
     var declarations = [];
     var parser = new CSSParser(val);
     var token = parser.getToken(true, true);
-    if (parser.parseDeclaration(token, declarations, true)
+    if (parser.parseDeclaration(token, declarations, true, true, null)
         && declarations.length
         && declarations[0].type == kJscsspSTYLE_DECLARATION) {
       var newDecl = declarations.cssRules[0];
@@ -3036,6 +3213,8 @@
   this.type = kJscsspFONT_FACE_RULE;
   this.parsedCssText = null;
   this.descriptors = [];
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspFontFaceRule.prototype = {
@@ -3073,6 +3252,8 @@
   this.parsedCssText = null;
   this.cssRules = [];
   this.media = [];
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspMediaRule.prototype = {
@@ -3111,6 +3292,8 @@
   this.parsedCssText = null;
   this.declarations = []
   this.mSelectorText = null;
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspStyleRule.prototype = {
@@ -3118,8 +3301,11 @@
     var rv = this.mSelectorText + " {\n";
     var preservedGTABS = gTABS;
     gTABS += "  ";
-    for (var i = 0; i < this.declarations.length; i++)
-      rv += gTABS + this.declarations[i].cssText + "\n";
+    for (var i = 0; i < this.declarations.length; i++) {
+      var declText = this.declarations[i].cssText;
+      if (declText)
+        rv += gTABS + this.declarations[i].cssText + "\n";
+    }
     gTABS = preservedGTABS;
     return rv + gTABS + "}";
   },
@@ -3129,7 +3315,7 @@
     var parser = new CSSParser(val);
     var token = parser.getToken(true, true);
     if (!token.isNotNull()) {
-      if (parser.parseStyleRule(token, sheet)) {
+      if (parser.parseStyleRule(token, sheet, false)) {
         var newRule = sheet.cssRules[0];
         this.mSelectorText = newRule.mSelectorText;
         this.declarations = newRule.declarations;
@@ -3166,6 +3352,8 @@
   this.parsedCssText = null;
   this.pageSelector = null;
   this.declarations = [];
+  this.parentStyleSheet = null;
+  this.parentRule = null;
 }
 
 jscsspPageRule.prototype = {
@@ -3197,3 +3385,79 @@
   }
 };
 
+/* kJscsspVARIABLES_RULE */
+
+function jscsspVariablesRule()
+{
+  this.type = kJscsspVARIABLES_RULE;
+  this.parsedCssText = null;
+  this.declarations = [];
+  this.parentStyleSheet = null;
+  this.parentRule = null;
+  this.media = null;
+}
+
+jscsspVariablesRule.prototype = {
+  get cssText() {
+    var rv = gTABS + "@variables " +
+                     (this.media.length ? this.media.join(", ") + " " : "") +
+                     "{\n";
+    var preservedGTABS = gTABS;
+    gTABS += "  ";
+    for (var i = 0; i < this.declarations.length; i++)
+      rv += gTABS + this.declarations[i].cssText + "\n";
+    gTABS = preservedGTABS;
+    return rv + gTABS + "}";
+  },
+
+  set cssText(val) {
+    var sheet = {cssRules: []};
+    var parser = new CSSParser(val);
+    var token = parser.getToken(true, true);
+    if (token.isAtRule("@variables")) {
+      if (parser.parseVariablesRule(token, sheet)) {
+        var newRule = sheet.cssRules[0];
+        this.declarations = newRule.declarations;
+        this.parsedCssText = newRule.parsedCssText;
+        return;
+      }
+    }
+    throw DOMException.SYNTAX_ERR;
+  }
+};
+
+const kJscsspINHERIT_VALUE = 0;
+const kJscsspPRIMITIVE_VALUE = 1;
+const kJscsspVARIABLE_VALUE = 4;
+
+function jscsspVariable(aType, aSheet)
+{
+  this.value = "";
+  this.type = aType;
+  this.name  = null;
+  this.parentRule = null;
+  this.parentStyleSheet = aSheet;
+}
+
+jscsspVariable.prototype = {
+  get cssText() {
+    if (this.type == kJscsspVARIABLE_VALUE)
+      return this.resolveVariable(this.name, this.parentRule, this.parentStyleSheet);
+    else
+      return this.value;
+  },
+
+  set cssText(val) {
+    if (this.type == kJscsspVARIABLE_VALUE)
+      throw DOMException.SYNTAX_ERR;
+    else
+      this.value = val;
+  },
+
+  resolveVariable: function(aName, aRule, aSheet)
+  {
+    if (aName.toLowerCase() in aSheet.variables)
+      return aSheet.variables[aName.toLowerCase()];
+    return null;
+  }
+};
--- a/demo.xhtml	Wed Mar 17 05:14:11 2010 -0500
+++ b/demo.xhtml	Mon Mar 22 10:31:48 2010 -0500
@@ -25,6 +25,11 @@
       var parser = new CSSParser();
       var sheet = parser.parse(ss, false, true);
       if (sheet) {
+		    if (sheet) {
+          var medium = document.getElementById("medium").value;
+		      sheet.resolveVariables(medium);
+        }
+
 	      var serialization = document.getElementById("serialization");
 	      serialization.textContent = sheet.cssText;
       }
@@ -39,23 +44,34 @@
   <p>Warning, it's a work in progress. One detail though: no regexps for parsing inside...</p>
   <p>The current document has the following stylesheet attached:</p>
   <textarea style="margin: auto; width: 100%" rows="15" id="source">/* JSCSSP test */
-pre { background-color: #FFFFCC; 
-      /* shorthands */ border: 2px silver solid; padding: 1em;
-      /* at least one of the 2 following properties not recognized by your browser
-         but parsed and preserved by JSCSSP */
-      -moz-border-radius: 11px; -webkit-border-radius:11px
-    }
[email protected] screen { myMargin: 10px }
+h1 {
+  margin-top: var(myMargin);
+  margin-right: var(myMargin);
+  -moz-border-radius-topright: var(myMargin);
+}
 
-body { font-family: sans-serif; margin: 1em }
-h1 { background-color: silver }
-h2 { background-color: lightgrey }
-h1, h2 { margin: 0px; padding: 4px }
+/* shorthands */
+#myP {
+  border: thin groove silver;
+}
 
 /* escapes */
-
 .f\00C4 oo { p\roperty: value; content: "foo \
 bar" }</textarea>
-  <p><button onclick="doit()">Parse and Reserialize below</button></p>
+  <p>
+    <button onclick="doit()">Parse and reserialize below, resolving CSS variables for this medium: </button>
+    <select id="medium">
+      <option>screen</option>
+      <option>print</option>
+      <option>braille</option>
+      <option>embossed</option>
+      <option>handheld</option>
+      <option>projection</option>
+      <option>speech</option>
+      <option>tty</option>
+      <option>tv</option>
+    </select></p>
   <p><a href="http://sources.disruptive-innovations.com/jscssp/">JSCSSP</a> is called to parse the
      stylesheet and here's how JSCSSP reserializes it:</p>
   <pre id="serialization"/>