var UglifyJS = require('uglify-js');
var utils = require('./utils');
-var trimWhitespace = String.prototype.trim ? function(str) {
+function trimWhitespace(str) {
if (typeof str !== 'string') {
return str;
}
- return str.trim();
-} : function(str) {
- if (typeof str !== 'string') {
- return str;
- }
- return str.replace(/^\s+/, '').replace(/\s+$/, '');
-};
+ return str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
+}
function collapseWhitespaceAll(str) {
- return str && str.replace(/\s+/g, function(spaces) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ return str && str.replace(/[ \n\r\t\f\xA0]+/g, function(spaces) {
return spaces === '\t' ? '\t' : spaces.replace(/(^|\xA0+)[^\xA0]+/g, '$1 ');
});
}
var lineBreakBefore = '', lineBreakAfter = '';
if (options.preserveLineBreaks) {
- str = str.replace(/^\s*?[\n\r]\s*/, function() {
+ str = str.replace(/^[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*/, function() {
lineBreakBefore = '\n';
return '';
- }).replace(/\s*?[\n\r]\s*$/, function() {
+ }).replace(/[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*$/, function() {
lineBreakAfter = '\n';
return '';
});
}
if (trimLeft) {
- str = str.replace(/^\s+/, function(spaces) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ str = str.replace(/^[ \n\r\t\f\xA0]+/, function(spaces) {
var conservative = !lineBreakBefore && options.conservativeCollapse;
if (conservative && spaces === '\t') {
return '\t';
}
if (trimRight) {
- str = str.replace(/\s+$/, function(spaces) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ str = str.replace(/[ \n\r\t\f\xA0]+$/, function(spaces) {
var conservative = !lineBreakAfter && options.conservativeCollapse;
if (conservative && spaces === '\t') {
return '\t';
return collapseWhitespace(chunk, {
preserveLineBreaks: options.preserveLineBreaks,
conservativeCollapse: !options.trimCustomFragments
- }, /^\s/.test(chunk), /\s$/.test(chunk));
+ }, /^[ \n\r\t\f]/.test(chunk), /[ \n\r\t\f]$/.test(chunk));
}
return chunk;
});
assert.equal(minify(input, { collapseWhitespace: true }), output);
});
+QUnit.test('types of whitespace that should always be preserved', function(assert) {
+ // Hair space:
+ var input = '<div>\u200afo\u200ao\u200a</div>';
+ assert.equal(minify(input, { collapseWhitespace: true }), input);
+
+ // Hair space passed as HTML entity:
+ var inputWithEntities = '<div> fo o </div>';
+ assert.equal(minify(inputWithEntities, { collapseWhitespace: true }), inputWithEntities);
+
+ // Hair space passed as HTML entity, in decodeEntities:true mode:
+ assert.equal(minify(inputWithEntities, { collapseWhitespace: true, decodeEntities: true }), input);
+
+
+ // Non-breaking space:
+ input = '<div>\xa0fo\xa0o\xa0</div>';
+ assert.equal(minify(input, { collapseWhitespace: true }), input);
+
+ // Non-breaking space passed as HTML entity:
+ inputWithEntities = '<div> fo o </div>';
+ assert.equal(minify(inputWithEntities, { collapseWhitespace: true }), inputWithEntities);
+
+ // Non-breaking space passed as HTML entity, in decodeEntities:true mode:
+ assert.equal(minify(inputWithEntities, { collapseWhitespace: true, decodeEntities: true }), input);
+
+ // Do not remove hair space when preserving line breaks between tags:
+ input = '<p></p>\u200a\n<p></p>\n';
+ assert.equal(minify(input, { collapseWhitespace: true, preserveLineBreaks: true }), input);
+
+ // Preserve hair space in attributes:
+ input = '<p class="foo\u200abar"></p>';
+ assert.equal(minify(input, { collapseWhitespace: true }), input);
+
+ // Preserve hair space in class names when deduplicating and reordering:
+ input = '<a class="0 1\u200a3 2 3"></a>';
+ assert.equal(minify(input, { sortClassName: false }), input);
+ assert.equal(minify(input, { sortClassName: true }), input);
+});
+
QUnit.test('doctype normalization', function(assert) {
var input;