improve trailing whitespace minification
authoralexlamsl <alexlamsl@gmail.com>
Sat, 16 Apr 2016 12:15:51 +0000 (20:15 +0800)
committeralexlamsl <alexlamsl@gmail.com>
Sat, 16 Apr 2016 12:15:51 +0000 (20:15 +0800)
src/htmlminifier.js
tests/minifier.js

index 0418217..f3db4f5 100644 (file)
@@ -850,30 +850,32 @@ function minify(value, options, partialMarkup) {
     buffer.length = Math.max(0, index);
   }
 
+  // look for trailing whitespaces, bypass any inline tags
+  function trimTrailingWhitespace(index, nextTag) {
+    for (var endTag = null; index >= 0 && _canTrimWhitespace(endTag); index--) {
+      var str = buffer[index];
+      var match = str.match(/^<\/([\w:-]+)>$/);
+      if (match) {
+        endTag = match[1];
+      }
+      else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
+        break;
+      }
+    }
+  }
+
   // look for trailing whitespaces from previously processed text
   // which may not be trimmed due to a following comment or an empty
   // element which has now been removed
   function squashTrailingWhitespace(nextTag) {
-    var charsIndex;
-    if (buffer.length > 1 && /^(?:<!|$)/.test(buffer[buffer.length - 1]) &&
-        /\s$/.test(buffer[buffer.length - 2])) {
-      charsIndex = buffer.length - 2;
-    }
-    else if (buffer.length > 0 && /\s$/.test(buffer[buffer.length - 1])) {
-      charsIndex = buffer.length - 1;
-    }
-    if (charsIndex >= 0) {
-      buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(text) {
-        return collapseWhitespaceSmart(text, 'comment', nextTag, options);
-      });
-    }
-  }
-
-  function trimPrevCharsTrailingWhitespace() {
-    var charsIndex = buffer.length - 2;
-    if (charsIndex >= 0) {
-      buffer[charsIndex] = collapseWhitespace(buffer[charsIndex], options, false, true);
+    var charsIndex = buffer.length - 1;
+    if (buffer.length > 1) {
+      var item = buffer[buffer.length - 1];
+      if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
+        charsIndex--;
+      }
     }
+    trimTrailingWhitespace(charsIndex, nextTag);
   }
 
   new HTMLParser(value, {
@@ -1071,7 +1073,7 @@ function minify(value, options, partialMarkup) {
           if (prevTag) {
             if (prevTag === '/nobr') {
               if (/^\s/.test(text)) {
-                trimPrevCharsTrailingWhitespace();
+                trimTrailingWhitespace(buffer.length - 1, 'br');
               }
             }
             else if (inlineTextTags(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
@@ -1080,16 +1082,7 @@ function minify(value, options, partialMarkup) {
           }
           text = prevTag || nextTag ? collapseWhitespaceSmart(text, prevTag, nextTag, options) : trimWhitespace(text);
           if (!text && /\s$/.test(currentChars) && prevTag && prevTag.charAt(0) === '/') {
-            for (var index = buffer.length - 1, endTag = prevTag.slice(1); index >= 0 && _canTrimWhitespace(endTag); index--) {
-              var str = buffer[index];
-              var match = str.match(/^<\/([\w:-]+)>$/);
-              if (match) {
-                endTag = match[1];
-              }
-              else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
-                break;
-              }
-            }
+            trimTrailingWhitespace(buffer.length - 1, nextTag);
           }
         }
         if (!stackNoCollapseWhitespace.length) {
@@ -1182,6 +1175,9 @@ function minify(value, options, partialMarkup) {
       removeEndTag();
     }
   }
+  if (options.collapseWhitespace) {
+    squashTrailingWhitespace('br');
+  }
 
   var str = joinResultSegments(buffer, options);
 
index e07221d..0ca58cf 100644 (file)
@@ -152,6 +152,14 @@ test('space normalization around text', function() {
     'mark', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
     'time', 'tt', 'u', 'var'
   ].forEach(function(el) {
+    equal(minify('foo <' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
+    equal(minify('foo<' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
+    equal(minify('foo <' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
+    equal(minify('foo<' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
+    equal(minify('foo <' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz </' + el + '>bar');
+    equal(minify('foo<' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '> baz </' + el + '>bar');
+    equal(minify('foo <' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz </' + el + '>bar');
+    equal(minify('foo<' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '> baz </' + el + '>bar');
     equal(minify('<div>foo <' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '> bar</div>');
     equal(minify('<div>foo<' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '>bar</div>');
     equal(minify('<div>foo <' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
@@ -164,6 +172,14 @@ test('space normalization around text', function() {
   [
     'bdi', 'bdo', 'button', 'cite', 'code', 'dfn', 'math', 'q', 'rt', 'rp', 'svg'
   ].forEach(function(el) {
+    equal(minify('foo <' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
+    equal(minify('foo<' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
+    equal(minify('foo <' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
+    equal(minify('foo<' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
+    equal(minify('foo <' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
+    equal(minify('foo<' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
+    equal(minify('foo <' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
+    equal(minify('foo<' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
     equal(minify('<div>foo <' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '> bar</div>');
     equal(minify('<div>foo<' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '>bar</div>');
     equal(minify('<div>foo <' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
@@ -173,26 +189,33 @@ test('space normalization around text', function() {
     equal(minify('<div>foo <' + el + '> baz </' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
     equal(minify('<div>foo<' + el + '> baz </' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '> bar</div>');
   });
-  equal(minify('<nobr>a</nobr>', { collapseWhitespace: true }), '<nobr>a</nobr>');
-  equal(minify('<nobr>a </nobr>', { collapseWhitespace: true }), '<nobr>a </nobr>');
-  equal(minify('<nobr> a</nobr>', { collapseWhitespace: true }), '<nobr>a</nobr>');
-  equal(minify('<nobr> a </nobr>', { collapseWhitespace: true }), '<nobr>a </nobr>');
-  equal(minify('a<nobr>b</nobr>c', { collapseWhitespace: true }), 'a<nobr>b</nobr>c');
-  equal(minify('a<nobr>b </nobr>c', { collapseWhitespace: true }), 'a<nobr>b </nobr>c');
-  equal(minify('a<nobr> b</nobr>c', { collapseWhitespace: true }), 'a<nobr> b</nobr>c');
-  equal(minify('a<nobr> b </nobr>c', { collapseWhitespace: true }), 'a<nobr> b </nobr>c');
-  equal(minify('a<nobr>b</nobr> c', { collapseWhitespace: true }), 'a<nobr>b</nobr> c');
-  equal(minify('a<nobr>b </nobr> c', { collapseWhitespace: true }), 'a<nobr>b</nobr> c');
-  equal(minify('a<nobr> b</nobr> c', { collapseWhitespace: true }), 'a<nobr> b</nobr> c');
-  equal(minify('a<nobr> b </nobr> c', { collapseWhitespace: true }), 'a<nobr> b</nobr> c');
-  equal(minify('a <nobr>b</nobr>c', { collapseWhitespace: true }), 'a <nobr>b</nobr>c');
-  equal(minify('a <nobr>b </nobr>c', { collapseWhitespace: true }), 'a <nobr>b </nobr>c');
-  equal(minify('a <nobr> b</nobr>c', { collapseWhitespace: true }), 'a <nobr>b</nobr>c');
-  equal(minify('a <nobr> b </nobr>c', { collapseWhitespace: true }), 'a <nobr>b </nobr>c');
-  equal(minify('a <nobr>b</nobr> c', { collapseWhitespace: true }), 'a <nobr>b</nobr> c');
-  equal(minify('a <nobr>b </nobr> c', { collapseWhitespace: true }), 'a <nobr>b</nobr> c');
-  equal(minify('a <nobr> b</nobr> c', { collapseWhitespace: true }), 'a <nobr>b</nobr> c');
-  equal(minify('a <nobr> b </nobr> c', { collapseWhitespace: true }), 'a <nobr>b</nobr> c');
+  [
+    ['<span> foo </span>', '<span>foo</span>'],
+    [' <span> foo </span> ', '<span>foo</span>'],
+    ['<nobr>a</nobr>', '<nobr>a</nobr>'],
+    ['<nobr>a </nobr>', '<nobr>a</nobr>'],
+    ['<nobr> a</nobr>', '<nobr>a</nobr>'],
+    ['<nobr> a </nobr>', '<nobr>a</nobr>'],
+    ['a<nobr>b</nobr>c', 'a<nobr>b</nobr>c'],
+    ['a<nobr>b </nobr>c', 'a<nobr>b </nobr>c'],
+    ['a<nobr> b</nobr>c', 'a<nobr> b</nobr>c'],
+    ['a<nobr> b </nobr>c', 'a<nobr> b </nobr>c'],
+    ['a<nobr>b</nobr> c', 'a<nobr>b</nobr> c'],
+    ['a<nobr>b </nobr> c', 'a<nobr>b</nobr> c'],
+    ['a<nobr> b</nobr> c', 'a<nobr> b</nobr> c'],
+    ['a<nobr> b </nobr> c', 'a<nobr> b</nobr> c'],
+    ['a <nobr>b</nobr>c', 'a <nobr>b</nobr>c'],
+    ['a <nobr>b </nobr>c', 'a <nobr>b </nobr>c'],
+    ['a <nobr> b</nobr>c', 'a <nobr>b</nobr>c'],
+    ['a <nobr> b </nobr>c', 'a <nobr>b </nobr>c'],
+    ['a <nobr>b</nobr> c', 'a <nobr>b</nobr> c'],
+    ['a <nobr>b </nobr> c', 'a <nobr>b</nobr> c'],
+    ['a <nobr> b</nobr> c', 'a <nobr>b</nobr> c'],
+    ['a <nobr> b </nobr> c', 'a <nobr>b</nobr> c']
+  ].forEach(function(inputs) {
+    equal(minify(inputs[0], { collapseWhitespace: true }), inputs[1]);
+    equal(minify('<div>' + inputs[0] + '</div>', { collapseWhitespace: true }), '<div>' + inputs[1] + '</div>');
+  });
   equal(minify('<p>foo <img> bar</p>', { collapseWhitespace: true }), '<p>foo <img> bar</p>');
   equal(minify('<p>foo<img>bar</p>', { collapseWhitespace: true }), '<p>foo<img>bar</p>');
   equal(minify('<p>foo <img>bar</p>', { collapseWhitespace: true }), '<p>foo <img>bar</p>');