* Adds prototypal OO.
* Adds basic tests.
};
this.errors = this.context.errors;
this.warnings = this.context.warnings;
- this.lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
};
CleanCSS.prototype.minify = function(data, callback) {
var stats = this.stats;
var options = this.options;
var context = this.context;
- var lineBreak = this.lineBreak;
- var commentsProcessor = new CommentsProcessor(
- 'keepSpecialComments' in options ? options.keepSpecialComments : '*',
- options.keepBreaks,
- lineBreak
- );
+ var commentsProcessor = new CommentsProcessor('keepSpecialComments' in options ? options.keepSpecialComments : '*', options.keepBreaks);
var expressionsProcessor = new ExpressionsProcessor();
var freeTextProcessor = new FreeTextProcessor();
var urlsProcessor = new UrlsProcessor();
--- /dev/null
+var EscapeStore = require('./escape-store');
+var QuoteScanner = require('./quote-scanner');
+
+var SPECIAL_COMMENT_PREFIX = '/*!';
+var COMMENT_PREFIX = '/*';
+var COMMENT_SUFFIX = '*/';
+
+var lineBreak = require('os').EOL;
+
+var CommentsProcessor = function CommentsProcessor(keepSpecialComments, keepBreaks) {
+ this.comments = new EscapeStore('COMMENT');
+ this.keepAll = keepSpecialComments == '*';
+ this.keepOne = keepSpecialComments == '1' || keepSpecialComments === 1;
+ this.keepBreaks = keepBreaks;
+};
+
+function quoteScannerFor(data) {
+ var quoteMap = [];
+ new QuoteScanner(data).each(function (quotedString, _, startsAt) {
+ quoteMap.push([startsAt, startsAt + quotedString.length]);
+ });
+
+ return function (position) {
+ for (var i = 0, l = quoteMap.length; i < l; i++) {
+ if (quoteMap[i][0] < position && quoteMap[i][1] > position)
+ return true;
+ }
+
+ return false;
+ };
+}
+
+CommentsProcessor.prototype.escape = function (data) {
+ var tempData = [];
+ var nextStart = 0;
+ var nextEnd = 0;
+ var cursor = 0;
+ var isQuotedAt = quoteScannerFor(data);
+
+ for (; nextEnd < data.length;) {
+ nextStart = data.indexOf(COMMENT_PREFIX, cursor);
+ if (nextStart == -1)
+ break;
+
+ if (isQuotedAt(nextStart)) {
+ tempData.push(data.substring(cursor, nextStart + COMMENT_PREFIX.length));
+ cursor = nextStart + COMMENT_PREFIX.length;
+ continue;
+ }
+
+ nextEnd = data.indexOf(COMMENT_SUFFIX, nextStart + COMMENT_PREFIX.length);
+ if (nextEnd == -1)
+ break;
+
+ tempData.push(data.substring(cursor, nextStart));
+
+ var comment = data.substring(nextStart, nextEnd + COMMENT_SUFFIX.length);
+ if (comment.indexOf(SPECIAL_COMMENT_PREFIX) === 0) {
+ var placeholder = this.comments.store(comment);
+ tempData.push(placeholder);
+ }
+
+ cursor = nextEnd + COMMENT_SUFFIX.length;
+ }
+
+ return tempData.length > 0 ?
+ tempData.join('') + data.substring(cursor, data.length) :
+ data;
+};
+
+CommentsProcessor.prototype.restore = function (data) {
+ var tempData = [];
+ var restored = 0;
+ var cursor = 0;
+ var addBreak;
+
+ for (; cursor < data.length;) {
+ var nextMatch = this.comments.nextMatch(data, cursor);
+ if (nextMatch.start < 0)
+ break;
+
+ tempData.push(data.substring(cursor, nextMatch.start));
+ var comment = this.comments.restore(nextMatch.match);
+
+ if (comment.indexOf(SPECIAL_COMMENT_PREFIX) === 0 && (this.keepAll || (this.keepOne && restored === 0))) {
+ restored++;
+ addBreak = this.keepBreaks && data[nextMatch.end] != '\n' && data[nextMatch.end] != '\r\n';
+ tempData.push(comment, addBreak ? lineBreak : '');
+ } else {
+ nextMatch.end += this.keepBreaks ? 1 : 0;
+ }
+
+ cursor = nextMatch.end;
+ }
+
+ return tempData.length > 0 ?
+ tempData.join('') + data.substring(cursor, data.length) :
+ data;
+};
+
+module.exports = CommentsProcessor;
+++ /dev/null
-var EscapeStore = require('./escape-store');
-var QuoteScanner = require('./quote-scanner');
-
-module.exports = function Comments(keepSpecialComments, keepBreaks, lineBreak) {
- var comments = new EscapeStore('COMMENT');
-
- return {
- // Strip special comments (/*! ... */) by replacing them by a special marker
- // for further restoring. Plain comments are removed. It's done by scanning data using
- // String#indexOf scanning instead of regexps to speed up the process.
- escape: function(data) {
- var tempData = [];
- var nextStart = 0;
- var nextEnd = 0;
- var cursor = 0;
- var isQuotedAt = (function () {
- var quoteMap = [];
- new QuoteScanner(data).each(function (quotedString, _, startsAt) {
- quoteMap.push([startsAt, startsAt + quotedString.length]);
- });
-
- return function (position) {
- for (var i = 0, l = quoteMap.length; i < l; i++) {
- if (quoteMap[i][0] < position && quoteMap[i][1] > position)
- return true;
- }
-
- return false;
- };
- })();
-
- for (; nextEnd < data.length;) {
- nextStart = data.indexOf('/*', cursor);
- if (nextStart == -1)
- break;
- if (isQuotedAt(nextStart)) {
- tempData.push(data.substring(cursor, nextStart + 2));
- cursor = nextStart + 2;
- continue;
- }
-
- nextEnd = data.indexOf('*/', nextStart + 2);
- if (nextEnd == -1)
- break;
-
- tempData.push(data.substring(cursor, nextStart));
- if (data[nextStart + 2] == '!') {
- // in case of special comments, replace them with a placeholder
- var comment = data.substring(nextStart, nextEnd + 2);
- var placeholder = comments.store(comment);
- tempData.push(placeholder);
- }
- cursor = nextEnd + 2;
- }
-
- return tempData.length > 0 ?
- tempData.join('') + data.substring(cursor, data.length) :
- data;
- },
-
- restore: function(data) {
- var restored = 0;
- var breakSuffix = keepBreaks ? lineBreak : '';
-
- return data.replace(new RegExp(comments.placeholderPattern + '(' + lineBreak + '| )?', 'g'), function(match, placeholder) {
- restored++;
-
- switch (keepSpecialComments) {
- case '*':
- return comments.restore(placeholder) + breakSuffix;
- case 1:
- case '1':
- return restored == 1 ?
- comments.restore(placeholder) + breakSuffix :
- '';
- case 0:
- case '0':
- return '';
- }
- });
- }
- };
-};
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+var CommentsProcessor = require('../../lib/text/comments-processor');
+
+var lineBreak = require('os').EOL;
+
+function processorContext(name, context, keepSpecialComments, keepBreaks) {
+ var vowContext = {};
+
+ function escaped (targetCSS) {
+ return function (sourceCSS) {
+ var result = new CommentsProcessor(keepSpecialComments, keepBreaks).escape(sourceCSS);
+ assert.equal(result, targetCSS);
+ };
+ }
+
+ function restored (targetCSS) {
+ return function (sourceCSS) {
+ var processor = new CommentsProcessor(keepSpecialComments, keepBreaks);
+ var result = processor.restore(processor.escape(sourceCSS));
+ assert.equal(result, targetCSS);
+ };
+ }
+
+ for (var key in context) {
+ vowContext[name + ' - ' + key] = {
+ topic: context[key][0],
+ escaped: escaped(context[key][1]),
+ restored: restored(context[key][2])
+ };
+ }
+
+ return vowContext;
+}
+
+vows.describe(CommentsProcessor)
+ .addBatch(
+ processorContext('all', {
+ 'no comments': [
+ 'a{color:red}',
+ 'a{color:red}',
+ 'a{color:red}'
+ ],
+ 'one comment': [
+ '/* some text */',
+ '',
+ ''
+ ],
+ 'one special comment': [
+ '/*! some text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0__',
+ '/*! some text */'
+ ],
+ 'two comments': [
+ '/* one text *//* another text */',
+ '',
+ ''
+ ],
+ 'two same comments': [
+ '/* one text *//* one text */',
+ '',
+ ''
+ ],
+ 'two special comments': [
+ '/*! one text *//*! another text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0____ESCAPED_COMMENT_CLEAN_CSS1__',
+ '/*! one text *//*! another text */'
+ ],
+ 'commented selector': [
+ '/* a{color:red} */',
+ '',
+ ''
+ ],
+ 'quoted comment': [
+ 'a{content:"/* text */"}',
+ 'a{content:"/* text */"}',
+ 'a{content:"/* text */"}'
+ ]
+ }, '*')
+ )
+ .addBatch(
+ processorContext('one', {
+ 'one comment': [
+ '/* some text */',
+ '',
+ ''
+ ],
+ 'one special comment': [
+ '/*! some text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0__',
+ '/*! some text */'
+ ],
+ 'two special comments': [
+ '/*! one text *//*! another text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0____ESCAPED_COMMENT_CLEAN_CSS1__',
+ '/*! one text */'
+ ]
+ }, '1')
+ )
+ .addBatch(
+ processorContext('zero', {
+ 'one comment': [
+ '/* some text */',
+ '',
+ ''
+ ],
+ 'one special comment': [
+ '/*! some text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0__',
+ ''
+ ],
+ 'two special comments': [
+ '/*! one text *//*! another text */',
+ '__ESCAPED_COMMENT_CLEAN_CSS0____ESCAPED_COMMENT_CLEAN_CSS1__',
+ ''
+ ]
+ }, '0')
+ )
+ .addBatch(
+ processorContext('zero with breaks', {
+ 'content and special comments': [
+ 'a{}' + lineBreak + '/*! some text */' + lineBreak + 'p{}',
+ 'a{}' + lineBreak + '__ESCAPED_COMMENT_CLEAN_CSS0__' + lineBreak + 'p{}',
+ 'a{}' + lineBreak + 'p{}'
+ ]
+ }, '0', true)
+ )
+ .addBatch(
+ processorContext('one with breaks', {
+ 'forces break after comments': [
+ 'a{}/*! some text */p{}',
+ 'a{}__ESCAPED_COMMENT_CLEAN_CSS0__p{}',
+ 'a{}/*! some text */' + lineBreak + 'p{}'
+ ],
+ 'if not given already comments': [
+ 'a{}/*! some text */' + lineBreak + 'p{}',
+ 'a{}__ESCAPED_COMMENT_CLEAN_CSS0__' + lineBreak + 'p{}',
+ 'a{}/*! some text */' + lineBreak + 'p{}'
+ ]
+ }, '1', true)
+ )
+ .export(module);