JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.5.3
[ckeditor.git] / _source / core / htmlparser / fragment.js
index f94f4e9..8c3ad5d 100644 (file)
@@ -37,38 +37,35 @@ CKEDITOR.htmlParser.fragment = function()
 \r
 (function()\r
 {\r
-       // Elements which the end tag is marked as optional in the HTML 4.01 DTD\r
-       // (expect empty elements).\r
-       var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1};\r
-\r
        // Block-level elements whose internal structure should be respected during\r
        // parser fixing.\r
-       var nonBreakingBlocks = CKEDITOR.tools.extend(\r
-                       {table:1,ul:1,ol:1,dl:1},\r
-                       CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl ),\r
-               listBlocks = CKEDITOR.dtd.$list, listItems = CKEDITOR.dtd.$listItem;\r
+       var nonBreakingBlocks = CKEDITOR.tools.extend( { table:1,ul:1,ol:1,dl:1 }, CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl );\r
+\r
+       var listBlocks = { ol:1, ul:1 };\r
+\r
+       // Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan <li>.\r
+       var rootDtd = CKEDITOR.tools.extend( {}, { html: 1 }, CKEDITOR.dtd.html, CKEDITOR.dtd.body, CKEDITOR.dtd.head, { style:1,script:1 } );\r
 \r
        /**\r
         * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.\r
         * @param {String} fragmentHtml The HTML to be parsed, filling the fragment.\r
         * @param {Number} [fixForBody=false] Wrap body with specified element if needed.\r
+        * @param {CKEDITOR.htmlParser.element} contextNode Parse the html as the content of this element.\r
         * @returns CKEDITOR.htmlParser.fragment The fragment created.\r
         * @example\r
         * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );\r
         * alert( fragment.children[0].name );  "b"\r
         * alert( fragment.children[1].value );  " Text"\r
         */\r
-       CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody )\r
+       CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody, contextNode )\r
        {\r
                var parser = new CKEDITOR.htmlParser(),\r
-                       html = [],\r
-                       fragment = new CKEDITOR.htmlParser.fragment(),\r
+                       fragment = contextNode || new CKEDITOR.htmlParser.fragment(),\r
                        pendingInline = [],\r
                        pendingBRs = [],\r
                        currentNode = fragment,\r
                    // Indicate we're inside a <pre> element, spaces should be touched differently.\r
-                       inPre = false,\r
-                       returnPoint;\r
+                       inPre = false;\r
 \r
                function checkPending( newTagName )\r
                {\r
@@ -108,19 +105,37 @@ CKEDITOR.htmlParser.fragment = function()
                        }\r
                }\r
 \r
-               function sendPendingBRs( brsToIgnore )\r
+               function sendPendingBRs()\r
                {\r
-                       while ( pendingBRs.length - ( brsToIgnore || 0 ) > 0 )\r
+                       while ( pendingBRs.length )\r
                                currentNode.add( pendingBRs.shift() );\r
                }\r
 \r
-               function addElement( element, target, enforceCurrent )\r
+               /*\r
+               * Beside of simply append specified element to target, this function also takes\r
+               * care of other dirty lifts like forcing block in body, trimming spaces at\r
+               * the block boundaries etc.\r
+               *\r
+               * @param {Element} element  The element to be added as the last child of {@link target}.\r
+               * @param {Element} target The parent element to relieve the new node.\r
+               * @param {Boolean} [moveCurrent=false] Don't change the "currentNode" global unless\r
+               * there's a return point node specified on the element, otherwise move current onto {@link target} node.\r
+                */\r
+               function addElement( element, target, moveCurrent )\r
                {\r
+                       // Ignore any element that has already been added.\r
+                       if ( element.previous !== undefined )\r
+                               return;\r
+\r
                        target = target || currentNode || fragment;\r
 \r
+                       // Current element might be mangled by fix body below,\r
+                       // save it for restore later.\r
+                       var savedCurrent = currentNode;\r
+\r
                        // If the target is the fragment and this inline element can't go inside\r
                        // body (if fixForBody).\r
-                       if ( fixForBody && !target.type )\r
+                       if ( fixForBody && ( !target.type || target.name == 'body' ) )\r
                        {\r
                                var elementName, realElementName;\r
                                if ( element.attributes\r
@@ -130,19 +145,14 @@ CKEDITOR.htmlParser.fragment = function()
                                else\r
                                        elementName =  element.name;\r
 \r
-                               if ( elementName && elementName in CKEDITOR.dtd.$inline )\r
+                               if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) )\r
                                {\r
-                                       var savedCurrent = currentNode;\r
-\r
                                        // Create a <p> in the fragment.\r
                                        currentNode = target;\r
                                        parser.onTagOpen( fixForBody, {} );\r
 \r
                                        // The new target now is the <p>.\r
-                                       target = currentNode;\r
-\r
-                                       if ( enforceCurrent )\r
-                                               currentNode = savedCurrent;\r
+                                       element.returnPoint = target = currentNode;\r
                                }\r
                        }\r
 \r
