/** * Clean-css - https://github.com/GoalSmashers/clean-css * Released under the terms of MIT license * * Copyright (C) 2011-2013 GoalSmashers.com */ var ColorShortener = require('./colors/shortener'); var ColorHSLToHex = require('./colors/hsl-to-hex'); var ColorRGBToHex = require('./colors/rgb-to-hex'); var ColorLongToShortHex = require('./colors/long-to-short-hex'); var ShorthandNotations = require('./properties/shorthand-notations'); var ImportInliner = require('./imports/inliner'); var UrlRebase = require('./images/url-rebase'); var CommentsProcessor = require('./text/comments'); var ExpressionsProcessor = require('./text/expressions'); var FreeTextProcessor = require('./text/free'); var UrlsProcessor = require('./text/urls'); var CleanCSS = { process: function(data, options) { var replace = function() { if (typeof arguments[0] == 'function') arguments[0](); else data = data.replace.apply(data, arguments); }; var lineBreak = process.platform == 'win32' ? '\r\n' : '\n'; this.lineBreak = lineBreak; options = options || {}; options.keepBreaks = options.keepBreaks || false; //active by default if (options.processImport === undefined) options.processImport = true; // replace function if (options.benchmark) { var originalReplace = replace; replace = function(pattern, replacement) { var name = typeof pattern == 'function' ? /function (\w+)\(/.exec(pattern.toString())[1] : pattern; var start = process.hrtime(); originalReplace(pattern, replacement); var itTook = process.hrtime(start); console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000.0); }; } var commentsProcessor = new CommentsProcessor( 'keepSpecialComments' in options ? options.keepSpecialComments : '*', options.keepBreaks, lineBreak ); var expressionsProcessor = new ExpressionsProcessor(); var freeTextProcessor = new FreeTextProcessor(); var urlsProcessor = new UrlsProcessor(); var importInliner = new ImportInliner(); if (options.processImport) { // inline all imports replace(function inlineImports() { data = importInliner.process(data, { root: options.root || process.cwd(), relativeTo: options.relativeTo }); }); } this.originalSize = data.length; replace(function escapeComments() { data = commentsProcessor.escape(data); }); // replace all escaped line breaks replace(/\\(\r\n|\n)/mg, ''); // strip parentheses in urls if possible (no spaces inside) replace(/url\((['"])([^\)]+)['"]\)/g, function(match, quote, url) { if (url.match(/[ \t]/g) !== null || url.indexOf('data:') === 0) return 'url(' + quote + url + quote + ')'; else return 'url(' + url + ')'; }); // strip parentheses in animation & font names replace(/(animation|animation\-name|font|font\-family):([^;}]+)/g, function(match, propertyName, fontDef) { return propertyName + ':' + fontDef.replace(/['"]([\w\-]+)['"]/g, '$1'); }); // strip parentheses in @keyframes replace(/@(\-moz\-|\-o\-|\-webkit\-)?keyframes ([^{]+)/g, function(match, prefix, name) { prefix = prefix || ''; return '@' + prefix + 'keyframes ' + (name.indexOf(' ') > -1 ? name : name.replace(/['"]/g, '')); }); // IE shorter filters, but only if single (IE 7 issue) replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\([^\)]+\))([;}'"])/g, function(match, filter, args, suffix) { return filter.toLowerCase() + args + suffix; }); replace(function escapeExpressions() { data = expressionsProcessor.escape(data); }); // strip parentheses in attribute values replace(/\[([^\]]+)\]/g, function(match, content) { var eqIndex = content.indexOf('='); var singleQuoteIndex = content.indexOf('\''); var doubleQuoteIndex = content.indexOf('"'); if (eqIndex < 0 && singleQuoteIndex < 0 && doubleQuoteIndex < 0) return match; if (singleQuoteIndex === 0 || doubleQuoteIndex === 0) return match; var key = content.substring(0, eqIndex); var value = content.substring(eqIndex + 1, content.length); if (/^['"](?:[a-zA-Z][a-zA-Z\d\-_]+)['"]$/.test(value)) return '[' + key + '=' + value.substring(1, value.length - 1) + ']'; else return match; }); replace(function escapeFreeText() { data = freeTextProcessor.escape(data); }); replace(function escapeUrls() { data = urlsProcessor.escape(data); }); // line breaks if (!options.keepBreaks) replace(/[\r]?\n/g, ' '); // multiple whitespace replace(/[\t ]+/g, ' '); // multiple semicolons (with optional whitespace) replace(/;[ ]?;+/g, ';'); // multiple line breaks to one replace(/ (?:\r\n|\n)/g, lineBreak); replace(/(?:\r\n|\n)+/g, lineBreak); // remove spaces around selectors replace(/ ([+~>]) /g, '$1'); // remove extra spaces inside content replace(/([!\(\{\}:;=,\n]) /g, '$1'); replace(/ ([!\)\{\};=,\n])/g, '$1'); replace(/(?:\r\n|\n)\}/g, '}'); replace(/([\{;,])(?:\r\n|\n)/g, '$1'); replace(/ :([^\{\};]+)([;}])/g, ':$1$2'); // restore spaces inside IE filters (IE 7 issue) replace(/progid:[^(]+\(([^\)]+)/g, function(match) { return match.replace(/,/g, ', '); }); // trailing semicolons replace(/;\}/g, '}'); replace(function hsl2Hex() { data = new ColorHSLToHex(data).process(); }); replace(function rgb2Hex() { data = new ColorRGBToHex(data).process(); }); replace(function longToShortHex() { data = new ColorLongToShortHex(data).process(); }); replace(function shortenColors() { data = new ColorShortener(data).process(); }); // replace font weight with numerical value replace(/(font\-weight|font):(normal|bold)([ ;\}!])(\w*)/g, function(match, property, weight, suffix, next) { if (suffix == ' ' && next.length > 0 && !/[.\d]/.test(next)) return match; if (weight == 'normal') return property + ':400' + suffix + next; else if (weight == 'bold') return property + ':700' + suffix + next; else return match; }); // zero + unit to zero replace(/(\s|:|,)0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, '$1' + '0'); replace(/rect\(0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, 'rect(0'); // fraction zeros removal replace(/\.([1-9]*)0+(\D)/g, function(match, nonZeroPart, suffix) { return (nonZeroPart ? '.' : '') + nonZeroPart + suffix; }); // restore 0% in hsl/hsla replace(/(hsl|hsla)\(([^\)]+)\)/g, function(match, colorFunction, colorDef) { var tokens = colorDef.split(','); if (tokens[1] == '0') tokens[1] = '0%'; if (tokens[2] == '0') tokens[2] = '0%'; return colorFunction + '(' + tokens.join(',') + ')'; }); // none to 0 replace(/(border|border-top|border-right|border-bottom|border-left|outline):none/g, '$1:0'); // background:none to background:0 0 replace(/background:none([;}])/g, 'background:0 0$1'); // multiple zeros into one replace(/box-shadow:0 0 0 0([^\.])/g, 'box-shadow:0 0$1'); replace(/:0 0 0 0([^\.])/g, ':0$1'); replace(/([: ,=\-])0\.(\d)/g, '$1.$2'); replace(function shorthandNotations() { data = new ShorthandNotations(data).process(); }); // restore rect(...) zeros syntax for 4 zeros replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)'); // remove universal selector when not needed (*#id, *.class etc) replace(/\*([\.#:\[])/g, '$1'); // Restore spaces inside calc back replace(/calc\([^\}]+\}/g, function(match) { return match.replace(/\+/g, ' + '); }); replace(function restoreUrls() { data = urlsProcessor.restore(data); }); replace(function rebaseUrls() { data = options.noRebase ? data : UrlRebase.process(data, options); }); replace(function restoreFreeText() { data = freeTextProcessor.restore(data); }); replace(function restoreComments() { data = commentsProcessor.restore(data); }); replace(function restoreExpressions() { data = expressionsProcessor.restore(data); }); // move first charset to the beginning replace(function moveCharset() { // get first charset in stylesheet var match = data.match(/@charset [^;]+;/); var firstCharset = match ? match[0] : null; if (!firstCharset) return; // reattach first charset and remove all subsequent data = firstCharset + (options.keepBreaks ? lineBreak : '') + data.replace(new RegExp('@charset [^;]+;(' + lineBreak + ')?', 'g'), ''); }); if (options.removeEmpty) { // empty elements replace(/[^\{\}]+\{\}/g, ''); // empty @media declarations replace(/@media [^\{]+\{\}/g, ''); } // trim spaces at beginning and end return data.trim(); } }; module.exports = CleanCSS;