From 8c1f7281fae4eef029e41925558f8040aea71dbc Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Sat, 1 Mar 2014 08:52:27 +0000 Subject: [PATCH] Adds a better algorithm for quotation marks' removal. 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 | 1 + lib/clean.js | 9 ++++----- lib/properties/scanner.js | 20 ++++++++++++++++++++ lib/text/name-quotes.js | 37 +++++++++++++++++++++++++++++++++++++ lib/text/quote-scanner.js | 2 +- test/unit-test.js | 3 +-- 6 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 lib/properties/scanner.js create mode 100644 lib/text/name-quotes.js diff --git a/History.md b/History.md index 83281c26..219d732b 100644 --- a/History.md +++ b/History.md @@ -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. diff --git a/lib/clean.js b/lib/clean.js index 0f311799..42b976f4 100644 --- a/lib/clean.js +++ b/lib/clean.js @@ -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 index 00000000..d6751d5b --- /dev/null +++ b/lib/properties/scanner.js @@ -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 index 00000000..cb1e8041 --- /dev/null +++ b/lib/text/name-quotes.js @@ -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; +})(); diff --git a/lib/text/quote-scanner.js b/lib/text/quote-scanner.js index 4bab694c..cc1b9e65 100644 --- a/lib/text/quote-scanner.js +++ b/lib/text/quote-scanner.js @@ -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; } diff --git a/test/unit-test.js b/test/unit-test.js index aa882842..473a303f 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -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}', -- 2.34.1