X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=_source%2Fcore%2Fhtmlparser%2Ffragment.js;h=bfa5cc57925c31bcd800e086e33f45a154e07f5c;hb=f0610347140239143439a511ee2bd48cb784f470;hp=7912569d05a86e5740713b79cc4238acd5dd152e;hpb=9afde8772159bd3436f1f5b7862960307710ae5a;p=ckeditor.git diff --git a/_source/core/htmlparser/fragment.js b/_source/core/htmlparser/fragment.js index 7912569..bfa5cc5 100644 --- a/_source/core/htmlparser/fragment.js +++ b/_source/core/htmlparser/fragment.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -37,38 +37,38 @@ 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 ); + + // IE < 8 don't output the close tag on definition list items. (#6975) + var optionalCloseTags = CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? { dd : 1, dt :1 } : {}; + + 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 +108,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;
     
    -			// If the target is the fragment and this element can't go inside
    +			// 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
    @@ -129,21 +147,15 @@ CKEDITOR.htmlParser.fragment = function()
     					elementName = realElementName;
     				else
     					elementName =  element.name;
    -				if ( elementName
    -						&& !( elementName in CKEDITOR.dtd.$body )
    -						&& !( elementName in CKEDITOR.dtd.$nonBodyContent )  )
    -				{
    -					var savedCurrent = currentNode;
     
    +				if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) )
    +				{
     					// 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; } } @@ -171,9 +183,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 ); @@ -182,6 +196,9 @@ CKEDITOR.htmlParser.fragment = function() if ( element.isUnknown && selfClosing ) element.isEmpty = true; + // Check for optional closed elements, including browser quirks and manually opened blocks. + element.isOptionalClose = tagName in optionalCloseTags || optionalClose; + // This is a tag to be removed if empty, so do not add it immediately. if ( CKEDITOR.dtd.$removeEmpty[ tagName ] ) { @@ -202,89 +219,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 ); @@ -309,7 +314,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 @@ -322,10 +327,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++ ) @@ -358,8 +364,8 @@ CKEDITOR.htmlParser.fragment = function() parser.onText = function( text ) { - // Trim empty spaces at beginning of element contents except
    .
    -			if ( !currentNode._.hasInlineStarted && !inPre )
    +			// Trim empty spaces at beginning of text contents except 
    .
    +			if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre )
     			{
     				text = CKEDITOR.tools.ltrim( text );
     
    @@ -374,7 +380,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
    @@ -392,6 +398,7 @@ CKEDITOR.htmlParser.fragment = function()
     
     		parser.onComment = function( comment )
     		{
    +			sendPendingBRs();
     			checkPending();
     			currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
     		};
    @@ -402,24 +409,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;
     	};
    @@ -432,13 +424,14 @@ CKEDITOR.htmlParser.fragment = function()
     		 *		following types: {@link CKEDITOR.htmlParser.element},
     		 *		{@link CKEDITOR.htmlParser.text} and
     		 *		{@link CKEDITOR.htmlParser.comment}.
    +		 *	@param {Number} [index] From where the insertion happens.
     		 * @example
     		 */
    -		add : function( node )
    +		add : function( node, index )
     		{
    -			var len = this.children.length,
    -				previous = len > 0 && this.children[ len - 1 ] || null;
    +			isNaN( index ) && ( index = this.children.length );
     
    +			var previous = index > 0 ? this.children[ index - 1 ] : null;
     			if ( previous )
     			{
     				// If the block to be appended is following text, trim spaces at
    @@ -463,7 +456,7 @@ CKEDITOR.htmlParser.fragment = function()
     			node.previous = previous;
     			node.parent = this;
     
    -			this.children.push( node );
    +			this.children.splice( index, 0, node );
     
     			this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
     		},