Make parser ignore block elements in inline elements (allowed in HTML5, not HTML4...
authorkangax <kangax@gmail.com>
Sat, 11 May 2013 19:05:42 +0000 (15:05 -0400)
committerkangax <kangax@gmail.com>
Sat, 11 May 2013 19:05:42 +0000 (15:05 -0400)
dist/all.js
src/htmlparser.js
tests/minifier.js

index f0cc986..7024f20 100644 (file)
@@ -31,7 +31,7 @@
       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");
 
@@ -50,7 +50,7 @@
 
   // Special Elements (can contain anything)
   var special = makeMap("script,style");
-  
+
   var reCache = { }, stackedTag, re;
 
   var HTMLParser = global.HTMLParser = function( html, handler ) {
@@ -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 );
 
         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 = '';
                }
           }
-          
+
           if ( handler.chars )
                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();
 
     function parseStartTag( tag, tagName, rest, unary ) {
-      if ( block[ tagName ] ) {
-        while ( stack.last() && inline[ stack.last() ] ) {
-          parseEndTag( "", stack.last() );
-        }
-      }
 
       if ( closeSelf[ tagName ] && stack.last() == tagName ) {
         parseEndTag( "", tagName );
 
       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;
   };
 
index 69803b4..d64c07e 100644 (file)
@@ -31,7 +31,7 @@
       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");
 
@@ -50,7 +50,7 @@
 
   // Special Elements (can contain anything)
   var special = makeMap("script,style");
-  
+
   var reCache = { }, stackedTag, re;
 
   var HTMLParser = global.HTMLParser = function( html, handler ) {
@@ -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 );
 
         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 = '';
                }
           }
-          
+
           if ( handler.chars )
                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();
 
     function parseStartTag( tag, tagName, rest, unary ) {
-      if ( block[ tagName ] ) {
-        while ( stack.last() && inline[ stack.last() ] ) {
-          parseEndTag( "", stack.last() );
-        }
-      }
 
       if ( closeSelf[ tagName ] && stack.last() == tagName ) {
         parseEndTag( "", tagName );
 
       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;
   };
 
index a56a686..c434b56 100644 (file)
@@ -24,7 +24,9 @@
     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('<a href="#"><p>Click me</p></a>'), '<a href="#"><p>Click me</p></a>');
+    equal(minify('<span><button>Hit me</button></span>'), '<span><button>Hit me</button></span>');
+
     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>');
     input = '<p> foo    bar</p>';
     output = '<p>foo bar</p>';
     equal(minify(input, { collapseWhitespace: true }), output);
-    
+
     input = '<p> foo    <span>  blah     <i>   22</i>    </span> bar <img src=""></p>';
     output = '<p>foo <span>blah <i>22</i></span> bar <img src=""></p>';
     equal(minify(input, { collapseWhitespace: true }), output);