Skip to content

Commit

Permalink
Merge pull request #540 from alexlamsl/parser-end-tag
Browse files Browse the repository at this point in the history
Omit auto-generated tags
  • Loading branch information
kangax committed Mar 10, 2016
2 parents 8ebac07 + b51c0db commit 6f89b55
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe
| `minifyJS` | Minify Javascript in script elements and event attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `false`, `Object` (options)) |
| `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `false`, `Object` (options)) |
| `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `Object` (options)) |
| `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
| `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ ]` |
| `ignoreCustomFragments` | Array of regex'es that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
| `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. `text/ng-template`, `text/x-handlebars-template`, etc.) | `[ ]` |
Expand Down
18 changes: 11 additions & 7 deletions dist/htmlminifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
return '';
});

parseEndTag( '', stackedTag );
parseEndTag( '</' + stackedTag + '>', stackedTag );
}

if ( html === last ) {
Expand Down Expand Up @@ -358,7 +358,7 @@
// Close all the open elements, up the stack
for ( var i = stack.length - 1; i >= pos; i-- ) {
if ( handler.end ) {
handler.end( stack[ i ].tag, stack[ i ].attrs );
handler.end( stack[ i ].tag, stack[ i ].attrs, i > pos || !tag );
}
}

Expand Down Expand Up @@ -1112,10 +1112,12 @@
return attr.customOpen + attrFragment + attr.customClose;
}

