First version of clean-css.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 7 Feb 2011 16:08:11 +0000 (17:08 +0100)
committerJakub Pawlowicz <jakub@goalsmashers.com>
Mon, 28 Feb 2011 18:53:41 +0000 (19:53 +0100)
* Implemented all standard transformations (cleaning up whitespace, semicolons, long color declarations, empty elements, etc...).
* Added tests for all transformations.
* Added tests cleaning up some standard libraries (reset.css, 960.gs, blueprint.css).

17 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
bin/cleancss [new file with mode: 0755]
index.js [new file with mode: 0644]
lib/clean.js [new file with mode: 0644]
lib/purify.js [deleted file]
package.json [new file with mode: 0644]
test/batch-test.js [new file with mode: 0644]
test/data/960-min.css [new file with mode: 0755]
test/data/960.css [new file with mode: 0755]
test/data/blueprint-min.css [new file with mode: 0644]
test/data/blueprint.css [new file with mode: 0644]
test/data/reset-min.css [new file with mode: 0644]
test/data/reset.css [new file with mode: 0644]
test/purifier-test.js [deleted file]
test/unit-test.js [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5ca0973
--- /dev/null
@@ -0,0 +1,2 @@
+.DS_Store
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..b60e628
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+TEST_DIR = test
+
+all: test
+  
+test:
+       @@echo "Running all tests via vows"
+       @@vows ${TEST_DIR}/*-test.js
+  
+.PHONY: all test
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8a6d038
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+## What is clean-css? ##
+
+Clean-css is a node.js library for minifying CSS files. It does the same job as YUI Compressor's CSS minifier but much faster thanks to speed of node.js V8 engine.
+
+## Usage ##
+
+### How to install clean-css? ###
+
+    npm install clean-css
+
+
+### How to use clean-css? ###
+
+You can minify one file **public.css** into **public-min.css** via:
+
+    cleancss -o public-min.css public.css
+    
+To minify the same **public.css** into standard output skip the -o parameter:
+
+    cleancss public.css
+
+Or more likely you would like to do something like this:
+
+    cat one.css two.css three.css | cleancss -o merged-and-minified.css
+    
+Or even gzip it at once:
+
+    cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz
+
+### How to use clean-css programatically ###
+
+    var CleanCSS = require('clean-css').CleanCSS;
+    
+    var source = "a{font-weight:bold}";
+    var minimized = CleanCSS.process(source);
+
+### How to run clean-css tests? ###
+
+You need vows testing framework (npm install vows) then simply run:
+
+    make test
diff --git a/bin/cleancss b/bin/cleancss
new file mode 100755 (executable)
index 0000000..3d641ac
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env node
+
+global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
+var CleanCSS = require('clean-css').CleanCSS,
+  fs = require('fs');
+
+var options = {
+  source: null,
+  target: null
+};
+
+var args = process.argv.slice(2);
+out: while (args.length > 0) {
+  var p = args.shift();
+  switch(p) {
+    case '-o':
+      options.target = args.shift();
+      break;
+    default:
+      options.source = args.shift();
+  }
+}
+
+if (options.source) {
+  fs.readFile(options.source, 'utf8', function(error, text) {
+    if (error) throw error;
+    output(CleanCSS.process(text));
+  });
+} else {
+  var stdin = process.openStdin();
+  stdin.setEncoding('utf-8');
+  var text = '';
+  stdin.on('data', function(chunk) { text += chunk; });
+  stdin.on('end', function() { output(CleanCSS.process(text)); });
+}
+
+function output(cleaned) {
+  var out;
+  if (options.target) {
+    out = fs.createWriteStream(options.target, { flags: 'w', encoding: 'utf-8', mode: 0644 });
+  } else {
+    out = process.stdout;
+  }
+  
+  out.write(cleaned);
+  out.end();
+};
diff --git a/index.js b/index.js
new file mode 100644 (file)
index 0000000..1103bd3
--- /dev/null
+++ b/index.js
@@ -0,0 +1 @@
+exports.CleanCSS = require("./lib/clean");
\ No newline at end of file
diff --git a/lib/clean.js b/lib/clean.js
new file mode 100644 (file)
index 0000000..90c777d
--- /dev/null
@@ -0,0 +1,123 @@
+var sys = require('sys');
+
+var CleanCSS = {
+  colors: {
+    white: '#fff',
+    black: '#000',
+    fuchsia: '#f0f',
+    yellow: '#ff0'
+  },
+  
+  process: function(data) {
+    var specialComments = [],
+      contentBlocks = [];
+
+    // replace function
+    var replace = function(pattern, replacement) {
+      var start = new Date().getTime();
+      data = data.replace(pattern, replacement);
+      var end = new Date().getTime();
+      
+      if (false && end > start) {
+        sys.print(pattern);
+        sys.print(' -> ');
+        sys.print(end - start);
+        sys.print(' milliseconds\n');
+      }
+    };
+    
+    // strip comments one by one
+    for (var end = 0; end < data.length; ) {
+      var start = data.indexOf('/*', end);
+      end = data.indexOf('*/', start);
+      if (start == -1 || end == -1) break;
+      
+      if (data[start + 2] == '!') {
+        // in case of special comments, replace them with a placeholder
+        specialComments.push(data.substring(start, end + 2));
+        data = data.substring(0, start) + '__CSSCOMMENT__' + data.substring(end + 2);
+      } else {
+        data = data.substring(0, start) + data.substring(end + 2);
+      }
+      end = start;
+    }
+    
+    // replace content: with a placeholder
+    for (var end = 0; end < data.length; ) {
+      var start = data.indexOf('content', end);
+      if (start == -1) break;
+      
+      var wrapper = /[^ :]/.exec(data.substring(start + 7))[0];
+      if (/['"]/.test(wrapper) == false) {
+        end = start + 7;
+        continue;
+      }
+
+      var firstIndex = data.indexOf(wrapper, start);
+      var lastIndex = data.indexOf(wrapper, firstIndex + 1);
+      
+      contentBlocks.push(data.substring(firstIndex, lastIndex + 1));
+      data = data.substring(0, firstIndex) + '__CSSCONTENT__' + data.substring(lastIndex + 1);
+      end = lastIndex + 1;
+    }
+    
+    replace(/;\s*;+/g, ';') // whitespace between semicolons & multiple semicolons
+    replace(/\n/g, '') // line breaks
+    replace(/\s+/g, ' ') // multiple whitespace
+    replace(/[ ]?,[ ]?/g, ',') // space with a comma
+    replace(/progid:[^(]+\(([^\)]+)/, function(match, contents) { // restore spaces inside IE filters (IE 7 issue)
+      return match.replace(/,/g, ', ');
+    })
+    replace(/ ([+~>]) /g, '$1') // replace spaces around selectors
+    replace(/\{([^}]+)\}/g, function(match, contents) { // whitespace inside content
+      return '{' + contents.trim().replace(/(\s*)([;:=\s])(\s*)/g, '$2') + '}';
+    })
+    replace(/;}/g, '}') // trailing semicolons
+    replace(/rgb\s*\(([^\)]+)\)/g, function(match, color) { // rgb to hex colors
+      var parts = color.split(',');
+      var encoded = '#';
+      for (var i = 0; i < 3; i++) {
+        var asHex = parseInt(parts[i], 10).toString(16);
+        encoded += asHex.length == 1 ? '0' + asHex : asHex;
+      }
+      return encoded;
+    })
+    replace(/([^"'=\s])\s*#([0-9a-f]{6})/gi, function(match, prefix, color) { // long hex to short hex
+      if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
+        return prefix + '#' + color[0] + color[2] + color[4];
+      else
+        return prefix + '#' + color;
+    })
+    replace(/(color|background):(\w+)/g, function(match, property, colorName) { // replace standard colors with hex values (only if color name is longer then hex value)
+      if (CleanCSS.colors[colorName]) return property + ':' + CleanCSS.colors[colorName];
+      else return match;
+    })
+    replace(/color:#f00/g, 'color:red') // replace #f00 with red as it's shorter
+    replace(/font\-weight:(\w+)/g, function(match, weight) { // replace font weight with numerical value
+      if (weight == 'normal') return 'font-weight:400';
+      else if (weight == 'bold') return 'font-weight:700';
+      else return match;
+    })
+    replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)/g, function(match, contents) { // IE shorter filters
+      return contents.toLowerCase();
+    })
+    replace(/(\s|:)0(px|em|ex|cm|mm|in|pt|pc|%)/g, '$1' + '0') // zero + unit to zero
+    replace(/(border|border-top|border-right|border-bottom|border-left|outline|background):none/g, '$1:0') // none to 0
+    replace(/0 0 0 0/g, '0') // multiple zeros into one
+    replace(/([: ,=\-])0\.(\d)/g, '$1.$2')
+    replace(/[^\}]+{(;)*}/g, '') // empty elements
+    if (data.indexOf('charset') > 0) replace(/(.+)(@charset [^;]+;)/, '$2$1') // move first charset to the beginning
+    replace(/(.)(@charset [^;]+;)/g, '$1') // remove all extra charsets that are not at the beginning
+    replace(/\*([\.#:\[])/g, '$1') // remove universal selector when not needed (*#id, *.class etc)
+    replace(/ {/g, '{') // whitespace before definition
+    replace(/\} /g, '}') // whitespace after definition
+    
+    // Get the special comments && content back
+    replace(/__CSSCOMMENT__/g, function() { return specialComments.shift(); });
+    replace(/__CSSCONTENT__/g, function() { return contentBlocks.shift(); });
+    
+    return data.trim() // trim spaces at beginning and end
+  }
+};
+
+exports.CleanCSS = CleanCSS;
\ No newline at end of file
diff --git a/lib/purify.js b/lib/purify.js
deleted file mode 100644 (file)
index e40ba5a..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-var Purify = {
-  process: function(data) {
-    // strip comments one by one
-    for (var end = 0; end < data.length; ) {
-      var start = data.indexOf('/*', end);
-      if (data[start + 2] == '!') { // skip special comments: /*!...*/
-        end = start + 1;
-        continue;
-      }
-      
-      end = data.indexOf('*/', start);
-      if (start == -1 || end == -1) break;
-      
-      data = data.substring(0, start) + data.substring(end + 2);
-      end = start;
-    }
-    
-    return data
-      .replace(/;\s*;/g, ';;') // whitespace between semicolons
-      .replace(/;+/g, ';') // multiple semicolons
-      .replace(/,[ ]+/g, ',') // comma
-      .replace(/\s+/g, ' ') // whitespace
-      .replace(/\{([^}]+)\}/g, function(match, contents) { // whitespace inside content
-        return '{' + contents.trim().replace(/(\s*)([;:=\s])(\s*)/g, '$2') + '}';
-      })
-      .replace(/;}/g, '}') // trailing semicolons
-      .replace(/rgb\s*\(([^\)]+)\)/g, function(match, color) { // rgb to hex colors
-        var parts = color.split(',');
-        var encoded = '#';
-        for (var i = 0; i < 3; i++) {
-          var asHex = parseInt(parts[i], 10).toString(16);
-          encoded += asHex.length == 1 ? '0' + asHex : asHex;
-        }
-        return encoded;
-      })
-      .replace(/([^"'=\s])\s*#([0-9a-f]{6})/gi, function(match, prefix, color) { // long hex to short hex
-        if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
-          return prefix + '#' + color[0] + color[2] + color[4];
-        else
-          return prefix + '#' + color;
-      })
-      .replace(/progid:DXImageTransform\.Microsoft\.Alpha/g, 'alpha') // IE alpha filter
-      .replace(/(\s|:)0(px|em|ex|cm|mm|in|pt|pc|%)/g, '$1' + '0') // zero + unit to zero
-      .replace(/none/g, '0') // none to 0
-      .replace(/( 0){1,4}/g, '') // multiple zeros into one
-      .replace(/([: ,])0\.(\d)+/g, '$1.$2')
-      .replace(/[^\}]+{(;)*}/g, '') // empty elements
-      .replace(/(.+)(@charset [^;]+;)/, '$2$1')
-      .replace(/(.+)(@charset [^;]+;)/g, '$1')
-      .replace(/ {/g, '{') // whitespace before definition
-      .replace(/\} /g, '}') // whitespace after definition
-  }
-};
-
-exports.Purify = Purify;
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..4440d44
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "name": "clean-css",
+  "author": "Jakub Pawlowicz <contact@jakubpawlowicz.com> (http://twitter.com/JakubPawlowicz)",
+  "description": "A well-tested CSS minifier",
+  "keywords": ["css", "minifier"],
+  "homepage": "http://github.com/jakubpawlowicz/clean-css",
+  "repository": {
+    "type" : "git",
+    "url" : "http://github.com/jakubpawlowicz/clean-css.git"
+  },
+  "version": "0.1.0",
+  "main": "index.js",
+  "bin": {
+    "purifycss": "./bin/cleancss"
+  }
+}
\ No newline at end of file
diff --git a/test/batch-test.js b/test/batch-test.js
new file mode 100644 (file)
index 0000000..3e9f3e1
--- /dev/null
@@ -0,0 +1,31 @@
+var vows = require('vows'),
+  fs = require('fs'),
+  assert = require('assert');
+
+var CleanCSS = require('../lib/clean').CleanCSS;
+
+var batchContexts = function() {
+  var context = {};
+  fs.readdirSync('./test/data/').forEach(function(filename) {
+    if (/min.css$/.exec(filename)) return;
+    var testName = filename.split('.')[0];
+    
+    context[testName] = {
+      topic: function() {
+        return {
+          plain: fs.readFileSync('./test/data/' + testName + '.css').toString('utf-8'),
+          minimized: fs.readFileSync('./test/data/' + testName + '-min.css').toString('utf-8').replace(/\n/g, '')
+        };
+      }
+    }
+    context[testName]['minimizing ' + testName + '.css'] = function(data) {
+      assert.equal(CleanCSS.process(data.plain), data.minimized)
+    };
+  });
+
+  return context;
+};
+
+vows.describe('clean-batch')
+  .addBatch(batchContexts())
+  .export(module);
\ No newline at end of file
diff --git a/test/data/960-min.css b/test/data/960-min.css
new file mode 100755 (executable)
index 0000000..c064dd4
--- /dev/null
@@ -0,0 +1 @@
+.container_24{margin-right:auto;margin-left:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16,.grid_17,.grid_18,.grid_19,.grid_20,.grid_21,.grid_22,.grid_23,.grid_24{display:inline;float:right;margin-right:5px;margin-left:5px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15,.push_16,.pull_16,.push_17,.pull_17,.push_18,.pull_18,.push_19,.pull_19,.push_20,.pull_20,.push_21,.pull_21,.push_22,.pull_22,.push_23,.pull_23{position:relative}.alpha{margin-right:0}.omega{margin-left:0}.container_24 .grid_1{width:30px}.container_24 .grid_2{width:70px}.container_24 .grid_3{width:110px}.container_24 .grid_4{width:150px}.container_24 .grid_5{width:190px}.container_24 .grid_6{width:230px}.container_24 .grid_7{width:270px}.container_24 .grid_8{width:310px}.container_24 .grid_9{width:350px}.container_24 .grid_10{width:390px}.container_24 .grid_11{width:430px}.container_24 .grid_12{width:470px}.container_24 .grid_13{width:510px}.container_24 .grid_14{width:550px}.container_24 .grid_15{width:590px}.container_24 .grid_16{width:630px}.container_24 .grid_17{width:670px}.container_24 .grid_18{width:710px}.container_24 .grid_19{width:750px}.container_24 .grid_20{width:790px}.container_24 .grid_21{width:830px}.container_24 .grid_22{width:870px}.container_24 .grid_23{width:910px}.container_24 .grid_24{width:950px}.container_24 .prefix_1{padding-right:40px}.container_24 .prefix_2{padding-right:80px}.container_24 .prefix_3{padding-right:120px}.container_24 .prefix_4{padding-right:160px}.container_24 .prefix_5{padding-right:200px}.container_24 .prefix_6{padding-right:240px}.container_24 .prefix_7{padding-right:280px}.container_24 .prefix_8{padding-right:320px}.container_24 .prefix_9{padding-right:360px}.container_24 .prefix_10{padding-right:400px}.container_24 .prefix_11{padding-right:440px}.container_24 .prefix_12{padding-right:480px}.container_24 .prefix_13{padding-right:520px}.container_24 .prefix_14{padding-right:560px}.container_24 .prefix_15{padding-right:600px}.container_24 .prefix_16{padding-right:640px}.container_24 .prefix_17{padding-right:680px}.container_24 .prefix_18{padding-right:720px}.container_24 .prefix_19{padding-right:760px}.container_24 .prefix_20{padding-right:800px}.container_24 .prefix_21{padding-right:840px}.container_24 .prefix_22{padding-right:880px}.container_24 .prefix_23{padding-right:920px}.container_24 .suffix_1{padding-left:40px}.container_24 .suffix_2{padding-left:80px}.container_24 .suffix_3{padding-left:120px}.container_24 .suffix_4{padding-left:160px}.container_24 .suffix_5{padding-left:200px}.container_24 .suffix_6{padding-left:240px}.container_24 .suffix_7{padding-left:280px}.container_24 .suffix_8{padding-left:320px}.container_24 .suffix_9{padding-left:360px}.container_24 .suffix_10{padding-left:400px}.container_24 .suffix_11{padding-left:440px}.container_24 .suffix_12{padding-left:480px}.container_24 .suffix_13{padding-left:520px}.container_24 .suffix_14{padding-left:560px}.container_24 .suffix_15{padding-left:600px}.container_24 .suffix_16{padding-left:640px}.container_24 .suffix_17{padding-left:680px}.container_24 .suffix_18{padding-left:720px}.container_24 .suffix_19{padding-left:760px}.container_24 .suffix_20{padding-left:800px}.container_24 .suffix_21{padding-left:840px}.container_24 .suffix_22{padding-left:880px}.container_24 .suffix_23{padding-left:920px}.container_24 .push_1{right:40px}.container_24 .push_2{right:80px}.container_24 .push_3{right:120px}.container_24 .push_4{right:160px}.container_24 .push_5{right:200px}.container_24 .push_6{right:240px}.container_24 .push_7{right:280px}.container_24 .push_8{right:320px}.container_24 .push_9{right:360px}.container_24 .push_10{right:400px}.container_24 .push_11{right:440px}.container_24 .push_12{right:480px}.container_24 .push_13{right:520px}.container_24 .push_14{right:560px}.container_24 .push_15{right:600px}.container_24 .push_16{right:640px}.container_24 .push_17{right:680px}.container_24 .push_18{right:720px}.container_24 .push_19{right:760px}.container_24 .push_20{right:800px}.container_24 .push_21{right:840px}.container_24 .push_22{right:880px}.container_24 .push_23{right:920px}.container_24 .pull_1{right:-40px}.container_24 .pull_2{right:-80px}.container_24 .pull_3{right:-120px}.container_24 .pull_4{right:-160px}.container_24 .pull_5{right:-200px}.container_24 .pull_6{right:-240px}.container_24 .pull_7{right:-280px}.container_24 .pull_8{right:-320px}.container_24 .pull_9{right:-360px}.container_24 .pull_10{right:-400px}.container_24 .pull_11{right:-440px}.container_24 .pull_12{right:-480px}.container_24 .pull_13{right:-520px}.container_24 .pull_14{right:-560px}.container_24 .pull_15{right:-600px}.container_24 .pull_16{right:-640px}.container_24 .pull_17{right:-680px}.container_24 .pull_18{right:-720px}.container_24 .pull_19{right:-760px}.container_24 .pull_20{right:-800px}.container_24 .pull_21{right:-840px}.container_24 .pull_22{right:-880px}.container_24 .pull_23{right:-920px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:before,.clearfix:after{content:'\0020';display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both}.clearfix{zoom:1}
\ No newline at end of file
diff --git a/test/data/960.css b/test/data/960.css
new file mode 100755 (executable)
index 0000000..efcf770
--- /dev/null
@@ -0,0 +1,602 @@
+/*
+       960 Grid System ~ Core CSS.
+       Learn more ~ http://960.gs/
+
+       Licensed under GPL and MIT.
+*/
+
+/* `Container >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+.container_24 {
+       margin-right: auto;
+       margin-left: auto;
+       width: 960px;
+}
+
+/* `Grid >> Global
+----------------------------------------------------------------------------------------------------*/
+
+.grid_1,
+.grid_2,
+.grid_3,
+.grid_4,
+.grid_5,
+.grid_6,
+.grid_7,
+.grid_8,
+.grid_9,
+.grid_10,
+.grid_11,
+.grid_12,
+.grid_13,
+.grid_14,
+.grid_15,
+.grid_16,
+.grid_17,
+.grid_18,
+.grid_19,
+.grid_20,
+.grid_21,
+.grid_22,
+.grid_23,
+.grid_24 {
+       display: inline;
+       float: right;
+       margin-right: 5px;
+       margin-left: 5px;
+}
+
+.push_1, .pull_1,
+.push_2, .pull_2,
+.push_3, .pull_3,
+.push_4, .pull_4,
+.push_5, .pull_5,
+.push_6, .pull_6,
+.push_7, .pull_7,
+.push_8, .pull_8,
+.push_9, .pull_9,
+.push_10, .pull_10,
+.push_11, .pull_11,
+.push_12, .pull_12,
+.push_13, .pull_13,
+.push_14, .pull_14,
+.push_15, .pull_15,
+.push_16, .pull_16,
+.push_17, .pull_17,
+.push_18, .pull_18,
+.push_19, .pull_19,
+.push_20, .pull_20,
+.push_21, .pull_21,
+.push_22, .pull_22,
+.push_23, .pull_23 {
+       position: relative;
+}
+
+/* `Grid >> Children (Alpha ~ First, Omega ~ Last)
+----------------------------------------------------------------------------------------------------*/
+
+.alpha {
+       margin-right: 0;
+}
+
+.omega {
+       margin-left: 0;
+}
+
+/* `Grid >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .grid_1 {
+       width: 30px;
+}
+
+.container_24 .grid_2 {
+       width: 70px;
+}
+
+.container_24 .grid_3 {
+       width: 110px;
+}
+
+.container_24 .grid_4 {
+       width: 150px;
+}
+
+.container_24 .grid_5 {
+       width: 190px;
+}
+
+.container_24 .grid_6 {
+       width: 230px;
+}
+
+.container_24 .grid_7 {
+       width: 270px;
+}
+
+.container_24 .grid_8 {
+       width: 310px;
+}
+
+.container_24 .grid_9 {
+       width: 350px;
+}
+
+.container_24 .grid_10 {
+       width: 390px;
+}
+
+.container_24 .grid_11 {
+       width: 430px;
+}
+
+.container_24 .grid_12 {
+       width: 470px;
+}
+
+.container_24 .grid_13 {
+       width: 510px;
+}
+
+.container_24 .grid_14 {
+       width: 550px;
+}
+
+.container_24 .grid_15 {
+       width: 590px;
+}
+
+.container_24 .grid_16 {
+       width: 630px;
+}
+
+.container_24 .grid_17 {
+       width: 670px;
+}
+
+.container_24 .grid_18 {
+       width: 710px;
+}
+
+.container_24 .grid_19 {
+       width: 750px;
+}
+
+.container_24 .grid_20 {
+       width: 790px;
+}
+
+.container_24 .grid_21 {
+       width: 830px;
+}
+
+.container_24 .grid_22 {
+       width: 870px;
+}
+
+.container_24 .grid_23 {
+       width: 910px;
+}
+
+.container_24 .grid_24 {
+       width: 950px;
+}
+
+/* `Prefix Extra Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .prefix_1 {
+       padding-right: 40px;
+}
+
+.container_24 .prefix_2 {
+       padding-right: 80px;
+}
+
+.container_24 .prefix_3 {
+       padding-right: 120px;
+}
+
+.container_24 .prefix_4 {
+       padding-right: 160px;
+}
+
+.container_24 .prefix_5 {
+       padding-right: 200px;
+}
+
+.container_24 .prefix_6 {
+       padding-right: 240px;
+}
+
+.container_24 .prefix_7 {
+       padding-right: 280px;
+}
+
+.container_24 .prefix_8 {
+       padding-right: 320px;
+}
+
+.container_24 .prefix_9 {
+       padding-right: 360px;
+}
+
+.container_24 .prefix_10 {
+       padding-right: 400px;
+}
+
+.container_24 .prefix_11 {
+       padding-right: 440px;
+}
+
+.container_24 .prefix_12 {
+       padding-right: 480px;
+}
+
+.container_24 .prefix_13 {
+       padding-right: 520px;
+}
+
+.container_24 .prefix_14 {
+       padding-right: 560px;
+}
+
+.container_24 .prefix_15 {
+       padding-right: 600px;
+}
+
+.container_24 .prefix_16 {
+       padding-right: 640px;
+}
+
+.container_24 .prefix_17 {
+       padding-right: 680px;
+}
+
+.container_24 .prefix_18 {
+       padding-right: 720px;
+}
+
+.container_24 .prefix_19 {
+       padding-right: 760px;
+}
+
+.container_24 .prefix_20 {
+       padding-right: 800px;
+}
+
+.container_24 .prefix_21 {
+       padding-right: 840px;
+}
+
+.container_24 .prefix_22 {
+       padding-right: 880px;
+}
+
+.container_24 .prefix_23 {
+       padding-right: 920px;
+}
+
+/* `Suffix Extra Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .suffix_1 {
+       padding-left: 40px;
+}
+
+.container_24 .suffix_2 {
+       padding-left: 80px;
+}
+
+.container_24 .suffix_3 {
+       padding-left: 120px;
+}
+
+.container_24 .suffix_4 {
+       padding-left: 160px;
+}
+
+.container_24 .suffix_5 {
+       padding-left: 200px;
+}
+
+.container_24 .suffix_6 {
+       padding-left: 240px;
+}
+
+.container_24 .suffix_7 {
+       padding-left: 280px;
+}
+
+.container_24 .suffix_8 {
+       padding-left: 320px;
+}
+
+.container_24 .suffix_9 {
+       padding-left: 360px;
+}
+
+.container_24 .suffix_10 {
+       padding-left: 400px;
+}
+
+.container_24 .suffix_11 {
+       padding-left: 440px;
+}
+
+.container_24 .suffix_12 {
+       padding-left: 480px;
+}
+
+.container_24 .suffix_13 {
+       padding-left: 520px;
+}
+
+.container_24 .suffix_14 {
+       padding-left: 560px;
+}
+
+.container_24 .suffix_15 {
+       padding-left: 600px;
+}
+
+.container_24 .suffix_16 {
+       padding-left: 640px;
+}
+
+.container_24 .suffix_17 {
+       padding-left: 680px;
+}
+
+.container_24 .suffix_18 {
+       padding-left: 720px;
+}
+
+.container_24 .suffix_19 {
+       padding-left: 760px;
+}
+
+.container_24 .suffix_20 {
+       padding-left: 800px;
+}
+
+.container_24 .suffix_21 {
+       padding-left: 840px;
+}
+
+.container_24 .suffix_22 {
+       padding-left: 880px;
+}
+
+.container_24 .suffix_23 {
+       padding-left: 920px;
+}
+
+/* `Push Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .push_1 {
+       right: 40px;
+}
+
+.container_24 .push_2 {
+       right: 80px;
+}
+
+.container_24 .push_3 {
+       right: 120px;
+}
+
+.container_24 .push_4 {
+       right: 160px;
+}
+
+.container_24 .push_5 {
+       right: 200px;
+}
+
+.container_24 .push_6 {
+       right: 240px;
+}
+
+.container_24 .push_7 {
+       right: 280px;
+}
+
+.container_24 .push_8 {
+       right: 320px;
+}
+
+.container_24 .push_9 {
+       right: 360px;
+}
+
+.container_24 .push_10 {
+       right: 400px;
+}
+
+.container_24 .push_11 {
+       right: 440px;
+}
+
+.container_24 .push_12 {
+       right: 480px;
+}
+
+.container_24 .push_13 {
+       right: 520px;
+}
+
+.container_24 .push_14 {
+       right: 560px;
+}
+
+.container_24 .push_15 {
+       right: 600px;
+}
+
+.container_24 .push_16 {
+       right: 640px;
+}
+
+.container_24 .push_17 {
+       right: 680px;
+}
+
+.container_24 .push_18 {
+       right: 720px;
+}
+
+.container_24 .push_19 {
+       right: 760px;
+}
+
+.container_24 .push_20 {
+       right: 800px;
+}
+
+.container_24 .push_21 {
+       right: 840px;
+}
+
+.container_24 .push_22 {
+       right: 880px;
+}
+
+.container_24 .push_23 {
+       right: 920px;
+}
+
+/* `Pull Space >> 24 Columns
+----------------------------------------------------------------------------------------------------*/
+
+.container_24 .pull_1 {
+       right: -40px;
+}
+
+.container_24 .pull_2 {
+       right: -80px;
+}
+
+.container_24 .pull_3 {
+       right: -120px;
+}
+
+.container_24 .pull_4 {
+       right: -160px;
+}
+
+.container_24 .pull_5 {
+       right: -200px;
+}
+
+.container_24 .pull_6 {
+       right: -240px;
+}
+
+.container_24 .pull_7 {
+       right: -280px;
+}
+
+.container_24 .pull_8 {
+       right: -320px;
+}
+
+.container_24 .pull_9 {
+       right: -360px;
+}
+
+.container_24 .pull_10 {
+       right: -400px;
+}
+
+.container_24 .pull_11 {
+       right: -440px;
+}
+
+.container_24 .pull_12 {
+       right: -480px;
+}
+
+.container_24 .pull_13 {
+       right: -520px;
+}
+
+.container_24 .pull_14 {
+       right: -560px;
+}
+
+.container_24 .pull_15 {
+       right: -600px;
+}
+
+.container_24 .pull_16 {
+       right: -640px;
+}
+
+.container_24 .pull_17 {
+       right: -680px;
+}
+
+.container_24 .pull_18 {
+       right: -720px;
+}
+
+.container_24 .pull_19 {
+       right: -760px;
+}
+
+.container_24 .pull_20 {
+       right: -800px;
+}
+
+.container_24 .pull_21 {
+       right: -840px;
+}
+
+.container_24 .pull_22 {
+       right: -880px;
+}
+
+.container_24 .pull_23 {
+       right: -920px;
+}
+
+/* `Clear Floated Elements
+----------------------------------------------------------------------------------------------------*/
+
+/* http://sonspring.com/journal/clearing-floats */
+
+.clear {
+       clear: both;
+       display: block;
+       overflow: hidden;
+       visibility: hidden;
+       width: 0;
+       height: 0;
+}
+
+/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
+
+.clearfix:before,
+.clearfix:after {
+       content: '\0020';
+       display: block;
+       overflow: hidden;
+       visibility: hidden;
+       width: 0;
+       height: 0;
+}
+
+.clearfix:after {
+       clear: both;
+}
+
+/*
+       The following zoom:1 rule is specifically for IE6 + IE7.
+       Move to separate stylesheet if invalid CSS is a problem.
+*/
+
+.clearfix {
+       zoom: 1;
+}
\ No newline at end of file
diff --git a/test/data/blueprint-min.css b/test/data/blueprint-min.css
new file mode 100644 (file)
index 0000000..8752547
--- /dev/null
@@ -0,0 +1,245 @@
+html{margin:0;padding:0;border:0}
+body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,code,del,dfn,em,img,q,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,dialog,figure,footer,header,hgroup,nav,section{margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}
+article,aside,dialog,figure,footer,header,hgroup,nav,section{display:block}
+body{line-height:1.5;background:#fff}
+table{border-collapse:separate;border-spacing:0}
+caption,th,td{text-align:left;font-weight:400;float:none !important}
+table,th,td{vertical-align:middle}
+blockquote:before,blockquote:after,q:before,q:after{content:''}
+blockquote,q{quotes:"" ""}
+a img{border:0}
+:focus{outline:0}
+html{font-size:100.01%}
+body{font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue",Arial,Helvetica,sans-serif}
+h1,h2,h3,h4,h5,h6{font-weight:400;color:#111}
+h1{font-size:3em;line-height:1;margin-bottom:.5em}
+h2{font-size:2em;margin-bottom:.75em}
+h3{font-size:1.5em;line-height:1;margin-bottom:1em}
+h4{font-size:1.2em;line-height:1.25;margin-bottom:1.25em}
+h5{font-size:1em;font-weight:700;margin-bottom:1.5em}
+h6{font-size:1em;font-weight:700}
+h1 img,h2 img,h3 img,h4 img,h5 img,h6 img{margin:0}
+p{margin:0 0 1.5em}
+.left{float:left !important}
+p .left{margin:1.5em 1.5em 1.5em 0;padding:0}
+.right{float:right !important}
+p .right{margin:1.5em 0 1.5em 1.5em;padding:0}
+a:focus,a:hover{color:#09f}
+a{color:#06c;text-decoration:underline}
+blockquote{margin:1.5em;color:#666;font-style:italic}
+strong,dfn{font-weight:700}
+em,dfn{font-style:italic}
+sup,sub{line-height:0}
+abbr,acronym{border-bottom:1px dotted #666}
+address{margin:0 0 1.5em;font-style:italic}
+del{color:#666}
+pre{margin:1.5em 0;white-space:pre}
+pre,code,tt{font:1em 'andale mono','lucida console',monospace;line-height:1.5}
+li ul,li ol{margin:0}
+ul,ol{margin:0 1.5em 1.5em 0;padding-left:1.5em}
+ul{list-style-type:disc}
+ol{list-style-type:decimal}
+dl{margin:0 0 1.5em 0}
+dl dt{font-weight:700}
+dd{margin-left:1.5em}
+table{margin-bottom:1.4em;width:100%}
+th{font-weight:700}
+thead th{background:#c3d9ff}
+th,td,caption{padding:4px 10px 4px 5px}
+tbody tr:nth-child(even) td,tbody tr.even td{background:#e5ecf9}
+tfoot{font-style:italic}
+caption{background:#eee}
+.small{font-size:.8em;margin-bottom:1.875em;line-height:1.875em}
+.large{font-size:1.2em;line-height:2.5em;margin-bottom:1.25em}
+.hide{display:none}
+.quiet{color:#666}
+.loud{color:#000}
+.highlight{background:#ff0}
+.added{background:#060;color:#fff}
+.removed{background:#900;color:#fff}
+.first{margin-left:0;padding-left:0}
+.last{margin-right:0;padding-right:0}
+.top{margin-top:0;padding-top:0}
+.bottom{margin-bottom:0;padding-bottom:0}
+label{font-weight:700}
+fieldset{padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc}
+legend{font-weight:700;font-size:1.2em;margin-top:-.2em;margin-bottom:1em}
+fieldset,#IE8#HACK{padding-top:1.4em}
+legend,#IE8#HACK{margin-top:0;margin-bottom:0}
+input[type=text],input[type=password],input.text,input.title,textarea{background-color:#fff;border:1px solid #bbb}
+input[type=text]:focus,input[type=password]:focus,input.text:focus,input.title:focus,textarea:focus{border-color:#666}
+select{background-color:#fff;border-width:1px;border-style:solid}
+input[type=text],input[type=password],input.text,input.title,textarea,select{margin:.5em 0}
+input.text,input.title{width:300px;padding:5px}
+input.title{font-size:1.5em}
+textarea{width:390px;height:250px;padding:5px}
+form.inline{line-height:3}
+form.inline p{margin-bottom:0}
+.error,.alert,.notice,.success,.info{padding:.8em;margin-bottom:1em;border:2px solid #ddd}
+.error,.alert{background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4}
+.notice{background:#fff6bf;color:#514721;border-color:#ffd324}
+.success{background:#e6efc2;color:#264409;border-color:#c6d880}
+.info{background:#d5edf8;color:#205791;border-color:#92cae4}
+.error a,.alert a{color:#8a1f11}
+.notice a{color:#514721}
+.success a{color:#264409}
+.info a{color:#205791}
+.container{width:950px;margin:0 auto}
+.showgrid{background:url(src/grid.png)}
+.column,.span-1,.span-2,.span-3,.span-4,.span-5,.span-6,.span-7,.span-8,.span-9,.span-10,.span-11,.span-12,.span-13,.span-14,.span-15,.span-16,.span-17,.span-18,.span-19,.span-20,.span-21,.span-22,.span-23,.span-24{float:left;margin-right:10px}
+.last{margin-right:0}
+.span-1{width:30px}
+.span-2{width:70px}
+.span-3{width:110px}
+.span-4{width:150px}
+.span-5{width:190px}
+.span-6{width:230px}
+.span-7{width:270px}
+.span-8{width:310px}
+.span-9{width:350px}
+.span-10{width:390px}
+.span-11{width:430px}
+.span-12{width:470px}
+.span-13{width:510px}
+.span-14{width:550px}
+.span-15{width:590px}
+.span-16{width:630px}
+.span-17{width:670px}
+.span-18{width:710px}
+.span-19{width:750px}
+.span-20{width:790px}
+.span-21{width:830px}
+.span-22{width:870px}
+.span-23{width:910px}
+.span-24{width:950px;margin-right:0}
+input.span-1,textarea.span-1,input.span-2,textarea.span-2,input.span-3,textarea.span-3,input.span-4,textarea.span-4,input.span-5,textarea.span-5,input.span-6,textarea.span-6,input.span-7,textarea.span-7,input.span-8,textarea.span-8,input.span-9,textarea.span-9,input.span-10,textarea.span-10,input.span-11,textarea.span-11,input.span-12,textarea.span-12,input.span-13,textarea.span-13,input.span-14,textarea.span-14,input.span-15,textarea.span-15,input.span-16,textarea.span-16,input.span-17,textarea.span-17,input.span-18,textarea.span-18,input.span-19,textarea.span-19,input.span-20,textarea.span-20,input.span-21,textarea.span-21,input.span-22,textarea.span-22,input.span-23,textarea.span-23,input.span-24,textarea.span-24{border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px}
+input.span-1,textarea.span-1{width:18px}
+input.span-2,textarea.span-2{width:58px}
+input.span-3,textarea.span-3{width:98px}
+input.span-4,textarea.span-4{width:138px}
+input.span-5,textarea.span-5{width:178px}
+input.span-6,textarea.span-6{width:218px}
+input.span-7,textarea.span-7{width:258px}
+input.span-8,textarea.span-8{width:298px}
+input.span-9,textarea.span-9{width:338px}
+input.span-10,textarea.span-10{width:378px}
+input.span-11,textarea.span-11{width:418px}
+input.span-12,textarea.span-12{width:458px}
+input.span-13,textarea.span-13{width:498px}
+input.span-14,textarea.span-14{width:538px}
+input.span-15,textarea.span-15{width:578px}
+input.span-16,textarea.span-16{width:618px}
+input.span-17,textarea.span-17{width:658px}
+input.span-18,textarea.span-18{width:698px}
+input.span-19,textarea.span-19{width:738px}
+input.span-20,textarea.span-20{width:778px}
+input.span-21,textarea.span-21{width:818px}
+input.span-22,textarea.span-22{width:858px}
+input.span-23,textarea.span-23{width:898px}
+input.span-24,textarea.span-24{width:938px}
+.append-1{padding-right:40px}
+.append-2{padding-right:80px}
+.append-3{padding-right:120px}
+.append-4{padding-right:160px}
+.append-5{padding-right:200px}
+.append-6{padding-right:240px}
+.append-7{padding-right:280px}
+.append-8{padding-right:320px}
+.append-9{padding-right:360px}
+.append-10{padding-right:400px}
+.append-11{padding-right:440px}
+.append-12{padding-right:480px}
+.append-13{padding-right:520px}
+.append-14{padding-right:560px}
+.append-15{padding-right:600px}
+.append-16{padding-right:640px}
+.append-17{padding-right:680px}
+.append-18{padding-right:720px}
+.append-19{padding-right:760px}
+.append-20{padding-right:800px}
+.append-21{padding-right:840px}
+.append-22{padding-right:880px}
+.append-23{padding-right:920px}
+.prepend-1{padding-left:40px}
+.prepend-2{padding-left:80px}
+.prepend-3{padding-left:120px}
+.prepend-4{padding-left:160px}
+.prepend-5{padding-left:200px}
+.prepend-6{padding-left:240px}
+.prepend-7{padding-left:280px}
+.prepend-8{padding-left:320px}
+.prepend-9{padding-left:360px}
+.prepend-10{padding-left:400px}
+.prepend-11{padding-left:440px}
+.prepend-12{padding-left:480px}
+.prepend-13{padding-left:520px}
+.prepend-14{padding-left:560px}
+.prepend-15{padding-left:600px}
+.prepend-16{padding-left:640px}
+.prepend-17{padding-left:680px}
+.prepend-18{padding-left:720px}
+.prepend-19{padding-left:760px}
+.prepend-20{padding-left:800px}
+.prepend-21{padding-left:840px}
+.prepend-22{padding-left:880px}
+.prepend-23{padding-left:920px}
+.border{padding-right:4px;margin-right:5px;border-right:1px solid #ddd}
+.colborder{padding-right:24px;margin-right:25px;border-right:1px solid #ddd}
+.pull-1{margin-left:-40px}
+.pull-2{margin-left:-80px}
+.pull-3{margin-left:-120px}
+.pull-4{margin-left:-160px}
+.pull-5{margin-left:-200px}
+.pull-6{margin-left:-240px}
+.pull-7{margin-left:-280px}
+.pull-8{margin-left:-320px}
+.pull-9{margin-left:-360px}
+.pull-10{margin-left:-400px}
+.pull-11{margin-left:-440px}
+.pull-12{margin-left:-480px}
+.pull-13{margin-left:-520px}
+.pull-14{margin-left:-560px}
+.pull-15{margin-left:-600px}
+.pull-16{margin-left:-640px}
+.pull-17{margin-left:-680px}
+.pull-18{margin-left:-720px}
+.pull-19{margin-left:-760px}
+.pull-20{margin-left:-800px}
+.pull-21{margin-left:-840px}
+.pull-22{margin-left:-880px}
+.pull-23{margin-left:-920px}
+.pull-24{margin-left:-960px}
+.pull-1,.pull-2,.pull-3,.pull-4,.pull-5,.pull-6,.pull-7,.pull-8,.pull-9,.pull-10,.pull-11,.pull-12,.pull-13,.pull-14,.pull-15,.pull-16,.pull-17,.pull-18,.pull-19,.pull-20,.pull-21,.pull-22,.pull-23,.pull-24{float:left;position:relative}
+.push-1{margin:0 -40px 1.5em 40px}
+.push-2{margin:0 -80px 1.5em 80px}
+.push-3{margin:0 -120px 1.5em 120px}
+.push-4{margin:0 -160px 1.5em 160px}
+.push-5{margin:0 -200px 1.5em 200px}
+.push-6{margin:0 -240px 1.5em 240px}
+.push-7{margin:0 -280px 1.5em 280px}
+.push-8{margin:0 -320px 1.5em 320px}
+.push-9{margin:0 -360px 1.5em 360px}
+.push-10{margin:0 -400px 1.5em 400px}
+.push-11{margin:0 -440px 1.5em 440px}
+.push-12{margin:0 -480px 1.5em 480px}
+.push-13{margin:0 -520px 1.5em 520px}
+.push-14{margin:0 -560px 1.5em 560px}
+.push-15{margin:0 -600px 1.5em 600px}
+.push-16{margin:0 -640px 1.5em 640px}
+.push-17{margin:0 -680px 1.5em 680px}
+.push-18{margin:0 -720px 1.5em 720px}
+.push-19{margin:0 -760px 1.5em 760px}
+.push-20{margin:0 -800px 1.5em 800px}
+.push-21{margin:0 -840px 1.5em 840px}
+.push-22{margin:0 -880px 1.5em 880px}
+.push-23{margin:0 -920px 1.5em 920px}
+.push-24{margin:0 -960px 1.5em 960px}
+.push-1,.push-2,.push-3,.push-4,.push-5,.push-6,.push-7,.push-8,.push-9,.push-10,.push-11,.push-12,.push-13,.push-14,.push-15,.push-16,.push-17,.push-18,.push-19,.push-20,.push-21,.push-22,.push-23,.push-24{float:left;position:relative}
+div.prepend-top,.prepend-top{margin-top:1.5em}
+div.append-bottom,.append-bottom{margin-bottom:1.5em}
+.box{padding:1.5em;margin-bottom:1.5em;background:#e5eCf9}
+hr{background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:0}
+hr.space{background:#fff;color:#fff;visibility:hidden}
+.clearfix:after,.container:after{content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden}
+.clearfix,.container{display:block}
+.clear{clear:both}
\ No newline at end of file
diff --git a/test/data/blueprint.css b/test/data/blueprint.css
new file mode 100644 (file)
index 0000000..ed0bd15
--- /dev/null
@@ -0,0 +1,556 @@
+/* --------------------------------------------------------------
+
+   reset.css
+   * Resets default browser CSS.
+
+-------------------------------------------------------------- */
+
+html { 
+       margin:0; 
+       padding:0; 
+       border:0; 
+}
+
+body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, code,
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-weight: inherit;
+  font-style: inherit;
+  font-size: 100%;
+  font-family: inherit;
+  vertical-align: baseline;
+}
+
+/* This helps to make newer HTML5 elements behave like DIVs in older browers */ 
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+    display:block;
+}
+
+/* Line-height should always be unitless! */
+body {
+  line-height: 1.5;
+  background: white; 
+}
+
+/* Tables still need 'cellspacing="0"' in the markup. */
+table { 
+       border-collapse: separate; 
+       border-spacing: 0; 
+}
+/* float:none prevents the span-x classes from breaking table-cell display */
+caption, th, td { 
+       text-align: left; 
+       font-weight: normal; 
+       float:none !important; 
+}
+table, th, td { 
+       vertical-align: middle; 
+}
+
+/* Remove possible quote marks (") from <q>, <blockquote>. */
+blockquote:before, blockquote:after, q:before, q:after { content: ''; }
+blockquote, q { quotes: "" ""; }
+
+/* Remove annoying border on linked images. */
+a img { border: none; }
+
+/* Remember to define your own focus styles! */
+:focus { outline: 0; }
+
+/* --------------------------------------------------------------
+
+   typography.css
+   * Sets up some sensible default typography.
+
+-------------------------------------------------------------- */
+
+/* Default font settings.
+   The font-size percentage is of 16px. (0.75 * 16px = 12px) */
+html { font-size:100.01%; }
+body {
+  font-size: 75%;
+  color: #222;
+  background: #fff;
+  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+}
+
+
+/* Headings
+-------------------------------------------------------------- */
+
+h1,h2,h3,h4,h5,h6 { font-weight: normal; color: #111; }
+
+h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
+h2 { font-size: 2em; margin-bottom: 0.75em; }
+h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
+h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
+h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
+h6 { font-size: 1em; font-weight: bold; }
+
+h1 img, h2 img, h3 img,
+h4 img, h5 img, h6 img {
+  margin: 0;
+}
+
+
+/* Text elements
+-------------------------------------------------------------- */
+
+p           { margin: 0 0 1.5em; }
+/* 
+       These can be used to pull an image at the start of a paragraph, so 
+       that the text flows around it (usage: <p><img class="left">Text</p>) 
+ */
+.left                          { float: left !important; }
+p .left                        { margin: 1.5em 1.5em 1.5em 0; padding: 0; }
+.right                         { float: right !important; }
+p .right               { margin: 1.5em 0 1.5em 1.5em; padding: 0; }
+
+a:focus,
+a:hover     { color: #09f; }
+a           { color: #06c; text-decoration: underline; }
+
+blockquote  { margin: 1.5em; color: #666; font-style: italic; }
+strong,dfn     { font-weight: bold; }
+em,dfn      { font-style: italic; }
+sup, sub    { line-height: 0; }
+
+abbr,
+acronym     { border-bottom: 1px dotted #666; }
+address     { margin: 0 0 1.5em; font-style: italic; }
+del         { color:#666; }
+
+pre         { margin: 1.5em 0; white-space: pre; }
+pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
+
+
+/* Lists
+-------------------------------------------------------------- */
+
+li ul,
+li ol       { margin: 0; }
+ul, ol      { margin: 0 1.5em 1.5em 0; padding-left: 1.5em; }
+
+ul          { list-style-type: disc; }
+ol          { list-style-type: decimal; }
+
+dl          { margin: 0 0 1.5em 0; }
+dl dt       { font-weight: bold; }
+dd          { margin-left: 1.5em;}
+
+
+/* Tables
+-------------------------------------------------------------- */
+
+/* 
+       Because of the need for padding on TH and TD, the vertical rhythm 
+       on table cells has to be 27px, instead of the standard 18px or 36px 
+       of other elements. 
+ */ 
+table       { margin-bottom: 1.4em; width:100%; }
+th          { font-weight: bold; }
+thead th    { background: #c3d9ff; }
+th,td,caption { padding: 4px 10px 4px 5px; }
+/*
+       You can zebra-stripe your tables in outdated browsers by adding 
+       the class "even" to every other table row. 
+ */
+tbody tr:nth-child(even) td, 
+tbody tr.even td  { 
+       background: #e5ecf9; 
+}
+tfoot       { font-style: italic; }
+caption     { background: #eee; }
+
+
+/* Misc classes
+-------------------------------------------------------------- */
+
+.small      { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
+.large      { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
+.hide       { display: none; }
+
+.quiet      { color: #666; }
+.loud       { color: #000; }
+.highlight  { background:#ff0; }
+.added      { background:#060; color: #fff; }
+.removed    { background:#900; color: #fff; }
+
+.first      { margin-left:0; padding-left:0; }
+.last       { margin-right:0; padding-right:0; }
+.top        { margin-top:0; padding-top:0; }
+.bottom     { margin-bottom:0; padding-bottom:0; }
+
+/* --------------------------------------------------------------
+
+   forms.css
+   * Sets up some default styling for forms
+   * Gives you classes to enhance your forms
+
+   Usage:
+   * For text fields, use class .title or .text
+   * For inline forms, use .inline (even when using columns)
+
+-------------------------------------------------------------- */
+
+/* 
+       A special hack is included for IE8 since it does not apply padding 
+       correctly on fieldsets
+ */ 
+label       { font-weight: bold; }
+fieldset    { padding:0 1.4em 1.4em 1.4em; margin: 0 0 1.5em 0; border: 1px solid #ccc; }
+legend      { font-weight: bold; font-size:1.2em; margin-top:-0.2em; margin-bottom:1em; }
+
+fieldset, #IE8#HACK { padding-top:1.4em; } 
+legend, #IE8#HACK { margin-top:0; margin-bottom:0; }
+
+/* Form fields
+-------------------------------------------------------------- */
+
+/* 
+  Attribute selectors are used to differentiate the different types 
+  of input elements, but to support old browsers, you will have to 
+  add classes for each one. ".title" simply creates a large text  
+  field, this is purely for looks.
+ */
+input[type=text], input[type=password],
+input.text, input.title,
+textarea {
+  background-color:#fff;
+  border:1px solid #bbb;
+}
+input[type=text]:focus, input[type=password]:focus,
+input.text:focus, input.title:focus,
+textarea:focus {
+  border-color:#666;
+}
+select { background-color:#fff; border-width:1px; border-style:solid; }
+
+input[type=text], input[type=password],
+input.text, input.title,
+textarea, select {
+  margin:0.5em 0;
+}
+
+input.text,
+input.title   { width: 300px; padding:5px; }
+input.title   { font-size:1.5em; }
+textarea      { width: 390px; height: 250px; padding:5px; }
+
+/* 
+  This is to be used on forms where a variety of elements are 
+  placed side-by-side. Use the p tag to denote a line. 
+ */
+form.inline { line-height:3; }
+form.inline p { margin-bottom:0; }
+
+
+/* Success, info, notice and error/alert boxes
+-------------------------------------------------------------- */
+
+.error,
+.alert, 
+.notice,
+.success, 
+.info                  { padding: 0.8em; margin-bottom: 1em; border: 2px solid #ddd; }
+
+.error, .alert { background: #fbe3e4; color: #8a1f11; border-color: #fbc2c4; }
+.notice     { background: #fff6bf; color: #514721; border-color: #ffd324; }
+.success    { background: #e6efc2; color: #264409; border-color: #c6d880; }
+.info                  { background: #d5edf8; color: #205791; border-color: #92cae4; }
+.error a, .alert a { color: #8a1f11; }
+.notice a   { color: #514721; }
+.success a  { color: #264409; }
+.info a                        { color: #205791; }
+
+
+/* --------------------------------------------------------------
+
+   grid.css
+   * Sets up an easy-to-use grid of 24 columns.
+
+   By default, the grid is 950px wide, with 24 columns
+   spanning 30px, and a 10px margin between columns.
+
+   If you need fewer or more columns, namespaces or semantic
+   element names, use the compressor script (lib/compress.rb)
+
+-------------------------------------------------------------- */
+
+/* A container should group all your columns. */
+.container {
+  width: 950px;
+  margin: 0 auto;
+}
+
+/* Use this class on any .span / container to see the grid. */
+.showgrid {
+  background: url(src/grid.png);
+}
+
+
+/* Columns
+-------------------------------------------------------------- */
+
+/* Sets up basic grid floating and margin. */
+.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {
+  float: left;
+  margin-right: 10px;
+}
+
+/* The last column in a row needs this class. */
+.last { margin-right: 0; }
+
+/* Use these classes to set the width of a column. */
+.span-1 {width: 30px;}
+
+.span-2 {width: 70px;}
+.span-3 {width: 110px;}
+.span-4 {width: 150px;}
+.span-5 {width: 190px;}
+.span-6 {width: 230px;}
+.span-7 {width: 270px;}
+.span-8 {width: 310px;}
+.span-9 {width: 350px;}
+.span-10 {width: 390px;}
+.span-11 {width: 430px;}
+.span-12 {width: 470px;}
+.span-13 {width: 510px;}
+.span-14 {width: 550px;}
+.span-15 {width: 590px;}
+.span-16 {width: 630px;}
+.span-17 {width: 670px;}
+.span-18 {width: 710px;}
+.span-19 {width: 750px;}
+.span-20 {width: 790px;}
+.span-21 {width: 830px;}
+.span-22 {width: 870px;}
+.span-23 {width: 910px;}
+.span-24 {width:950px; margin-right:0;}
+
+/* Use these classes to set the width of an input. */
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {
+  border-left-width: 1px;
+  border-right-width: 1px;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+
+input.span-1, textarea.span-1 { width: 18px; }
+input.span-2, textarea.span-2 { width: 58px; }
+input.span-3, textarea.span-3 { width: 98px; }
+input.span-4, textarea.span-4 { width: 138px; }
+input.span-5, textarea.span-5 { width: 178px; }
+input.span-6, textarea.span-6 { width: 218px; }
+input.span-7, textarea.span-7 { width: 258px; }
+input.span-8, textarea.span-8 { width: 298px; }
+input.span-9, textarea.span-9 { width: 338px; }
+input.span-10, textarea.span-10 { width: 378px; }
+input.span-11, textarea.span-11 { width: 418px; }
+input.span-12, textarea.span-12 { width: 458px; }
+input.span-13, textarea.span-13 { width: 498px; }
+input.span-14, textarea.span-14 { width: 538px; }
+input.span-15, textarea.span-15 { width: 578px; }
+input.span-16, textarea.span-16 { width: 618px; }
+input.span-17, textarea.span-17 { width: 658px; }
+input.span-18, textarea.span-18 { width: 698px; }
+input.span-19, textarea.span-19 { width: 738px; }
+input.span-20, textarea.span-20 { width: 778px; }
+input.span-21, textarea.span-21 { width: 818px; }
+input.span-22, textarea.span-22 { width: 858px; }
+input.span-23, textarea.span-23 { width: 898px; }
+input.span-24, textarea.span-24 { width: 938px; }
+
+/* Add these to a column to append empty cols. */
+
+.append-1 { padding-right: 40px;}
+.append-2 { padding-right: 80px;}
+.append-3 { padding-right: 120px;}
+.append-4 { padding-right: 160px;}
+.append-5 { padding-right: 200px;}
+.append-6 { padding-right: 240px;}
+.append-7 { padding-right: 280px;}
+.append-8 { padding-right: 320px;}
+.append-9 { padding-right: 360px;}
+.append-10 { padding-right: 400px;}
+.append-11 { padding-right: 440px;}
+.append-12 { padding-right: 480px;}
+.append-13 { padding-right: 520px;}
+.append-14 { padding-right: 560px;}
+.append-15 { padding-right: 600px;}
+.append-16 { padding-right: 640px;}
+.append-17 { padding-right: 680px;}
+.append-18 { padding-right: 720px;}
+.append-19 { padding-right: 760px;}
+.append-20 { padding-right: 800px;}
+.append-21 { padding-right: 840px;}
+.append-22 { padding-right: 880px;}
+.append-23 { padding-right: 920px;}
+
+/* Add these to a column to prepend empty cols. */
+
+.prepend-1 { padding-left: 40px;}
+.prepend-2 { padding-left: 80px;}
+.prepend-3 { padding-left: 120px;}
+.prepend-4 { padding-left: 160px;}
+.prepend-5 { padding-left: 200px;}
+.prepend-6 { padding-left: 240px;}
+.prepend-7 { padding-left: 280px;}
+.prepend-8 { padding-left: 320px;}
+.prepend-9 { padding-left: 360px;}
+.prepend-10 { padding-left: 400px;}
+.prepend-11 { padding-left: 440px;}
+.prepend-12 { padding-left: 480px;}
+.prepend-13 { padding-left: 520px;}
+.prepend-14 { padding-left: 560px;}
+.prepend-15 { padding-left: 600px;}
+.prepend-16 { padding-left: 640px;}
+.prepend-17 { padding-left: 680px;}
+.prepend-18 { padding-left: 720px;}
+.prepend-19 { padding-left: 760px;}
+.prepend-20 { padding-left: 800px;}
+.prepend-21 { padding-left: 840px;}
+.prepend-22 { padding-left: 880px;}
+.prepend-23 { padding-left: 920px;}
+
+
+/* Border on right hand side of a column. */
+.border {
+  padding-right: 4px;
+  margin-right: 5px;
+  border-right: 1px solid #ddd;
+}
+
+/* Border with more whitespace, spans one column. */
+.colborder {
+  padding-right: 24px;
+  margin-right: 25px;
+  border-right: 1px solid #ddd;
+}
+
+
+/* Use these classes on an element to push it into the
+next column, or to pull it into the previous column.  */
+
+
+.pull-1 { margin-left: -40px; }
+.pull-2 { margin-left: -80px; }
+.pull-3 { margin-left: -120px; }
+.pull-4 { margin-left: -160px; }
+.pull-5 { margin-left: -200px; }
+.pull-6 { margin-left: -240px; }
+.pull-7 { margin-left: -280px; }
+.pull-8 { margin-left: -320px; }
+.pull-9 { margin-left: -360px; }
+.pull-10 { margin-left: -400px; }
+.pull-11 { margin-left: -440px; }
+.pull-12 { margin-left: -480px; }
+.pull-13 { margin-left: -520px; }
+.pull-14 { margin-left: -560px; }
+.pull-15 { margin-left: -600px; }
+.pull-16 { margin-left: -640px; }
+.pull-17 { margin-left: -680px; }
+.pull-18 { margin-left: -720px; }
+.pull-19 { margin-left: -760px; }
+.pull-20 { margin-left: -800px; }
+.pull-21 { margin-left: -840px; }
+.pull-22 { margin-left: -880px; }
+.pull-23 { margin-left: -920px; }
+.pull-24 { margin-left: -960px; }
+
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float: left; position:relative;}
+
+
+.push-1 { margin: 0 -40px 1.5em 40px; }
+.push-2 { margin: 0 -80px 1.5em 80px; }
+.push-3 { margin: 0 -120px 1.5em 120px; }
+.push-4 { margin: 0 -160px 1.5em 160px; }
+.push-5 { margin: 0 -200px 1.5em 200px; }
+.push-6 { margin: 0 -240px 1.5em 240px; }
+.push-7 { margin: 0 -280px 1.5em 280px; }
+.push-8 { margin: 0 -320px 1.5em 320px; }
+.push-9 { margin: 0 -360px 1.5em 360px; }
+.push-10 { margin: 0 -400px 1.5em 400px; }
+.push-11 { margin: 0 -440px 1.5em 440px; }
+.push-12 { margin: 0 -480px 1.5em 480px; }
+.push-13 { margin: 0 -520px 1.5em 520px; }
+.push-14 { margin: 0 -560px 1.5em 560px; }
+.push-15 { margin: 0 -600px 1.5em 600px; }
+.push-16 { margin: 0 -640px 1.5em 640px; }
+.push-17 { margin: 0 -680px 1.5em 680px; }
+.push-18 { margin: 0 -720px 1.5em 720px; }
+.push-19 { margin: 0 -760px 1.5em 760px; }
+.push-20 { margin: 0 -800px 1.5em 800px; }
+.push-21 { margin: 0 -840px 1.5em 840px; }
+.push-22 { margin: 0 -880px 1.5em 880px; }
+.push-23 { margin: 0 -920px 1.5em 920px; }
+.push-24 { margin: 0 -960px 1.5em 960px; }
+
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float: left; position:relative;}
+
+
+/* Misc classes and elements
+-------------------------------------------------------------- */
+
+/* In case you need to add a gutter above/below an element */
+div.prepend-top, .prepend-top {
+  margin-top:1.5em;
+}
+div.append-bottom, .append-bottom {
+  margin-bottom:1.5em;
+}
+
+/* Use a .box to create a padded box inside a column.  */
+.box {
+  padding: 1.5em;
+  margin-bottom: 1.5em;
+  background: #e5eCf9;
+}
+
+/* Use this to create a horizontal ruler across a column. */
+hr {
+  background: #ddd;
+  color: #ddd;
+  clear: both;
+  float: none;
+  width: 100%;
+  height: 1px;
+  margin: 0 0 1.45em;
+  border: none;
+}
+
+hr.space {
+  background: #fff;
+  color: #fff;
+  visibility: hidden;
+}
+
+
+/* Clearing floats without extra markup
+   Based on How To Clear Floats Without Structural Markup by PiE
+   [http://www.positioniseverything.net/easyclearing.html] */
+
+.clearfix:after, .container:after {
+  content: "\0020";
+  display: block;
+  height: 0;
+  clear: both;
+  visibility: hidden;
+  overflow:hidden;
+}
+.clearfix, .container {display: block;}
+
+/* Regular clearing
+   apply to column that should drop below previous ones. */
+
+.clear { clear:both; }
+
diff --git a/test/data/reset-min.css b/test/data/reset-min.css
new file mode 100644 (file)
index 0000000..4dd3944
--- /dev/null
@@ -0,0 +1,12 @@
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}
+:focus{outline:0}
+body{line-height:1;color:#000;background:#fff}
+ol,ul{list-style:none}
+table{border-collapse:separate;border-spacing:0}
+caption,th,td{text-align:left;font-weight:400}
+blockquote:before,blockquote:after,q:before,q:after{content:""}
+blockquote,q{quotes:"" ""}
+.clear{clear:both;display:inline-block}
+.clear:after,.container:after{content:".";display:block;height:0;clear:both;visibility:hidden}
+* html .clear{height:1%}
+.clear{display:block}
\ No newline at end of file
diff --git a/test/data/reset.css b/test/data/reset.css
new file mode 100644 (file)
index 0000000..cb2aabe
--- /dev/null
@@ -0,0 +1,64 @@
+/*reset*/
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  outline: 0;
+  font-weight: inherit;
+  font-style: inherit;
+  font-size: 100%;
+  font-family: inherit;
+  vertical-align: baseline;
+}
+/* remember to define focus styles! */
+:focus {
+  outline: 0;
+}
+body {
+  line-height: 1;
+  color: black;
+  background: white;
+}
+ol, ul {
+  list-style: none;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+  border-collapse: separate;
+  border-spacing: 0;
+}
+caption, th, td {
+  text-align: left;
+  font-weight: normal;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+  content: "";
+}
+blockquote, q {
+  quotes: "" "";
+}
+.clear { 
+  clear:both;
+  display: inline-block; 
+}   
+.clear:after, .container:after {
+  content: "."; 
+  display: block; 
+  height: 0; 
+  clear: both; 
+  visibility: hidden;
+}
+* html .clear { 
+  height: 1%; 
+}
+.clear { 
+  display: block; 
+}
\ No newline at end of file
diff --git a/test/purifier-test.js b/test/purifier-test.js
deleted file mode 100644 (file)
index 8abec44..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-var vows = require('vows'),
-  assert = require('assert');
-
-var Purify = require('../lib/purify').Purify;
-
-var cssContext = function(groups) {
-  var context = {};
-  var clean = function(cleanCss) {
-    return function(css) { assert.equal(Purify.process(css), cleanCss); }
-  };
-  
-  for (var g in groups) {
-    var transformation = groups[g];
-    if (typeof transformation == 'string') transformation = [transformation, transformation];
-    
-    context[g] = {
-      topic: transformation[0],
-      clean: clean(transformation[1])
-    };
-  }
-  
-  return context;
-};
-
-vows.describe('purify').addBatch({
-  'identity': cssContext({
-    'preserve minified content': 'a{color:#f00}'
-  }),
-  'semicolons': cssContext({
-    'multiple semicolons': [
-      'a{color:#fff;;;width:0; ;}',
-      'a{color:#fff;width:0}'
-    ],
-    'trailing semicolon': [
-      'a{color:#fff;}',
-      'a{color:#fff}'
-    ],
-    'trailing semicolon and space': [
-      'a{color:#fff ; }',
-      'a{color:#fff}'
-    ],
-    'comma and space': [
-      'a{color:rgba(0, 0,  5, .5)}',
-      'a{color:rgba(0,0,5,.5)}'
-    ]
-  }),
-  'whitespace': cssContext({
-    'one argument': [
-      'div  a  { color:#fff  }',
-      'div a{color:#fff}'
-    ],
-    'line breaks': [
-      'div \na\r\n { width:500px }',
-      'div a{width:500px}'
-    ],
-    'multiple arguments': [
-      'a{color:#fff ;  font-weight:  bold }',
-      'a{color:#fff;font-weight:bold}'
-    ],
-    'space delimited arguments': [
-      'a {border: 1px solid #f00; margin: 0 auto }',
-      'a{border:1px solid #f00;margin:0 auto}'
-    ]
-  }),
-  'empty elements': cssContext({
-    'single': [
-      ' div p {  \n}',
-      ''
-    ],
-    'between non-empty': [
-      'div {color:#fff}  a{  } p{  line-height:1.35em}',
-      'div{color:#fff}p{line-height:1.35em}'
-    ],
-    'just a semicolon': [
-      'div { ; }',
-      ''
-    ]
-  }),
-  'comments': cssContext({
-    'single line': [
-      'a{color:#fff}/* some comment*/p{height:10px/* other comment */}',
-      'a{color:#fff}p{height:10px}'
-    ],
-    'multiline': [
-      '/* \r\n multiline \n comment */a{color:rgba(0,0,0,0.8)}',
-      'a{color:rgba(0,0,0,.8)}'
-    ],
-    'comment chars in comments': [
-      '/* \r\n comment chars * inside / comments */a{color:#fff}',
-      'a{color:#fff}'
-    ],
-    'comment inside block': [
-      'a{/* \r\n some comments */color:#fff}',
-      'a{color:#fff}'
-    ],
-    'special comments': [
-      '/*! special comment */a{color:#f00} /* normal comment */',
-      '/*! special comment */a{color:#f00}'
-    ]
-  }),
-  'zero values': cssContext({
-    'with units': [
-      'a{margin:0px 0pt 0em 0%;padding: 0in 0cm 0mm 0pc;border-top-width:0ex}',
-      'a{margin:0;padding:0;border-top-width:0}'
-    ],
-    'multiple into one': [
-      'a{margin:0 0 0 0;padding:0 0 0;border-width:0 0}',
-      'a{margin:0;padding:0;border-width:0}'
-    ],
-    'none to zeros': [
-      'a{border:none;background:none}',
-      'a{border:0;background:0}'
-    ]
-  }),
-  'floats': cssContext({
-    'strips zero in fractions': [
-      'a{ margin-bottom: 0.5em}',
-      'a{margin-bottom:.5em}'
-    ],
-    'not strips zero in fractions of numbers greater than zero': [
-      'a{ margin-bottom: 20.5em}',
-      'a{margin-bottom:20.5em}'
-    ]
-  }),
-  'colors': cssContext({
-    'shorten rgb to standard hexadecimal format': [
-      'a{ color:rgb (5, 10, 15) }',
-      'a{color:#050a0f}'
-    ],
-    'skip rgba shortening': [
-      'a{ color:rgba(5, 10, 15, 0.5)}',
-      'a{color:rgba(5,10,15,.5)}'
-    ],
-    'shorten colors to 3 digit hex instead of 6 digit': [
-      'a{ background-color: #ff0000; color:rgb(0, 17, 255)}',
-      'a{background-color:#f00;color:#01f}'
-    ],
-    'skip shortening IE filter colors': [
-      'a{ filter: chroma(color = "#ff0000")}',
-      'a{filter:chroma(color="#ff0000")}'
-    ]
-  }),
-  'ie filters': cssContext({
-    'alpha': [
-      "a{ filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80); -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)';}",
-      "a{filter:alpha(Opacity=80);-ms-filter:'alpha(Opacity=50)'}"
-    ]
-  }),
-  'charsets': cssContext({
-    'not at beginning': [
-      "a{ color: #f00; }@charset 'utf-8';b { font-weight: bold}",
-      "@charset 'utf-8';a{color:#f00}b{font-weight:bold}"
-    ],
-    'multiple charsets': [
-      "@charset 'utf-8';div :before { display: block }@charset 'utf-8';a { color: #f00 }",
-      "@charset 'utf-8';div :before{display:block}a{color:#f00}"
-    ]
-  })
-}).export(module);
\ No newline at end of file
diff --git a/test/unit-test.js b/test/unit-test.js
new file mode 100644 (file)
index 0000000..77af20d
--- /dev/null
@@ -0,0 +1,252 @@
+var vows = require('vows'),
+  assert = require('assert');
+
+var CleanCSS = require('../lib/clean').CleanCSS;
+
+var cssContext = function(groups) {
+  var context = {};
+  var clean = function(cleanCss) {
+    return function(css) {
+      assert.equal(CleanCSS.process(css), cleanCss);
+    }
+  };
+  
+  for (var g in groups) {
+    var transformation = groups[g];
+    if (typeof transformation == 'string') transformation = [transformation, transformation];
+    if (!transformation[0].push) {
+      transformation = [[transformation[0], transformation[1]]];
+    }
+    
+    for (var i = 0, c = transformation.length; i < c; i++) {
+      context[g + ' #' + (i + 1)] = {
+        topic: transformation[i][0],
+        clean: clean(transformation[i][1])
+      };
+    }
+  }
+  
+  return context;
+};
+
+vows.describe('clean-units').addBatch({
+  'identity': cssContext({
+    'preserve minified content': 'a{color:#f10}'
+  }),
+  'semicolons': cssContext({
+    'multiple semicolons': [
+      'a{color:#fff;;;width:0; ;}',
+      'a{color:#fff;width:0}'
+    ],
+    'trailing semicolon': [
+      'a{color:#fff;}',
+      'a{color:#fff}'
+    ],
+    'trailing semicolon and space': [
+      'a{color:#fff ; }',
+      'a{color:#fff}'
+    ],
+    'comma and space': [
+      'a{color:rgba(0, 0,  5, .5)}',
+      'a{color:rgba(0,0,5,.5)}'
+    ]
+  }),
+  'whitespace': cssContext({
+    'one argument': [
+      'div  a  { color:#fff  }',
+      'div a{color:#fff}'
+    ],
+    'line breaks': [
+      'div \na\r\n { width:500px }',
+      'div a{width:500px}'
+    ],
+    'line breaks #2': [
+      'div \na\r\n, p { width:500px }',
+      'div a,p{width:500px}'
+    ],
+    'multiple arguments': [
+      'a{color:#fff ;  font-weight:  bolder }',
+      'a{color:#fff;font-weight:bolder}'
+    ],
+    'space delimited arguments': [
+      'a {border: 1px solid #f10; margin: 0 auto }',
+      'a{border:1px solid #f10;margin:0 auto}'
+    ],
+    'at beginning': [
+      ' a {color:#fff}',
+      'a{color:#fff}'
+    ],
+    'at end': [
+      'a{color:#fff } ',
+      'a{color:#fff}'
+    ]
+  }),
+  'empty elements': cssContext({
+    'single': [
+      ' div p {  \n}',
+      ''
+    ],
+    'between non-empty': [
+      'div {color:#fff}  a{  } p{  line-height:1.35em}',
+      'div{color:#fff}p{line-height:1.35em}'
+    ],
+    'just a semicolon': [
+      'div { ; }',
+      ''
+    ]
+  }),
+  'selectors': cssContext({
+    'remove spaces around selectors': [
+      'div + span >   em',
+      'div+span>em'
+    ],
+    'not remove spaces for pseudo-classes': [
+      'div :first-child',
+      'div :first-child'
+    ],
+    'strip universal selector when coming with id/class/attribute selectors': [
+      [
+        '* > *#id > *.class',
+        '*>#id>.class'
+      ],[
+        '*:first-child > *[data-id]',
+        ':first-child>[data-id]'
+      ]
+    ],
+    'not strip standalone universal selector': [
+      'label ~ * + span',
+      'label~*+span'
+    ]
+  }),
+  'comments': cssContext({
+    'single line': [
+      'a{color:#fff}/* some comment*/p{height:10px/* other comment */}',
+      'a{color:#fff}p{height:10px}'
+    ],
+    'multiline': [
+      '/* \r\n multiline \n comment */a{color:rgba(0,0,0,0.8)}',
+      'a{color:rgba(0,0,0,.8)}'
+    ],
+    'comment chars in comments': [
+      '/* \r\n comment chars * inside / comments */a{color:#fff}',
+      'a{color:#fff}'
+    ],
+    'comment inside block': [
+      'a{/* \r\n some comments */color:#fff}',
+      'a{color:#fff}'
+    ],
+    'special comments': [
+      '/*! special comment */a{color:#f10} /* normal comment */',
+      '/*! special comment */a{color:#f10}'
+    ],
+    'should keep exact structure': [
+      '/*!  \n  a > span { } with some content */',
+      '/*!  \n  a > span { } with some content */'
+    ]
+  }),
+  'text content': cssContext({
+    'normal': 'a{content:"."}',
+    'open quote': [
+      'a{content : open-quote;opacity:1}',
+      'a{content:open-quote;opacity:1}'
+    ],
+    'close quote': [
+      'a{content:  close-quote;clear:left}',
+      'a{content:close-quote;clear:left}'
+    ],
+    'special characters': [
+      'a{content : "  a > div { }  "}',
+      'a{content:"  a > div { }  "}'
+    ]
+  }),
+  'zero values': cssContext({
+    'with units': [
+      'a{margin:0px 0pt 0em 0%;padding: 0in 0cm 0mm 0pc;border-top-width:0ex}',
+      'a{margin:0;padding:0;border-top-width:0}'
+    ],
+    'multiple into one': [
+      'a{margin:0 0 0 0;padding:0 0 0 0;border-width:0 0 0 0}',
+      'a{margin:0;padding:0;border-width:0}'
+    ],
+    'none to zeros': [
+      'a{border:none;background:none}',
+      'a{border:0;background:0}'
+    ],
+    'outline:none to outline:0': [
+      'a{outline:none}',
+      'a{outline:0}'
+    ],
+    'display:none not changed': 'a{display:none}',
+    'mixed zeros not changed': 'div{margin:0 0 1px 0}',
+    'mixed zeros not changed #2': 'div{padding:0 1px 0 0}'
+  }),
+  'floats': cssContext({
+    'strips zero in fractions': [
+      'a{ margin-bottom: 0.5em}',
+      'a{margin-bottom:.5em}'
+    ],
+    'not strips zero in fractions of numbers greater than zero': [
+      'a{ margin-bottom: 20.5em}',
+      'a{margin-bottom:20.5em}'
+    ]
+  }),
+  'colors': cssContext({
+    'shorten rgb to standard hexadecimal format': [
+      'a{ color:rgb (5, 10, 15) }',
+      'a{color:#050a0f}'
+    ],
+    'skip rgba shortening': [
+      'a{ color:rgba(5, 10, 15, 0.5)}',
+      'a{color:rgba(5,10,15,.5)}'
+    ],
+    'shorten colors to 3 digit hex instead of 6 digit': [
+      'a{ background-color: #aa0000; color:rgb(0, 17, 255)}',
+      'a{background-color:#a00;color:#01f}'
+    ],
+    'skip shortening IE filter colors': [
+      'a{ filter: chroma(color = "#ff0000")}',
+      'a{filter:chroma(color="#ff0000")}'
+    ],
+    'color names to hex values': [
+      'a{color:white;border-color:black;background-color:fuchsia}p{background:yellow}',
+      'a{color:#fff;border-color:#000;background-color:#f0f}p{background:#ff0}'
+    ],
+    'hex value to color name': [
+      'p{color:#f00}',
+      'p{color:red}'
+    ]
+  }),
+  'font weights': cssContext({
+    'font-weight:normal to 400': [
+      'p{font-weight:normal}',
+      'p{font-weight:400}'
+    ],
+    'font-weight:bold to 700': [
+      'p{font-weight:bold}',
+      'p{font-weight:700}'
+    ]
+  }),
+  'ie filters': cssContext({
+    'short alpha': [
+      "a{ filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80); -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)';}",
+      "a{filter:alpha(Opacity=80);-ms-filter:'alpha(Opacity=50)'}"
+    ],
+    'short chroma': [
+      'progid:DXImageTransform.Microsoft.Chroma(color=#919191)', 'chroma(color=#919191)'
+    ],
+    'matrix filter spaces': [
+      "progid:DXImageTransform.Microsoft.Matrix(M11=0.984, M22=0.984, M12=0.17, M21=-0.17, SizingMethod='auto expand')",
+      "progid:DXImageTransform.Microsoft.Matrix(M11=.984, M22=.984, M12=.17, M21=-.17, SizingMethod='auto expand')"
+    ]
+  }),
+  'charsets': cssContext({
+    'not at beginning': [
+      "a{ color: #f10; }@charset 'utf-8';b { font-weight: bolder}",
+      "@charset 'utf-8';a{color:#f10}b{font-weight:bolder}"
+    ],
+    'multiple charsets': [
+      "@charset 'utf-8';div :before { display: block }@charset 'utf-8';a { color: #f10 }",
+      "@charset 'utf-8';div :before{display:block}a{color:#f10}"
+    ]
+  })
+}).export(module);
\ No newline at end of file