Adds a better algorithm for quotation marks' removal.
authorJakub Pawlowicz <jakub@goalsmashers.com>
Sat, 1 Mar 2014 08:52:27 +0000 (08:52 +0000)
committerJakub Pawlowicz <jakub@goalsmashers.com>
Sat, 1 Mar 2014 23:36:20 +0000 (23:36 +0000)
Quotation removal is now done via data traversal rather than regexp so
it is much more flexible.

Also adds a property name scanner to quickly locate last matching property
at a given location.

History.md
lib/clean.js
lib/properties/scanner.js [new file with mode: 0644]
lib/text/name-quotes.js [new file with mode: 0644]
lib/text/quote-scanner.js
test/unit-test.js

index 83281c2..219d732 100644 (file)
@@ -1,6 +1,7 @@
 [2.2.0 / 2014-xx-xx (UNRELEASED)](https://github.com/GoalSmashers/clean-css/compare/v2.1.4...HEAD)
 ==================
 
+* Adds a better algorithm for quotation marks' removal.
 * Adds a better non-adjacent optimizer compatible with the upcoming new property optimizer.
 * Moves quotation matching into a QuoteScanner class.
 * Fixed issue [#247](https://github.com/GoalSmashers/clean-css/issues/247) - removes deprecated `selectorsMergeMode` switch.
index 0f31179..42b976f 100644 (file)
@@ -21,6 +21,7 @@ var CommentsProcessor = require('./text/comments');
 var ExpressionsProcessor = require('./text/expressions');
 var FreeTextProcessor = require('./text/free');
 var UrlsProcessor = require('./text/urls');
+var NameQuotesProcessor = require('./text/name-quotes');
 
 var SelectorsOptimizer = require('./selectors/optimizer');
 
@@ -92,6 +93,7 @@ var minify = function(data, callback) {
   var expressionsProcessor = new ExpressionsProcessor();
   var freeTextProcessor = new FreeTextProcessor();
   var urlsProcessor = new UrlsProcessor();
+  var nameQuotesProcessor = new NameQuotesProcessor();
 
   if (options.debug) {
     this.startedAt = process.hrtime();
@@ -143,11 +145,8 @@ var minify = function(data, callback) {
   });
 
   // strip parentheses in animation & font names
-  replace(/(animation|animation\-name|font|font\-family):([^;}]+)/g, function(match, propertyName, def) {
-    if (def.indexOf('\'{') === 0)
-      return match;
-
-    return propertyName + ':' + def.replace(/['"]([a-zA-Z][a-zA-Z\d\-_]+)['"]/g, '$1');
+  replace(function removeQuotes() {
+    data = nameQuotesProcessor.process(data);
   });
 
   // strip parentheses in @keyframes
diff --git a/lib/properties/scanner.js b/lib/properties/scanner.js
new file mode 100644 (file)
index 0000000..d6751d5
--- /dev/null
@@ -0,0 +1,20 @@
+(function() {
+  var OPEN_BRACE = '{';
+  var SEMICOLON = ';';
+  var COLON = ':';
+
+  var PropertyScanner = function PropertyScanner(data) {
+    this.data = data;
+  };
+
+  PropertyScanner.prototype.nextAt = function(cursor) {
+    var lastColon = this.data.lastIndexOf(COLON, cursor);
+    var lastOpenBrace = this.data.lastIndexOf(OPEN_BRACE, cursor);
+    var lastSemicolon = this.data.lastIndexOf(SEMICOLON, cursor);
+    var startAt = Math.max(lastOpenBrace, lastSemicolon);
+
+    return this.data.substring(startAt + 1, lastColon).trim();
+  };
+
+  module.exports = PropertyScanner;
+})();
diff --git a/lib/text/name-quotes.js b/lib/text/name-quotes.js
new file mode 100644 (file)
index 0000000..cb1e804
--- /dev/null
@@ -0,0 +1,37 @@
+(function() {
+  var QuoteScanner = require('./quote-scanner');
+  var PropertyScanner = require('../properties/scanner');
+
+  var NameQuotes = function NameQuotes() {};
+
+  var STRIPPABLE = /['"][a-zA-Z][a-zA-Z\d\-_]+['"]/;
+
+  var properties = [
+    'animation',
+    '-moz-animation',
+    '-o-animation',
+    '-webkit-animation',
+    'animation-name',
+    '-moz-animation-name',
+    '-o-animation-name',
+    '-webkit-animation-name',
+    'font',
+    'font-family'
+  ];
+
+  NameQuotes.prototype.process = function(data) {
+    var scanner = new PropertyScanner(data);
+
+    return new QuoteScanner(data).each(function(match, store, cursor) {
+      var lastProperty = scanner.nextAt(cursor);
+      if (properties.indexOf(lastProperty) > -1) {
+        if (STRIPPABLE.test(match))
+          match = match.substring(1, match.length - 1);
+      }
+
+      store.push(match);
+    });
+  };
+
+  module.exports = NameQuotes;
+})();
index 4bab694..cc1b9e6 100644 (file)
@@ -56,7 +56,7 @@
 
       var text = data.substring(nextStart, nextEnd + 1);
       tempData.push(data.substring(cursor, nextStart));
-      callback(text, tempData);
+      callback(text, tempData, nextStart);
 
       cursor = nextEnd + 1;
     }
index aa88284..473a303 100644 (file)
@@ -806,8 +806,7 @@ path)}',
       'a{background:url("/very/long/\
 path")}',
       'a{background:url(/very/long/path)}'
-    ],
-    'do not remove quotation from enclosed JSON (weird, I know)': "p{font-family:'{ \"current\" : \"large\", \"all\" : [\"small\", \"medium\", \"large\"], \"position\" : 2 }'}"
+    ]
   }),
   'urls rewriting - no root or target': cssContext({
     'no @import': 'a{background:url(test/data/partials/extra/down.gif) 0 0 no-repeat}',