function setDefaultTesters(options) {
function processOptions(options) {
if (!('includeAutoGeneratedTags' in options)) {
options.includeAutoGeneratedTags = true;
}

var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];

for (var i = 0, len = defaultTesters.length; i < len; i++) {
if (!options[defaultTesters[i]]) {
options[defaultTesters[i]] = function() {
Expand Down Expand Up @@ -1248,7 +1250,7 @@
function minify(value, options, partialMarkup) {
options = options || {};
var optionsStack = [];
setDefaultTesters(options);
processOptions(options);
value = options.collapseWhitespace ? trimWhitespace(value) : value;

var buffer = [ ],
Expand Down Expand Up @@ -1438,7 +1440,7 @@

buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
},
end: function(tag, attrs) {
end: function(tag, attrs, autoGenerated) {
var lowerTag = tag.toLowerCase();
if (lowerTag === 'svg') {
options = optionsStack.pop();
Expand Down Expand Up @@ -1491,7 +1493,9 @@
}
else {
// push out everything but the end tag
buffer.push('</' + tag + '>');
if (options.includeAutoGeneratedTags || !autoGenerated) {
buffer.push('</' + tag + '>');
}
charsPrevTag = '/' + tag;
if (!inlineTextTags(tag)) {
currentChars = '';
Expand Down
2 changes: 1 addition & 1 deletion dist/htmlminifier.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sample-cli-config-file.conf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"caseSensitive": false,
"minifyJS": true,
"minifyCSS": true,
"includeAutoGeneratedTags": false,
"ignoreCustomComments": [],
"processScripts": []
}
14 changes: 9 additions & 5 deletions src/htmlminifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,12 @@
return attr.customOpen + attrFragment + attr.customClose;
}

function setDefaultTesters(options) {
function processOptions(options) {
if (!('includeAutoGeneratedTags' in options)) {
options.includeAutoGeneratedTags = true;
}

var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];

for (var i = 0, len = defaultTesters.length; i < len; i++) {
if (!options[defaultTesters[i]]) {
options[defaultTesters[i]] = function() {
Expand Down Expand Up @@ -734,7 +736,7 @@
function minify(value, options, partialMarkup) {
options = options || {};
var optionsStack = [];
setDefaultTesters(options);
processOptions(options);
value = options.collapseWhitespace ? trimWhitespace(value) : value;

var buffer = [ ],
Expand Down Expand Up @@ -924,7 +926,7 @@

buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
},
end: function(tag, attrs) {
end: function(tag, attrs, autoGenerated) {
var lowerTag = tag.toLowerCase();
if (lowerTag === 'svg') {
options = optionsStack.pop();
Expand Down Expand Up @@ -977,7 +979,9 @@
}
else {
// push out everything but the end tag
buffer.push('</' + tag + '>');
if (options.includeAutoGeneratedTags || !autoGenerated) {
buffer.push('</' + tag + '>');
}
charsPrevTag = '/' + tag;
if (!inlineTextTags(tag)) {
currentChars = '';
Expand Down
4 changes: 2 additions & 2 deletions src/htmlparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
return '';
});

parseEndTag( '', stackedTag );
parseEndTag( '</' + stackedTag + '>', stackedTag );
}

if ( html === last ) {
Expand Down Expand Up @@ -353,7 +353,7 @@
// Close all the open elements, up the stack
for ( var i = stack.length - 1; i >= pos; i-- ) {
if ( handler.end ) {
handler.end( stack[ i ].tag, stack[ i ].attrs );
handler.end( stack[ i ].tag, stack[ i ].attrs, i > pos || !tag );
}
}

Expand Down
79 changes: 50 additions & 29 deletions tests/minifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,103 +418,103 @@

test('cleaning class/style attributes', function() {
input = '<p class=" foo bar ">foo bar baz</p>';
equal(minify(input, { cleanAttributes: true }), '<p class="foo bar">foo bar baz</p>');
equal(minify(input), '<p class="foo bar">foo bar baz</p>');

input = '<p class=" foo ">foo bar baz</p>';
equal(minify(input, { cleanAttributes: true }), '<p class="foo">foo bar baz</p>');
equal(minify(input, { cleanAttributes: true, removeAttributeQuotes: true }), '<p class=foo>foo bar baz</p>');
equal(minify(input), '<p class="foo">foo bar baz</p>');
equal(minify(input, { removeAttributeQuotes: true }), '<p class=foo>foo bar baz</p>');

input = '<p class="\n \n foo \n\n\t \t\n ">foo bar baz</p>';
output = '<p class="foo">foo bar baz</p>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<p class="\n \n foo \n\n\t \t\n class1 class-23 ">foo bar baz</p>';
output = '<p class="foo class1 class-23">foo bar baz</p>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<p style=" color: red; background-color: rgb(100, 75, 200); "></p>';
output = '<p style="color: red; background-color: rgb(100, 75, 200)"></p>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<p style="font-weight: bold ; "></p>';
output = '<p style="font-weight: bold"></p>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);
});

test('cleaning URI-based attributes', function() {
input = '<a href=" http://example.com ">x</a>';
output = '<a href="http://example.com">x</a>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<a href=" \t\t \n \t ">x</a>';
output = '<a href="">x</a>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<img src=" http://example.com " title="bleh " longdesc=" http://example.com/longdesc \n\n \t ">';
output = '<img src="http://example.com" title="bleh " longdesc="http://example.com/longdesc">';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<img src="" usemap=" http://example.com ">';
output = '<img src="" usemap="http://example.com">';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<form action=" somePath/someSubPath/someAction?foo=bar&baz=qux "></form>';
output = '<form action="somePath/someSubPath/someAction?foo=bar&baz=qux"></form>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<BLOCKQUOTE cite=" \n\n\n http://www.mycom.com/tolkien/twotowers.html "><P>foobar</P></BLOCKQUOTE>';
output = '<blockquote cite="http://www.mycom.com/tolkien/twotowers.html"><p>foobar</p></blockquote>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<head profile=" http://gmpg.org/xfn/11 "></head>';
output = '<head profile="http://gmpg.org/xfn/11"></head>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<object codebase=" http://example.com "></object>';
output = '<object codebase="http://example.com"></object>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<span profile=" 1, 2, 3 ">foo</span>';
equal(minify(input, { cleanAttributes: true }), input);
equal(minify(input), input);

input = '<div action=" foo-bar-baz ">blah</div>';
equal(minify(input, { cleanAttributes: true }), input);
equal(minify(input), input);
});

test('cleaning Number-based attributes', function() {
input = '<a href="#" tabindex=" 1 ">x</a><button tabindex=" 2 ">y</button>';
output = '<a href="#" tabindex="1">x</a><button tabindex="2">y</button>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<input value="" maxlength=" 5 ">';
output = '<input value="" maxlength="5">';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<select size=" 10 \t\t "><option>x</option></select>';
output = '<select size="10"><option>x</option></select>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<textarea rows=" 20 " cols=" 30 "></textarea>';
output = '<textarea rows="20" cols="30"></textarea>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<COLGROUP span=" 40 "><COL span=" 39 "></COLGROUP>';
output = '<colgroup span="40"><col span="39"></colgroup>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<tr><td colspan=" 2 ">x</td><td rowspan=" 3 "></td></tr>';
output = '<tr><td colspan="2">x</td><td rowspan="3"></td></tr>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);
});

test('cleaning other attributes', function() {
input = '<a href="#" onclick=" window.prompt(\'boo\'); " onmouseover=" \n\n alert(123) \t \n\t ">blah</a>';
output = '<a href="#" onclick="window.prompt(\'boo\')" onmouseover="alert(123)">blah</a>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);

input = '<body onload=" foo(); bar() ; "><p>x</body>';
output = '<body onload="foo(); bar()"><p>x</p></body>';
equal(minify(input, { cleanAttributes: true }), output);
equal(minify(input), output);
});

test('removing redundant attributes (&lt;form method="get" ...>)', function() {
Expand Down Expand Up @@ -579,16 +579,16 @@

test('removing redundant attributes (&lt;... = "javascript: ..." ...>)', function() {
input = '<p onclick="javascript:alert(1)">x</p>';
equal(minify(input, { cleanAttributes: true }), '<p onclick="alert(1)">x</p>');
equal(minify(input), '<p onclick="alert(1)">x</p>');

input = '<p onclick="javascript:x">x</p>';
equal(minify(input, { cleanAttributes: true, removeAttributeQuotes: true }), '<p onclick=x>x</p>');
equal(minify(input, { removeAttributeQuotes: true }), '<p onclick=x>x</p>');

input = '<p onclick=" JavaScript: x">x</p>';
equal(minify(input, { cleanAttributes: true }), '<p onclick="x">x</p>');
equal(minify(input), '<p onclick="x">x</p>');

input = '<p title="javascript:(function() { /* some stuff here */ })()">x</p>';
equal(minify(input, { cleanAttributes: true }), input);
equal(minify(input), input);
});

test('removing type="text/javascript" attributes', function() {
Expand Down Expand Up @@ -2021,6 +2021,27 @@
}), output);
});

test('auto-generated tags', function() {
input = '<p id=""class=""title="">x';
output = '<p id="" class="" title="">x';
equal(minify(input, { includeAutoGeneratedTags: false }), output);
output = '<p id="" class="" title="">x</p>';
equal(minify(input), output);
equal(minify(input, { includeAutoGeneratedTags: true }), output);

input = '<body onload=" foo(); bar() ; "><p>x</body>';
output = '<body onload="foo(); bar()"><p>x</body>';
equal(minify(input, { includeAutoGeneratedTags: false }), output);

input = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
output = '<a href="#"><div>Well, look at me! I\'m a div!</div>';

equal(minify(input, { html5: false, includeAutoGeneratedTags: false }), output);

var options = { maxLineLength: 25, includeAutoGeneratedTags: false };
equal(minify('<p id=""class=""title="">x', options), '<p id="" class="" \ntitle="">x');
});

test('tests from PHPTAL', function() {
[
// trailing </p> removed by minifier, but not by PHPTAL
Expand Down

0 comments on commit 6f89b55

Please sign in to comment.