Fix minifier issue with prefixed tags (closing)
authorEven André Fiskvik <eaf@oyatel.com>
Thu, 18 Apr 2013 09:43:53 +0000 (11:43 +0200)
committerEven André Fiskvik <eaf@oyatel.com>
Thu, 18 Apr 2013 09:43:53 +0000 (11:43 +0200)
dist/all.js
package.json
src/htmlminifier.js
src/htmlparser.js
tests/minifier.js

index 674b649..f0cc986 100644 (file)
 (function(global){
 
   // Regular Expressions for parsing tags and attributes
-  var startTag = /^<(\w+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
-      endTag = /^<\/(\w+)[^>]*>/,
+  var startTag = /^<([\w:-]+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
+      endTag = /^<\/([\w:-]+)[^>]*>/,
       attr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
       doctype = /^<!DOCTYPE [^>]+>/i;
-
+    
   // Empty Elements - HTML 4.01
   var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
 
 
   // Special Elements (can contain anything)
   var special = makeMap("script,style");
-
+  
   var reCache = { }, stackedTag, re;
 
   var HTMLParser = global.HTMLParser = function( html, handler ) {
-    var index, chars, match, stack = [], last = html;
+    var index, chars, match, stack = [], last = html, prevTag, nextTag;
     stack.last = function(){
       return this[ this.length - 1 ];
     };
@@ -68,7 +68,7 @@
         // Comment
         if ( html.indexOf("<!--") === 0 ) {
           index = html.indexOf("-->");
-
+  
           if ( index >= 0 ) {
             if ( handler.comment )
               handler.comment( html.substring( 4, index ) );
             handler.doctype( match[0] );
           html = html.substring( match[0].length );
           chars = false;
-
+        
         // end tag
         } else if ( html.indexOf("</") === 0 ) {
           match = html.match( endTag );
-
+  
           if ( match ) {
             html = html.substring( match[0].length );
             match[0].replace( endTag, parseEndTag );
+            prevTag = '/'+match[1];
             chars = false;
           }
-
+  
         // start tag
         } else if ( html.indexOf("<") === 0 ) {
           match = html.match( startTag );
-
+  
           if ( match ) {
             html = html.substring( match[0].length );
             match[0].replace( startTag, parseStartTag );
+            prevTag = match[1];
             chars = false;
           }
         }
 
         if ( chars ) {
           index = html.indexOf("<");
-
+          
           var text = index < 0 ? html : html.substring( 0, index );
           html = index < 0 ? "" : html.substring( index );
-
+          
+          // next tag
+          tagMatch = html.match( startTag );
+          if (tagMatch) {
+               nextTag = tagMatch[1];
+          } else {
+               tagMatch = html.match( endTag );
+               if (tagMatch) {
+                 nextTag = '/'+tagMatch[1];
+               } else {
+                     nextTag = '';
+               }
+          }
+          
           if ( handler.chars )
-            handler.chars( text );
+               handler.chars(text, prevTag, nextTag);
+          
         }
 
       } else {
-
+        
         stackedTag = stack.last().toLowerCase();
         reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp("([\\s\\S]*?)<\/" + stackedTag + "[^>]*>", "i"));
-
+        
         html = html.replace(reStackedTag, function(all, text) {
           if (stackedTag !== 'script' && stackedTag !== 'style') {
             text = text
         throw "Parse Error: " + html;
       last = html;
     }
-
+    
     // Clean up any remaining tags
     parseEndTag();
 
 
       if ( !unary )
         stack.push( tagName );
-
+      
       if ( handler.start ) {
         var attrs = [];
-
+  
         rest.replace(attr, function(match, name) {
           var value = arguments[2] ? arguments[2] :
             arguments[3] ? arguments[3] :
             escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
           });
         });
-
+  
         if ( handler.start )
           handler.start( tagName, attrs, unary );
       }
       // If no tag name is provided, clean shop
       if ( !tagName )
         var pos = 0;
-
+        
       // Find the closest opened tag of the same type
       else
         for ( var pos = stack.length - 1; pos >= 0; pos-- )
           if ( stack[ pos ] == tagName )
             break;
-
+      
       if ( pos >= 0 ) {
         // Close all the open elements, up the stack
         for ( var i = stack.length - 1; i >= pos; i-- )
           if ( handler.end )
             handler.end( stack[ i ] );
-
+        
         // Remove the open elements from the stack
         stack.length = pos;
       }
     }
   };
-
+  
   global.HTMLtoXML = function( html ) {
     var results = "";
-
+    
     HTMLParser(html, {
       start: function( tag, attrs, unary ) {
         results += "<" + tag;
-
+    
         for ( var i = 0; i < attrs.length; i++ )
           results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
-
+    
         results += (unary ? "/" : "") + ">";
       },
       end: function( tag ) {
         results += "<!--" + text + "-->";
       }
     });
-
+    
     return results;
   };
-
+  
   global.HTMLtoDOM = function( html, doc ) {
     // There can be only one of these elements
     var one = makeMap("html,head,body,title");
-
+    
     // Enforce a structure for the document
     var structure = {
       link: "head",
       base: "head"
     };
-
+  
     if ( !doc ) {
       if ( typeof DOMDocument != "undefined" )
         doc = new DOMDocument();
         doc = document.implementation.createDocument("", "", null);
       else if ( typeof ActiveX != "undefined" )
         doc = new ActiveXObject("Msxml.DOMDocument");
-
+      
     } else
       doc = doc.ownerDocument ||
         doc.getOwnerDocument && doc.getOwnerDocument() ||
         doc;
-
+    
     var elems = [],
       documentElement = doc.documentElement ||
         doc.getDocumentElement && doc.getDocumentElement();
-
+        
     // If we're dealing with an empty document then we
     // need to pre-populate it with the HTML document structure
     if ( !documentElement && doc.createElement ) (function(){
       html.appendChild( doc.createElement("body") );
       doc.appendChild( html );
     })();
-
+    
     // Find all the unique elements
     if ( doc.getElementsByTagName )
       for ( var i in one )
         one[ i ] = doc.getElementsByTagName( i )[0];
-
+    
     // If we're working with a document, inject contents into
     // the body element
     var curParentNode = one.body;
-
+    
     HTMLParser( html, {
       start: function( tagName, attrs, unary ) {
         // If it's a pre-built element, then we can ignore
           curParentNode = one[ tagName ];
           return;
         }
-
+      
         var elem = doc.createElement( tagName );
-
+        
         for ( var attr in attrs )
           elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
-
+        
         if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
           one[ structure[ tagName ] ].appendChild( elem );
-
+        
         else if ( curParentNode && curParentNode.appendChild )
           curParentNode.appendChild( elem );
-
+          
         if ( !unary ) {
           elems.push( elem );
           curParentNode = elem;
       },
       end: function( tag ) {
         elems.length -= 1;
-
+        
         // Init the new parentNode
         curParentNode = elems[ elems.length - 1 ];
       },
         // create comment node
       }
     });
-
+    
     return doc;
   };
 
     }
     return obj;
   }
