/*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
\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
+ // IE < 8 don't output the close tag on definition list items. (#6975)\r
+ var optionalCloseTags = CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? { dd : 1, dt :1 } : {};\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
+ function isRemoveEmpty( node )\r
+ {\r
+ // Empty link is to be removed when empty but not anchor. (#7894)\r
+ return node.name == 'a' && node.attributes.href\r
+ || CKEDITOR.dtd.$removeEmpty[ node.name ];\r
+ }\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 <textarea> element, spaces should be touched differently.\r
+ inTextarea = false,\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
pendingInline.splice( i, 1 );\r
i--;\r
}\r
+ else\r
+ {\r
+ // Some element of the same type cannot be nested, flat them,\r
+ // e.g. <a href="#">foo<a href="#">bar</a></a>. (#7894)\r
+ if ( pendingName == currentNode.name )\r
+ addElement( currentNode, currentNode.parent, 1 ), i--;\r
+ }\r
}\r
}\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
- // If the target is the fragment and this element can't go inside\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
&& ( realElementName =\r
- element.attributes[ '_cke_real_element_type' ] ) )\r
+ element.attributes[ 'data-cke-real-element-type' ] ) )\r
elementName = realElementName;\r
else\r
elementName = element.name;\r
- if ( elementName\r
- && !( elementName in CKEDITOR.dtd.$body )\r
- && !( elementName in CKEDITOR.dtd.$nonBodyContent ) )\r
- {\r
- var savedCurrent = currentNode;\r
\r
+ if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) )\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
// Rtrim empty spaces on block end boundary. (#3585)\r
if ( element._.isBlockLike\r
- && element.name != 'pre' )\r
+ && element.name != 'pre' && element.name != 'textarea' )\r
{\r
\r
var length = element.children.length,\r
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
if ( element.isUnknown && selfClosing )\r
element.isEmpty = true;\r
\r
+ // Check for optional closed elements, including browser quirks and manually opened blocks.\r
+ element.isOptionalClose = tagName in optionalCloseTags || 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
+ if ( isRemoveEmpty( element ) )\r
{\r
pendingInline.push( element );\r
return;\r
currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );\r
return;\r
}\r
+ else if ( tagName == 'textarea' )\r
+ inTextarea = true;\r
\r
if ( tagName == 'br' )\r
{\r
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
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
// 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
if ( currentNode.name == 'pre' )\r
inPre = false;\r
\r
+ if ( currentNode.name == 'textarea' )\r
+ inTextarea = false;\r
+\r
if ( candidate._.isBlockLike )\r
sendPendingBRs();\r
\r
\r
parser.onText = function( text )\r
{\r
- // Trim empty spaces at beginning of element contents except <pre>.\r
- if ( !currentNode._.hasInlineStarted && !inPre )\r
+ // Trim empty spaces at beginning of text contents except <pre> and <textarea>.\r
+ if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre && !inTextarea )\r
{\r
text = CKEDITOR.tools.ltrim( text );\r
\r
&& ( !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
// text contents.\r
- if ( !inPre )\r
+ if ( !inPre && !inTextarea )\r
text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );\r
\r
currentNode.add( new CKEDITOR.htmlParser.text( text ) );\r
\r
parser.onComment = function( comment )\r
{\r
+ sendPendingBRs();\r
checkPending();\r
currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );\r
};\r
// 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
* following types: {@link CKEDITOR.htmlParser.element},\r
* {@link CKEDITOR.htmlParser.text} and\r
* {@link CKEDITOR.htmlParser.comment}.\r
+ * @param {Number} [index] From where the insertion happens.\r
* @example\r
*/\r
- add : function( node )\r
+ add : function( node, index )\r
{\r
- var len = this.children.length,\r
- previous = len > 0 && this.children[ len - 1 ] || null;\r
+ isNaN( index ) && ( index = this.children.length );\r
\r
+ var previous = index > 0 ? this.children[ index - 1 ] : null;\r
if ( previous )\r
{\r
// If the block to be appended is following text, trim spaces at\r
node.previous = previous;\r
node.parent = this;\r
\r
- this.children.push( node );\r
+ this.children.splice( index, 0, node );\r
\r
this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );\r
},\r