@@ -170,9 +180,11 @@ CKEDITOR.htmlParser.fragment = function()
                                currentNode = element.returnPoint;\r
                                delete element.returnPoint;\r
                        }\r
+                       else\r
+                               currentNode = moveCurrent ? target : savedCurrent;\r
                }\r
 \r
-               parser.onTagOpen = function( tagName, attributes, selfClosing )\r
+               parser.onTagOpen = function( tagName, attributes, selfClosing, optionalClose )\r
                {\r
                        var element = new CKEDITOR.htmlParser.element( tagName, attributes );\r
 \r
@@ -181,6 +193,8 @@ CKEDITOR.htmlParser.fragment = function()
                        if ( element.isUnknown && selfClosing )\r
                                element.isEmpty = true;\r
 \r
+                       element.isOptionalClose = optionalClose;\r
+\r
                        // This is a tag to be removed if empty, so do not add it immediately.\r
                        if ( CKEDITOR.dtd.$removeEmpty[ tagName ] )\r
                        {\r
@@ -201,89 +215,77 @@ CKEDITOR.htmlParser.fragment = function()
                                return;\r
                        }\r
 \r
-                       var currentName = currentNode.name;\r
-\r
-                       var currentDtd = currentName\r
-                               && ( CKEDITOR.dtd[ currentName ]\r
-                                       || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );\r
-\r
-                       // If the element cannot be child of the current element.\r
-                       if ( currentDtd   // Fragment could receive any elements.\r
-                                && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )\r
+                       while( 1 )\r
                        {\r
+                               var currentName = currentNode.name;\r
 \r
-                               var reApply = false,\r
-                                       addPoint;   // New position to start adding nodes.\r
+                               var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ]\r
+                                               || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) )\r
+                                               : rootDtd;\r
 \r
-                               // Fixing malformed nested lists by moving it into a previous list item. (#3828)\r
-                               if ( tagName in listBlocks\r
-                                       && currentName in listBlocks )\r
+                               // If the element cannot be child of the current element.\r
+                               if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )\r
                                {\r
-                                       var children = currentNode.children,\r
-                                               lastChild = children[ children.length - 1 ];\r
+                                       // Current node doesn't have a close tag, time for a close\r
+                                       // as this element isn't fit in. (#7497)\r
+                                       if ( currentNode.isOptionalClose )\r
+                                               parser.onTagClose( currentName );\r
+                                       // Fixing malformed nested lists by moving it into a previous list item. (#3828)\r
+                                       else if ( tagName in listBlocks\r
+                                               && currentName in listBlocks )\r
+                                       {\r
+                                               var children = currentNode.children,\r
+                                                       lastChild = children[ children.length - 1 ];\r
 \r
-                                       // Establish the list item if it's not existed.\r
-                                       if ( !( lastChild && lastChild.name in listItems ) )\r
-                                               addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );\r
+                                               // Establish the list item if it's not existed.\r
+                                               if ( !( lastChild && lastChild.name == 'li' ) )\r
+                                                       addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );\r
 \r
-                                       returnPoint = currentNode, addPoint = lastChild;\r
-                               }\r
-                               // If the element name is the same as the current element name,\r
-                               // then just close the current one and append the new one to the\r
-                               // parent. This situation usually happens with <p>, <li>, <dt> and\r
-                               // <dd>, specially in IE. Do not enter in this if block in this case.\r
-                               else if ( tagName == currentName )\r
-                               {\r
-                                       addElement( currentNode, currentNode.parent );\r
-                               }\r
-                               else if ( tagName in CKEDITOR.dtd.$listItem )\r
-                               {\r
-                                       parser.onTagOpen( 'ul', {} );\r
-                                       addPoint = currentNode;\r
-                                       reApply = true;\r
-                               }\r
-                               else\r
-                               {\r
-                                       if ( nonBreakingBlocks[ currentName ] )\r
+                                               !element.returnPoint && ( element.returnPoint = currentNode );\r
+                                               currentNode = lastChild;\r
+                                       }\r
+                                       // Establish new list root for orphan list items.\r
+                                       else if ( tagName in CKEDITOR.dtd.$listItem && currentName != tagName )\r
+                                               parser.onTagOpen( tagName == 'li' ? 'ul' : 'dl', {}, 0, 1 );\r
+                                       // We're inside a structural block like table and list, AND the incoming element\r
+                                       // is not of the same type (e.g. <td>td1<td>td2</td>), we simply add this new one before it,\r
+                                       // and most importantly, return back to here once this element is added,\r
+                                       // e.g. <table><tr><td>td1</td><p>p1</p><td>td2</td></tr></table>\r
+                                       else if ( currentName in nonBreakingBlocks && currentName != tagName )\r
                                        {\r
-                                               if ( !returnPoint )\r
-                                                       returnPoint = currentNode;\r
+                                               !element.returnPoint && ( element.returnPoint = currentNode );\r
+                                               currentNode = currentNode.parent;\r
                                        }\r
                                        else\r
                                        {\r
-                                               addElement( currentNode, currentNode.parent, true );\r
+                                               // The current element is an inline element, which\r
+                                               // need to be continued even after the close, so put\r
+                                               // it in the pending list.\r
+                                               if ( currentName in CKEDITOR.dtd.$inline )\r
+                                                       pendingInline.unshift( currentNode );\r
 \r
-                                               if ( !optionalClose[ currentName ] )\r
+                                               // The most common case where we just need to close the\r
+                                               // current one and append the new one to the parent.\r
+                                               if ( currentNode.parent )\r
+                                                       addElement( currentNode, currentNode.parent, 1 );\r
+                                               // We've tried our best to fix the embarrassment here, while\r
+                                               // this element still doesn't find it's parent, mark it as\r
+                                               // orphan and show our tolerance to it.\r
+                                               else\r
                                                {\r
-                                                       // The current element is an inline element, which\r
-                                                       // cannot hold the new one. Put it in the pending list,\r
-                                                       // and try adding the new one after it.\r
-                                                       pendingInline.unshift( currentNode );\r
+                                                       element.isOrphan = 1;\r
+                                                       break;\r
                                                }\r
                                        }\r