-})(typeof exports === 'undefined' ? window : exports);/*!
- * HTMLMinifier v0.4.4
+})(typeof exports === 'undefined' ? this : exports);
+/*!
+ * HTMLMinifier v0.5.0
  * http://kangax.github.com/html-minifier/
  *
- * Copyright (c) 2010 Juriy "kangax" Zaytsev
+ * Copyright (c) 2010-2013 Juriy "kangax" Zaytsev
  * Licensed under the MIT license.
  *
  */
     HTMLParser = require('./htmlparser').HTMLParser;
   }
 
-  function trimWhitespace(str) {
+  var trimWhitespace = function(str) {
     return str.replace(/^\s+/, '').replace(/\s+$/, '');
-  }
+  };
   if (String.prototype.trim) {
     trimWhitespace = function(str) {
       return str.trim();
   function collapseWhitespace(str) {
     return str.replace(/\s+/g, ' ');
   }
+  
+  function collapseWhitespaceSmart(str, prevTag, nextTag) {
+    // array of tags that will maintain a single space outside of them
+    var tags = ['a', 'b', 'big', 'button', 'em', 'font','i',  'img', 'mark', 's', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'tt', 'u'];
+    
+    if (prevTag && (prevTag.substr(0,1) !== '/'
+      || ( prevTag.substr(0,1) === '/' && tags.indexOf(prevTag.substr(1)) === -1))) {
+      str = str.replace(/^\s+/, '');
+    }
+    
+    if (nextTag && (nextTag.substr(0,1) === '/'
+      || ( nextTag.substr(0,1) !== '/' && tags.indexOf(nextTag) === -1))) {
+      str = str.replace(/\s+$/, '');
+    } 
+    
+    if (prevTag && nextTag) {
+      // strip non space whitespace then compress spaces to one
+      return str.replace(/[\t\n\r]+/g, '').replace(/[ ]+/g, ' ');
+    }
+    
+    return str;
+  }
 
   function isConditionalComment(text) {
-    return (/\[if[^\]]+\]/).test(text);
+    return ((/\[if[^\]]+\]/).test(text) || (/\s*(<!\[endif\])$/).test(text));
   }
 
   function isEventAttribute(attrName) {
       (tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
       (tag === 'colgroup' && attrName === 'span') ||
       (tag === 'col' && attrName === 'span') ||
-      ((tag === 'th' || tag == 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
+      ((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
     );
   }
 
       if (!options[defaultTesters[i]]) {
         options[defaultTesters[i]] = function() {
           return false;
-        }
+        };
       }
     }
   }
         stackNoTrimWhitespace = [],
         stackNoCollapseWhitespace = [],
         lint = options.lint,
-        t = new Date()
+        t = new Date();
 
     function _canCollapseWhitespace(tag, attrs) {
       return canCollapseWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
     }
 
     HTMLParser(value, {
-      start: function( tag, attrs, unary ) {
+      start: function( tag, attrs ) {
         tag = tag.toLowerCase();
         currentTag = tag;
         currentChars = '';
         // check if current tag is in a whitespace stack
         if (options.collapseWhitespace) {
           if (stackNoTrimWhitespace.length &&
-            tag == stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
+            tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
             stackNoTrimWhitespace.pop();
           }
           if (stackNoCollapseWhitespace.length &&
-            tag == stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
+            tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
             stackNoCollapseWhitespace.pop();
           }
         }
         buffer.length = 0;
         currentChars = '';
       },
-      chars: function( text ) {
+      chars: function( text, prevTag, nextTag ) {
         if (currentTag === 'script' || currentTag === 'style') {
           if (options.removeCommentsFromCDATA) {
             text = removeComments(text, currentTag);
         }
         if (options.collapseWhitespace) {
           if (!stackNoTrimWhitespace.length && _canTrimWhitespace(currentTag, currentAttrs)) {
-            text = trimWhitespace(text);
+            text = (prevTag || nextTag) ? collapseWhitespaceSmart(text, prevTag, nextTag) : trimWhitespace(text);
           }
           if (!stackNoCollapseWhitespace.length && _canCollapseWhitespace(currentTag, currentAttrs)) {
             text = collapseWhitespace(text);
       }
     });
 
-    results.push.apply(results, buffer)
+    results.push.apply(results, buffer);
     var str = results.join('');
     log('minified in: ' + (new Date() - t) + 'ms');
     return str;
   }
 
-  // export
-  global.minify = minify;
+  // for CommonJS enviroments, export everything
+  if ( typeof exports !== "undefined" ) {
+    exports.minify = minify;
+  } else {
+    global.minify = minify;
+  }
 
-})(typeof exports === 'undefined' ? window : exports);/*!
+}(this));
+/*!
  * HTMLLint (to be used in conjunction with HTMLMinifier)
  *
- * Copyright (c) 2010 Juriy "kangax" Zaytsev
+ * Copyright (c) 2010-2013 Juriy "kangax" Zaytsev
  * Licensed under the MIT license.
  *
  */
 
   global.HTMLLint = Lint;
 
-})(typeof exports === 'undefined' ? window : exports);
+})(typeof exports === 'undefined' ? this : exports);
index 1354e32..a7acf2e 100644 (file)
@@ -29,7 +29,7 @@
     "src": "./src"
   },
   "scripts": {
-    "test": "jshint src/htmlminifier.js src/htmllint.js && node test.js"
+    "test": "jshint src/htmlminifier.js src/htmllint.js && cat src/htmlparser.js src/htmlminifier.js src/htmllint.js > dist/all.js && node test.js"
   },
   "devDependencies": {
     "qunit": "0.5.x",
index e172b70..ff4fe7d 100644 (file)
   }
   
   function collapseWhitespaceSmart(str, prevTag, nextTag) {
-       // array of tags that will maintain a single space outside of them
-       var tags = ['a', 'b', 'big', 'button', 'em', 'font','i',  'img', 'mark', 's', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'tt', 'u'];
-       
-       if (prevTag && (prevTag.substr(0,1) !== '/'
-               || ( prevTag.substr(0,1) === '/' && tags.indexOf(prevTag.substr(1)) === -1))) {
-               str = str.replace(/^\s+/, '');
-       }
-       
-       if (nextTag && (nextTag.substr(0,1) === '/'
-               || ( nextTag.substr(0,1) !== '/' && tags.indexOf(nextTag) === -1))) {
-               str = str.replace(/\s+$/, '');
-       
-       
-       if (prevTag && nextTag) {
-               // strip non space whitespace then compress spaces to one
-               return str.replace(/[\t\n\r]+/g, '').replace(/[ ]+/g, ' ');
-       }
-       
+    // array of tags that will maintain a single space outside of them
+    var tags = ['a', 'b', 'big', 'button', 'em', 'font','i',  'img', 'mark', 's', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'tt', 'u'];
+    
+    if (prevTag && (prevTag.substr(0,1) !== '/'
+      || ( prevTag.substr(0,1) === '/' && tags.indexOf(prevTag.substr(1)) === -1))) {
+      str = str.replace(/^\s+/, '');
+    }
+    
+    if (nextTag && (nextTag.substr(0,1) === '/'
+      || ( nextTag.substr(0,1) !== '/' && tags.indexOf(nextTag) === -1))) {
+      str = str.replace(/\s+$/, '');
+    } 
+    
+    if (prevTag && nextTag) {
+      // strip non space whitespace then compress spaces to one
+      return str.replace(/[\t\n\r]+/g, '').replace(/[ ]+/g, ' ');
+    }
+    
     return str;
   }
 
index 0559f59..69803b4 100644 (file)
@@ -28,7 +28,7 @@
 
   // Regular Expressions for parsing tags and attributes
   var startTag = /^<([\w:-]+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
-      endTag = /^<\/(\w+)[^>]*>/,
+      endTag = /^<\/([\w:-]+)[^>]*>/,
       attr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
       doctype = /^<!DOCTYPE [^>]+>/i;
     
index 3ab1baf..a56a686 100644 (file)
     equal(minify('<a title="x"href=" ">foo</a>'), '<a title="x" href="">foo</a>');
     equal(minify('<p id=""class=""title="">x'), '<p id="" class="" title="">x</p>');
     equal(minify('<p x="x\'"">x</p>'), '<p x="x\'">x</p>', 'trailing quote should be ignored');
+    
+    equal(minify('<ng-include src="x"></ng-include>'), '<ng-include src="x"></ng-include>');
+    equal(minify('<ng:include src="x"></ng:include>'), '<ng:include src="x"></ng:include>');
+    equal(minify('<ng-include src="\'views/partial-notification.html\'"></ng-include><div ng-view></div>'), '<ng-include src="\'views/partial-notification.html\'"></ng-include><div ng-view=""></div>');
   });
 
   test('`minifiy` exists', function() {