improve quote and escape
authoralexlamsl <alexlamsl@gmail.com>
Wed, 20 Jan 2016 10:44:43 +0000 (18:44 +0800)
committeralexlamsl <alexlamsl@gmail.com>
Wed, 20 Jan 2016 10:44:43 +0000 (18:44 +0800)
- avoid excessive attribute value character escaping
- choose between ' and " if quoteCharacter unspecified

src/htmlminifier.js
src/htmlparser.js
tests/minifier.js

index f43bf6f..8d490ec 100644 (file)
       if (options.minifyJS) {
         var wrappedCode = '(function(){' + attrValue + '})()';
         var minified = minifyJS(wrappedCode, options.minifyJS);
-        return minified.slice(12, minified.length - 4).replace(/"/g, '&quot;');
+        return minified.slice(12, minified.length - 4);
       }
       return attrValue;
     }
   function normalizeAttribute(attr, attrs, tag, hasUnarySlash, index, options, isLast) {
 
     var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
-        attrValue = options.preventAttributesEscaping ? attr.value : attr.escaped,
-        attrQuote = options.preventAttributesEscaping ? attr.quote : (options.quoteCharacter === '\'' ? '\'' : '"'),
+        attrValue = attr.value,
+        attrQuote = attr.quote,
         attrFragment,
         emittedAttrValue;
 
 
     if (attrValue !== undefined && !options.removeAttributeQuotes ||
         !canRemoveAttributeQuotes(attrValue)) {
+      if (!options.preventAttributesEscaping) {
+        if (options.quoteCharacter !== undefined) {
+          attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
+        }
+        else {
+          var apos = (attrValue.match(/'/g) || []).length;
+          var quot = (attrValue.match(/"/g) || []).length;
+          attrQuote = apos < quot ? '\'' : '"';
+        }
+        if (attrQuote === '"') {
+          attrValue = attrValue.replace(/"/g, '&#34;');
+        }
+        else {
+          attrValue = attrValue.replace(/'/g, '&#39;');
+        }
+      }
       emittedAttrValue = attrQuote + attrValue + attrQuote;
     }
     // make sure trailing slash is not interpreted as HTML self-closing tag
         var insert = buffer.length;
         for (var i = attrs.length; --i >= 0; ) {
           if (lint) {
-            lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].escaped);
+            lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].value);
           }
           token = normalizeAttribute(attrs[i], attrs, tag, hasUnarySlash, i, options, isLast);
           if (token) {
index 3fc5988..c1e07c9 100644 (file)
         attrs.push({
           name: name,
           value: value,
-          escaped: value && value.replace(/(^|.)("+)/g, function(match) {
-            return match.replace(/"/g, '&quot;');
-          }),
           customAssign: customAssign || '=',
           customOpen:  customOpen || '',
           customClose: customClose || '',
         results += '<' + tag;
 
         for ( var i = 0; i < attrs.length; i++ ) {
-          results += ' ' + attrs[i].name + '="' + attrs[i].escaped + '"';
+          results += ' ' + attrs[i].name + '="' + (attrs[i].value || '').replace(/"/g, '&#34;') + '"';
         }
 
         results += (unary ? '/' : '') + '>';
index 732c48b..18533b8 100644 (file)
 
   test('nested quotes', function() {
     input = '<div data=\'{"test":"\\"test\\""}\'></div>';
-    output = '<div data="{&quot;test&quot;:&quot;\\&quot;test\\&quot;&quot;}"></div>';
-    equal(minify(input), output);
+    equal(minify(input), input);
+    equal(minify(input, { quoteCharacter: '\'' }), input);
+
+    output = '<div data="{&#34;test&#34;:&#34;\\&#34;test\\&#34;&#34;}"></div>';
+    equal(minify(input, { quoteCharacter: '"' }), output);
   });
 
   test('script minification', function() {
     equal(minify(input, { minifyJS: true }), output);
 
     input = '<a onclick="try{ dcsMultiTrack(\'DCS.dcsuri\',\'USPS\',\'WT.ti\') }catch(e){}"> foobar</a>';
-    output = '<a onclick="try{dcsMultiTrack(&quot;DCS.dcsuri&quot;,&quot;USPS&quot;,&quot;WT.ti&quot;)}catch(e){}"> foobar</a>';
+    output = '<a onclick=\'try{dcsMultiTrack("DCS.dcsuri","USPS","WT.ti")}catch(e){}\'> foobar</a>';
 
     equal(minify(input, { minifyJS: { mangle: false } }), output);
+    equal(minify(input, { minifyJS: { mangle: false }, quoteCharacter: '\'' }), output);
+
+    input = '<a onclick="try{ dcsMultiTrack(\'DCS.dcsuri\',\'USPS\',\'WT.ti\') }catch(e){}"> foobar</a>';
+    output = '<a onclick="try{dcsMultiTrack(&#34;DCS.dcsuri&#34;,&#34;USPS&#34;,&#34;WT.ti&#34;)}catch(e){}"> foobar</a>';
+
+    equal(minify(input, { minifyJS: { mangle: false }, quoteCharacter: '"' }), output);
 
     input = '<a onClick="_gaq.push([\'_trackEvent\', \'FGF\', \'banner_click\']);"></a>';
-    output = '<a onclick="_gaq.push([&quot;_trackEvent&quot;,&quot;FGF&quot;,&quot;banner_click&quot;])"></a>';
+    output = '<a onclick=\'_gaq.push(["_trackEvent","FGF","banner_click"])\'></a>';
 
     equal(minify(input, { minifyJS: true }), output);
+    equal(minify(input, { minifyJS: true, quoteCharacter: '\'' }), output);
+
+    input = '<a onClick="_gaq.push([\'_trackEvent\', \'FGF\', \'banner_click\']);"></a>';
+    output = '<a onclick="_gaq.push([&#34;_trackEvent&#34;,&#34;FGF&#34;,&#34;banner_click&#34;])"></a>';
+
+    equal(minify(input, { minifyJS: true, quoteCharacter: '"' }), output);
 
     input = '<button type="button" onclick=";return false;" id="appbar-guide-button"></button>';
     output = '<button type="button" onclick="return!1" id="appbar-guide-button"></button>';