-\r
-                                       reApply = true;\r
                                }\r
-\r
-                               if ( addPoint )\r
-                                       currentNode = addPoint;\r
-                               // Try adding it to the return point, or the parent element.\r
                                else\r
-                                       currentNode = currentNode.returnPoint || currentNode.parent;\r
-\r
-                               if ( reApply )\r
-                               {\r
-                                       parser.onTagOpen.apply( this, arguments );\r
-                                       return;\r
-                               }\r
+                                       break;\r
                        }\r
 \r
                        checkPending( tagName );\r
                        sendPendingBRs();\r
 \r
                        element.parent = currentNode;\r
-                       element.returnPoint = returnPoint;\r
-                       returnPoint = 0;\r
 \r
                        if ( element.isEmpty )\r
                                addElement( element );\r
@@ -308,7 +310,7 @@ CKEDITOR.htmlParser.fragment = function()
                                newPendingInline = [],\r
                                candidate = currentNode;\r
 \r
-                       while ( candidate.type && candidate.name != tagName )\r
+                       while ( candidate != fragment && candidate.name != tagName )\r
                        {\r
                                // If this is an inline element, add it to the pending list, if we're\r
                                // really closing one of the parents element later, they will continue\r
@@ -321,10 +323,11 @@ CKEDITOR.htmlParser.fragment = function()
                                // one of the nodes. So, for now, we just cache it.\r
                                pendingAdd.push( candidate );\r
 \r
-                               candidate = candidate.parent;\r
+                               // Make sure return point is properly restored.\r
+                               candidate = candidate.returnPoint || candidate.parent;\r
                        }\r
 \r
-                       if ( candidate.type )\r
+                       if ( candidate != fragment )\r
                        {\r
                                // Add all elements that have been found in the above loop.\r
                                for ( i = 0 ; i < pendingAdd.length ; i++ )\r
@@ -373,7 +376,7 @@ CKEDITOR.htmlParser.fragment = function()
                                 && ( !currentNode.type || currentNode.name == 'body' )\r
                                 && CKEDITOR.tools.trim( text ) )\r
                        {\r
-                               this.onTagOpen( fixForBody, {} );\r
+                               this.onTagOpen( fixForBody, {}, 0, 1 );\r
                        }\r
 \r
                        // Shrinking consequential spaces into one single for all elements\r
@@ -402,24 +405,9 @@ CKEDITOR.htmlParser.fragment = function()
                // Send all pending BRs except one, which we consider a unwanted bogus. (#5293)\r
                sendPendingBRs( !CKEDITOR.env.ie && 1 );\r
 \r
-               // Close all pending nodes.\r
-               while ( currentNode.type )\r
-               {\r
-                       var parent = currentNode.parent,\r
-                               node = currentNode;\r
-\r
-                       if ( fixForBody\r
-                                && ( !parent.type || parent.name == 'body' )\r
-                                && !CKEDITOR.dtd.$body[ node.name ] )\r
-                       {\r
-                               currentNode = parent;\r
-                               parser.onTagOpen( fixForBody, {} );\r
-                               parent = currentNode;\r
-                       }\r
-\r
-                       parent.add( node );\r
-                       currentNode = parent;\r
-               }\r
+               // Close all pending nodes, make sure return point is properly restored.\r
+               while ( currentNode != fragment )\r
+                       addElement( currentNode, currentNode.parent, 1 );\r
 \r
                return fragment;\r
        };\r