X-Git-Url: https://jasonwoof.com/gitweb/?p=ckeditor.git;a=blobdiff_plain;f=_source%2Fcore%2Fhtmlparser%2Ffragment.js;h=8c3ad5d3b960f98564078c2e139cfc93b6280f91;hp=f94f4e917d242c61b6e99e942495f852abae6868;hb=4e90e78dc97789709ee7404359a5517540c27553;hpb=8f6c203fdaa543c3bca40baea6ae4ddcdf1a77f5 diff --git a/_source/core/htmlparser/fragment.js b/_source/core/htmlparser/fragment.js index f94f4e9..8c3ad5d 100644 --- a/_source/core/htmlparser/fragment.js +++ b/_source/core/htmlparser/fragment.js @@ -37,38 +37,35 @@ CKEDITOR.htmlParser.fragment = function() (function() { - // Elements which the end tag is marked as optional in the HTML 4.01 DTD - // (expect empty elements). - var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1}; - // Block-level elements whose internal structure should be respected during // parser fixing. - 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 ), - listBlocks = CKEDITOR.dtd.$list, listItems = CKEDITOR.dtd.$listItem; + 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 ); + + var listBlocks = { ol:1, ul:1 }; + + // Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan
  • . + var rootDtd = CKEDITOR.tools.extend( {}, { html: 1 }, CKEDITOR.dtd.html, CKEDITOR.dtd.body, CKEDITOR.dtd.head, { style:1,script:1 } ); /** * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string. * @param {String} fragmentHtml The HTML to be parsed, filling the fragment. * @param {Number} [fixForBody=false] Wrap body with specified element if needed. + * @param {CKEDITOR.htmlParser.element} contextNode Parse the html as the content of this element. * @returns CKEDITOR.htmlParser.fragment The fragment created. * @example * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); * alert( fragment.children[0].name ); "b" * alert( fragment.children[1].value ); " Text" */ - CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody ) + CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody, contextNode ) { var parser = new CKEDITOR.htmlParser(), - html = [], - fragment = new CKEDITOR.htmlParser.fragment(), + fragment = contextNode || new CKEDITOR.htmlParser.fragment(), pendingInline = [], pendingBRs = [], currentNode = fragment, // Indicate we're inside a
     element, spaces should be touched differently.
    -			inPre = false,
    -			returnPoint;
    +			inPre = false;
     
     		function checkPending( newTagName )
     		{
    @@ -108,19 +105,37 @@ CKEDITOR.htmlParser.fragment = function()
     			}
     		}
     
    -		function sendPendingBRs( brsToIgnore )
    +		function sendPendingBRs()
     		{
    -			while ( pendingBRs.length - ( brsToIgnore || 0 ) > 0 )
    +			while ( pendingBRs.length )
     				currentNode.add( pendingBRs.shift() );
     		}
     
    -		function addElement( element, target, enforceCurrent )
    +		/*
    +		* Beside of simply append specified element to target, this function also takes
    +		* care of other dirty lifts like forcing block in body, trimming spaces at
    +		* the block boundaries etc.
    +		*
    +		* @param {Element} element  The element to be added as the last child of {@link target}.
    +		* @param {Element} target The parent element to relieve the new node.
    +		* @param {Boolean} [moveCurrent=false] Don't change the "currentNode" global unless
    +		* there's a return point node specified on the element, otherwise move current onto {@link target} node.
    +		 */
    +		function addElement( element, target, moveCurrent )
     		{
    +			// Ignore any element that has already been added.
    +			if ( element.previous !== undefined )
    +				return;
    +
     			target = target || currentNode || fragment;
     
    +			// Current element might be mangled by fix body below,
    +			// save it for restore later.
    +			var savedCurrent = currentNode;
    +
     			// If the target is the fragment and this inline element can't go inside
     			// body (if fixForBody).
    -			if ( fixForBody && !target.type )
    +			if ( fixForBody && ( !target.type || target.name == 'body' ) )
     			{
     				var elementName, realElementName;
     				if ( element.attributes
    @@ -130,19 +145,14 @@ CKEDITOR.htmlParser.fragment = function()
     				else
     					elementName =  element.name;
     
    -				if ( elementName && elementName in CKEDITOR.dtd.$inline )
    +				if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) )
     				{
    -					var savedCurrent = currentNode;
    -
     					// Create a 

    in the fragment. currentNode = target; parser.onTagOpen( fixForBody, {} ); // The new target now is the

    . - target = currentNode; - - if ( enforceCurrent ) - currentNode = savedCurrent; + element.returnPoint = target = currentNode; } } @@ -170,9 +180,11 @@ CKEDITOR.htmlParser.fragment = function() currentNode = element.returnPoint; delete element.returnPoint; } + else + currentNode = moveCurrent ? target : savedCurrent; } - parser.onTagOpen = function( tagName, attributes, selfClosing ) + parser.onTagOpen = function( tagName, attributes, selfClosing, optionalClose ) { var element = new CKEDITOR.htmlParser.element( tagName, attributes ); @@ -181,6 +193,8 @@ CKEDITOR.htmlParser.fragment = function() if ( element.isUnknown && selfClosing ) element.isEmpty = true; + element.isOptionalClose = optionalClose; + // This is a tag to be removed if empty, so do not add it immediately. if ( CKEDITOR.dtd.$removeEmpty[ tagName ] ) { @@ -201,89 +215,77 @@ CKEDITOR.htmlParser.fragment = function() return; } - var currentName = currentNode.name; - - var currentDtd = currentName - && ( CKEDITOR.dtd[ currentName ] - || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ); - - // If the element cannot be child of the current element. - if ( currentDtd // Fragment could receive any elements. - && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) + while( 1 ) { + var currentName = currentNode.name; - var reApply = false, - addPoint; // New position to start adding nodes. + var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] + || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) + : rootDtd; - // Fixing malformed nested lists by moving it into a previous list item. (#3828) - if ( tagName in listBlocks - && currentName in listBlocks ) + // If the element cannot be child of the current element. + if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) { - var children = currentNode.children, - lastChild = children[ children.length - 1 ]; + // Current node doesn't have a close tag, time for a close + // as this element isn't fit in. (#7497) + if ( currentNode.isOptionalClose ) + parser.onTagClose( currentName ); + // Fixing malformed nested lists by moving it into a previous list item. (#3828) + else if ( tagName in listBlocks + && currentName in listBlocks ) + { + var children = currentNode.children, + lastChild = children[ children.length - 1 ]; - // Establish the list item if it's not existed. - if ( !( lastChild && lastChild.name in listItems ) ) - addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode ); + // Establish the list item if it's not existed. + if ( !( lastChild && lastChild.name == 'li' ) ) + addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode ); - returnPoint = currentNode, addPoint = lastChild; - } - // If the element name is the same as the current element name, - // then just close the current one and append the new one to the - // parent. This situation usually happens with

    ,

  • ,
    and - //
    , specially in IE. Do not enter in this if block in this case. - else if ( tagName == currentName ) - { - addElement( currentNode, currentNode.parent ); - } - else if ( tagName in CKEDITOR.dtd.$listItem ) - { - parser.onTagOpen( 'ul', {} ); - addPoint = currentNode; - reApply = true; - } - else - { - if ( nonBreakingBlocks[ currentName ] ) + !element.returnPoint && ( element.returnPoint = currentNode ); + currentNode = lastChild; + } + // Establish new list root for orphan list items. + else if ( tagName in CKEDITOR.dtd.$listItem && currentName != tagName ) + parser.onTagOpen( tagName == 'li' ? 'ul' : 'dl', {}, 0, 1 ); + // We're inside a structural block like table and list, AND the incoming element + // is not of the same type (e.g. td1td2), we simply add this new one before it, + // and most importantly, return back to here once this element is added, + // e.g.

    p1

    td1td2
    + else if ( currentName in nonBreakingBlocks && currentName != tagName ) { - if ( !returnPoint ) - returnPoint = currentNode; + !element.returnPoint && ( element.returnPoint = currentNode ); + currentNode = currentNode.parent; } else { - addElement( currentNode, currentNode.parent, true ); + // The current element is an inline element, which + // need to be continued even after the close, so put + // it in the pending list. + if ( currentName in CKEDITOR.dtd.$inline ) + pendingInline.unshift( currentNode ); - if ( !optionalClose[ currentName ] ) + // The most common case where we just need to close the + // current one and append the new one to the parent. + if ( currentNode.parent ) + addElement( currentNode, currentNode.parent, 1 ); + // We've tried our best to fix the embarrassment here, while + // this element still doesn't find it's parent, mark it as + // orphan and show our tolerance to it. + else { - // The current element is an inline element, which - // cannot hold the new one. Put it in the pending list, - // and try adding the new one after it. - pendingInline.unshift( currentNode ); + element.isOrphan = 1; + break; } } - - reApply = true; } - - if ( addPoint ) - currentNode = addPoint; - // Try adding it to the return point, or the parent element. else - currentNode = currentNode.returnPoint || currentNode.parent; - - if ( reApply ) - { - parser.onTagOpen.apply( this, arguments ); - return; - } + break; } checkPending( tagName ); sendPendingBRs(); element.parent = currentNode; - element.returnPoint = returnPoint; - returnPoint = 0; if ( element.isEmpty ) addElement( element ); @@ -308,7 +310,7 @@ CKEDITOR.htmlParser.fragment = function() newPendingInline = [], candidate = currentNode; - while ( candidate.type && candidate.name != tagName ) + while ( candidate != fragment && candidate.name != tagName ) { // If this is an inline element, add it to the pending list, if we're // really closing one of the parents element later, they will continue @@ -321,10 +323,11 @@ CKEDITOR.htmlParser.fragment = function() // one of the nodes. So, for now, we just cache it. pendingAdd.push( candidate ); - candidate = candidate.parent; + // Make sure return point is properly restored. + candidate = candidate.returnPoint || candidate.parent; } - if ( candidate.type ) + if ( candidate != fragment ) { // Add all elements that have been found in the above loop. for ( i = 0 ; i < pendingAdd.length ; i++ ) @@ -373,7 +376,7 @@ CKEDITOR.htmlParser.fragment = function() && ( !currentNode.type || currentNode.name == 'body' ) && CKEDITOR.tools.trim( text ) ) { - this.onTagOpen( fixForBody, {} ); + this.onTagOpen( fixForBody, {}, 0, 1 ); } // Shrinking consequential spaces into one single for all elements @@ -402,24 +405,9 @@ CKEDITOR.htmlParser.fragment = function() // Send all pending BRs except one, which we consider a unwanted bogus. (#5293) sendPendingBRs( !CKEDITOR.env.ie && 1 ); - // Close all pending nodes. - while ( currentNode.type ) - { - var parent = currentNode.parent, - node = currentNode; - - if ( fixForBody - && ( !parent.type || parent.name == 'body' ) - && !CKEDITOR.dtd.$body[ node.name ] ) - { - currentNode = parent; - parser.onTagOpen( fixForBody, {} ); - parent = currentNode; - } - - parent.add( node ); - currentNode = parent; - } + // Close all pending nodes, make sure return point is properly restored. + while ( currentNode != fragment ) + addElement( currentNode, currentNode.parent, 1 ); return fragment; };