/*\r
-Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.\r
For licensing, see LICENSE.html or http://ckeditor.com/license\r
*/\r
\r
// parser fixing.\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
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
\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
// 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
\r
target.add( element );\r
\r
+ if ( element.name == 'pre' )\r
+ inPre = false;\r
+\r
+ if ( element.name == 'textarea' )\r
+ inTextarea = false;\r
+\r
+\r
if ( element.returnPoint )\r
{\r
currentNode = element.returnPoint;\r
if ( element.isUnknown && selfClosing )\r
element.isEmpty = true;\r
\r
- element.isOptionalClose = optionalClose;\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
\r
currentNode = candidate;\r
\r
- if ( currentNode.name == 'pre' )\r
- inPre = false;\r
-\r
if ( candidate._.isBlockLike )\r
sendPendingBRs();\r
\r
\r
parser.onText = function( text )\r
{\r
- // Trim empty spaces at beginning of text contents except <pre>.\r
- if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !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
return;\r
}\r
\r
+ var currentName = currentNode.name,\r
+ currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ]\r
+ || ( currentNode._.isBlockLike ?\r
+ CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd;\r
+\r
+ // Fix orphan text in list/table. (#8540) (#8870)\r
+ if ( !inTextarea &&\r
+ !currentDtd [ '#' ] &&\r
+ currentName in nonBreakingBlocks )\r
+ {\r
+ parser.onTagOpen( currentName in listBlocks ? 'li' :\r
+ currentName == 'dl' ? 'dd' :\r
+ currentName == 'table' ? 'tr' :\r
+ currentName == 'tr' ? 'td' : '' );\r
+ parser.onText( text );\r
+ return;\r
+ }\r
+\r
sendPendingBRs();\r
checkPending();\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
* 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