From: Jakub Pawlowicz Date: Thu, 25 Sep 2014 22:58:27 +0000 (+0100) Subject: Improves FreeTextProcessor. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=ec98b3c9997a60521f3fb33afa4c7d66535fb68f;p=clean-css.git Improves FreeTextProcessor. * Removes module wrapper. * Adds basic tests. * Adds very ugly normalization (pending refactoring). --- diff --git a/lib/text/free-text-processor.js b/lib/text/free-text-processor.js new file mode 100644 index 00000000..a7cfe91a --- /dev/null +++ b/lib/text/free-text-processor.js @@ -0,0 +1,72 @@ +var EscapeStore = require('./escape-store'); +var QuoteScanner = require('./quote-scanner'); + +var FreeTextProcessor = function FreeTextProcessor() { + this.matches = new EscapeStore('FREE_TEXT'); +}; + +// Strip content tags by replacing them by the a special +// marker for further restoring. It's done via string scanning +// instead of regexps to speed up the process. +FreeTextProcessor.prototype.escape = function(data) { + var self = this; + + return new QuoteScanner(data).each(function(match, store) { + var placeholder = self.matches.store(match); + store.push(placeholder); + }); +}; + +function normalize(text, data, cursor) { + // FIXME: this is a hack + var lastSemicolon = data.lastIndexOf(';', cursor); + var lastOpenBrace = data.lastIndexOf('{', cursor); + var lastOne = 0; + + if (lastSemicolon > -1 && lastOpenBrace > -1) + lastOne = Math.max(lastSemicolon, lastOpenBrace); + else if (lastSemicolon == -1) + lastOne = lastOpenBrace; + else + lastOne = lastSemicolon; + + var context = data.substring(lastOne + 1, cursor); + + if (/\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context)) + text = text.replace(/\\\n|\\\r\n/g, ''); + + if (/^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/.test(text) && !/format\($/.test(context)) { + var isFont = /^(font|font\-family):/.test(context); + var isAttribute = /\[[\w\d\-]+[\*\|\~\^\$]?=$/.test(context); + var isKeyframe = /@(-moz-|-o-|-webkit-)?keyframes /.test(context); + var isAnimation = /^(-moz-|-o-|-webkit-)?animation(-name)?:/.test(context); + + if (isFont || isAttribute || isKeyframe || isAnimation) + text = text.substring(1, text.length - 1); + } + + return text; +} + +FreeTextProcessor.prototype.restore = function(data) { + var tempData = []; + var cursor = 0; + + for (; cursor < data.length;) { + var nextMatch = this.matches.nextMatch(data, cursor); + if (nextMatch.start < 0) + break; + + tempData.push(data.substring(cursor, nextMatch.start)); + var text = normalize(this.matches.restore(nextMatch.match), data, nextMatch.start); + tempData.push(text); + + cursor = nextMatch.end; + } + + return tempData.length > 0 ? + tempData.join('') + data.substring(cursor, data.length) : + data; +}; + +module.exports = FreeTextProcessor; diff --git a/lib/text/free.js b/lib/text/free.js deleted file mode 100644 index 97f24e5b..00000000 --- a/lib/text/free.js +++ /dev/null @@ -1,26 +0,0 @@ -(function() { - var EscapeStore = require('./escape-store'); - var QuoteScanner = require('./quote-scanner'); - - var Free = function Free() { - this.matches = new EscapeStore('FREE_TEXT'); - }; - - // Strip content tags by replacing them by the a special - // marker for further restoring. It's done via string scanning - // instead of regexps to speed up the process. - Free.prototype.escape = function(data) { - var self = this; - - return new QuoteScanner(data).each(function(match, store) { - var placeholder = self.matches.store(match); - store.push(placeholder); - }); - }; - - Free.prototype.restore = function(data) { - return data.replace(this.matches.placeholderRegExp, this.matches.restore); - }; - - module.exports = Free; -})(); diff --git a/test/text/free-text-processor-test.js b/test/text/free-text-processor-test.js new file mode 100644 index 00000000..0fea779c --- /dev/null +++ b/test/text/free-text-processor-test.js @@ -0,0 +1,69 @@ +var vows = require('vows'); +var assert = require('assert'); +var FreeTextProcessor = require('../../lib/text/free-text-processor'); + +function processorContext(context) { + var vowContext = {}; + + function escaped (targetCSS) { + return function (sourceCSS) { + var result = new FreeTextProcessor().escape(sourceCSS); + assert.equal(result, targetCSS); + }; + } + + function restored (targetCSS) { + return function (sourceCSS) { + var processor = new FreeTextProcessor(); + var result = processor.restore(processor.escape(sourceCSS)); + assert.equal(result, targetCSS); + }; + } + + for (var key in context) { + vowContext[key] = { + topic: context[key][0], + escaped: escaped(context[key][1]), + restored: restored(context[key][2]) + }; + } + + return vowContext; +} + +vows.describe(FreeTextProcessor) + .addBatch( + processorContext({ + 'no quotes': [ + 'a{color:red;display:block}', + 'a{color:red;display:block}', + 'a{color:red;display:block}' + ], + 'single quoted': [ + 'a{color:red;content:\'1234\';display:block}', + 'a{color:red;content:__ESCAPED_FREE_TEXT_CLEAN_CSS0__;display:block}', + 'a{color:red;content:\'1234\';display:block}' + ], + 'double quoted': [ + 'a{color:red;content:"1234";display:block}', + 'a{color:red;content:__ESCAPED_FREE_TEXT_CLEAN_CSS0__;display:block}', + 'a{color:red;content:"1234";display:block}' + ], + 'inside format': [ + '@font-face{font-family:X;src:X.ttf format(\'opentype\')}', + '@font-face{font-family:X;src:X.ttf format(__ESCAPED_FREE_TEXT_CLEAN_CSS0__)}', + '@font-face{font-family:X;src:X.ttf format(\'opentype\')}' + ], + 'attribute': [ + 'a[data-type="search"]{}', + 'a[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{}', + 'a[data-type=search]{}' + ], + 'font name': [ + 'a{font-family:"Times","Times New Roman",serif}', + 'a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0__,__ESCAPED_FREE_TEXT_CLEAN_CSS1__,serif}', + 'a{font-family:Times,"Times New Roman",serif}' + ] + }) + ) + .export(module);