From b99f1c4c369bf9e6da8e0d7e5149809d71c37ccb Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Wed, 20 Jan 2016 20:21:47 +0800 Subject: [PATCH] strips space between tag attributes --- README.md | 3 ++- benchmark.conf | 1 + src/htmlminifier.js | 22 +++++++++++++------ src/htmlparser.js | 6 ++++-- tests/minifier.js | 51 ++++++++++++++++++++++++++++++++++++++------- 5 files changed, 67 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ad0b2b7..746ce3f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe | `collapseInlineTagWhitespace` | Don't leave any spaces between `display:inline;` elements when collapsing. Must be used in conjunction with `collapseWhitespace=true` | `false` | | `preserveLineBreaks` | Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break. Must be used in conjunction with `collapseWhitespace=true` | `false` | | `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` | +| `removeTagWhitespace` | Remove space between attributes whenever possible. | `false` | | `removeAttributeQuotes` | [Remove quotes around attributes when possible.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` | | `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` | | `preventAttributesEscaping` | Prevents the escaping of the values of attributes. | `false` | @@ -61,7 +62,7 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe | `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `'
'`) | `[ ]` | | `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. ``) | `[ ]` | | `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng\-class/`) | | -| `quoteCharacter` | Type of quote to use for attribute values (' or ") | " | +| `quoteCharacter` | Type of quote to use for attribute values (' or ") | | ## Special cases diff --git a/benchmark.conf b/benchmark.conf index 7d63462..9c21889 100644 --- a/benchmark.conf +++ b/benchmark.conf @@ -5,6 +5,7 @@ "collapseWhitespace": true, "conservativeCollapse": false, "collapseBooleanAttributes": true, + "removeTagWhitespace": true, "removeAttributeQuotes": true, "removeRedundantAttributes": true, "useShortDoctype": true, diff --git a/src/htmlminifier.js b/src/htmlminifier.js index 8d490ec..cb82093 100644 --- a/src/htmlminifier.js +++ b/src/htmlminifier.js @@ -429,24 +429,30 @@ } } emittedAttrValue = attrQuote + attrValue + attrQuote; + if (!isLast && !options.removeTagWhitespace) { + emittedAttrValue += ' '; + } } // make sure trailing slash is not interpreted as HTML self-closing tag - else if (isLast && (hasUnarySlash || /\/$/.test(attrValue))) { - emittedAttrValue = attrValue + ' '; + else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) { + emittedAttrValue = attrValue; } else { - emittedAttrValue = attrValue; + emittedAttrValue = attrValue + ' '; } if (attrValue === undefined || (options.collapseBooleanAttributes && isBooleanAttribute(attrName, attrValue))) { attrFragment = attrName; + if (!isLast) { + attrFragment += ' '; + } } else { attrFragment = attrName + attr.customAssign + emittedAttrValue; } - return (' ' + attr.customOpen + attrFragment + attr.customClose); + return attr.customOpen + attrFragment + attr.customClose; } function setDefaultTesters(options) { @@ -676,8 +682,8 @@ lint.testElement(tag); } + var parts = [ ]; var token, isLast = true; - var insert = buffer.length; for (var i = attrs.length; --i >= 0; ) { if (lint) { lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].value); @@ -685,9 +691,13 @@ token = normalizeAttribute(attrs[i], attrs, tag, hasUnarySlash, i, options, isLast); if (token) { isLast = false; - buffer.splice(insert, 0, token); + parts.unshift(token); } } + if (parts.length > 0) { + buffer.push(' '); + buffer.push.apply(buffer, parts); + } buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>'); }, diff --git a/src/htmlparser.js b/src/htmlparser.js index c1e07c9..d9c1c61 100644 --- a/src/htmlparser.js +++ b/src/htmlparser.js @@ -91,7 +91,9 @@ // Capture the custom attribute opening and closing markup surrounding the standard attribute rules attrClauses[i] = '(?:\\s*' + handler.customAttrSurround[i][0].source + + '\\s*' + startTagAttrs.source + + '\\s*' + handler.customAttrSurround[i][1].source + ')'; } @@ -125,9 +127,9 @@ var attrClauses = []; for ( var i = handler.customAttrSurround.length - 1; i >= 0; i-- ) { attrClauses[i] = '(?:' - + '(' + handler.customAttrSurround[i][0].source + ')' + + '(' + handler.customAttrSurround[i][0].source + ')\\s*' + singleAttr.source - + '(' + handler.customAttrSurround[i][1].source + ')' + + '\\s*(' + handler.customAttrSurround[i][1].source + ')' + ')'; } attrClauses.unshift('(?:' + singleAttr.source + ')'); diff --git a/tests/minifier.js b/tests/minifier.js index 18533b8..e625fd0 100644 --- a/tests/minifier.js +++ b/tests/minifier.js @@ -545,7 +545,7 @@ input = ''; equal(minify(input, customAttrOptions), input); - input = ''; + input = ''; equal(minify(input, customAttrOptions), input); input = ''; @@ -564,8 +564,11 @@ input = ''; equal(minify(input, customAttrOptions), ''); - input = ''; - equal(minify(input, customAttrOptions), ''); + input = ''; + equal(minify(input, customAttrOptions), ''); + + customAttrOptions.keepClosingSlash = true; + equal(minify(input, customAttrOptions), ''); }); test('preserving custom attribute-joining markup', function() { @@ -1312,25 +1315,25 @@ equal(minify('