--- /dev/null
+/*\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
+ * A lightweight representation of an HTML DOM structure.\r
+ * @constructor\r
+ * @example\r
+ */\r
+CKEDITOR.htmlParser.fragment = function()\r
+{\r
+ /**\r
+ * The nodes contained in the root of this fragment.\r
+ * @type Array\r
+ * @example\r
+ * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );\r
+ * alert( fragment.children.length ); "2"\r
+ */\r
+ this.children = [];\r
+\r
+ /**\r
+ * Get the fragment parent. Should always be null.\r
+ * @type Object\r
+ * @default null\r
+ * @example\r
+ */\r
+ this.parent = null;\r
+\r
+ /** @private */\r
+ this._ =\r
+ {\r
+ isBlockLike : true,\r
+ hasInlineStarted : false\r
+ };\r
+};\r
+\r
+(function()\r
+{\r
+ // Block-level elements whose internal structure should be respected during\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
+ 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, contextNode )\r
+ {\r
+ var parser = new CKEDITOR.htmlParser(),\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
+\r
+ function checkPending( newTagName )\r
+ {\r
+ var pendingBRsSent;\r
+\r
+ if ( pendingInline.length > 0 )\r
+ {\r
+ for ( var i = 0 ; i < pendingInline.length ; i++ )\r
+ {\r
+ var pendingElement = pendingInline[ i ],\r
+ pendingName = pendingElement.name,\r
+ pendingDtd = CKEDITOR.dtd[ pendingName ],\r
+ currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];\r
+\r
+ if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )\r
+ {\r
+ if ( !pendingBRsSent )\r
+ {\r
+ sendPendingBRs();\r
+ pendingBRsSent = 1;\r
+ }\r
+\r
+ // Get a clone for the pending element.\r
+ pendingElement = pendingElement.clone();\r
+\r
+ // Add it to the current node and make it the current,\r
+ // so the new element will be added inside of it.\r
+ pendingElement.parent = currentNode;\r
+ currentNode = pendingElement;\r
+\r
+ // Remove the pending element (back the index by one\r
+ // to properly process the next entry).\r
+ pendingInline.splice( i, 1 );\r
+ i--;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ function sendPendingBRs()\r
+ {\r
+ while ( pendingBRs.length )\r
+ currentNode.add( pendingBRs.shift() );\r
+ }\r
+\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 || target.name == 'body' ) )\r
+ {\r
+ var elementName, realElementName;\r
+ if ( element.attributes\r
+ && ( realElementName =\r
+ element.attributes[ 'data-cke-real-element-type' ] ) )\r
+ elementName = realElementName;\r
+ else\r
+ elementName = element.name;\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
+ 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
+ {\r
+\r
+ var length = element.children.length,\r
+ lastChild = element.children[ length - 1 ],\r
+ text;\r
+ if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT )\r
+ {\r
+ if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) )\r
+ element.children.length = length -1;\r
+ else\r
+ lastChild.value = text;\r
+ }\r
+ }\r
+\r
+ target.add( element );\r
+\r
+ if ( element.returnPoint )\r
+ {\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, optionalClose )\r
+ {\r
+ var element = new CKEDITOR.htmlParser.element( tagName, attributes );\r
+\r
+ // "isEmpty" will be always "false" for unknown elements, so we\r
+ // must force it if the parser has identified it as a selfClosing tag.\r
+ 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
+ pendingInline.push( element );\r
+ return;\r
+ }\r
+ else if ( tagName == 'pre' )\r
+ inPre = true;\r
+ else if ( tagName == 'br' && inPre )\r
+ {\r
+ currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) );\r
+ return;\r
+ }\r
+\r
+ if ( tagName == 'br' )\r
+ {\r
+ pendingBRs.push( element );\r
+ return;\r
+ }\r
+\r
+ while( 1 )\r
+ {\r
+ var currentName = currentNode.name;\r
+\r
+ var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ]\r
+ || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) )\r
+ : rootDtd;\r
+\r
+ // If the element cannot be child of the current element.\r
+ if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )\r
+ {\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 == 'li' ) )\r
+ addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode );\r
+\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
+ !element.returnPoint && ( element.returnPoint = currentNode );\r
+ currentNode = currentNode.parent;\r
+ }\r
+ else\r
+ {\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
+ // 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
+ element.isOrphan = 1;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ else\r
+ break;\r
+ }\r
+\r
+ checkPending( tagName );\r
+ sendPendingBRs();\r
+\r
+ element.parent = currentNode;\r
+\r
+ if ( element.isEmpty )\r
+ addElement( element );\r
+ else\r
+ currentNode = element;\r
+ };\r
+\r
+ parser.onTagClose = function( tagName )\r
+ {\r
+ // Check if there is any pending tag to be closed.\r
+ for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- )\r
+ {\r
+ // If found, just remove it from the list.\r
+ if ( tagName == pendingInline[ i ].name )\r
+ {\r
+ pendingInline.splice( i, 1 );\r
+ return;\r
+ }\r
+ }\r
+\r
+ var pendingAdd = [],\r
+ newPendingInline = [],\r
+ candidate = currentNode;\r
+\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
+ // after it.\r
+ if ( !candidate._.isBlockLike )\r
+ newPendingInline.unshift( candidate );\r
+\r
+ // This node should be added to it's parent at this point. But,\r
+ // it should happen only if the closing tag is really closing\r
+ // one of the nodes. So, for now, we just cache it.\r
+ pendingAdd.push( candidate );\r
+\r
+ // Make sure return point is properly restored.\r
+ candidate = candidate.returnPoint || candidate.parent;\r
+ }\r
+\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
+ {\r
+ var node = pendingAdd[ i ];\r
+ addElement( node, node.parent );\r
+ }\r
+\r
+ currentNode = candidate;\r
+\r
+ if ( currentNode.name == 'pre' )\r
+ inPre = false;\r
+\r
+ if ( candidate._.isBlockLike )\r
+ sendPendingBRs();\r
+\r
+ addElement( candidate, candidate.parent );\r
+\r
+ // The parent should start receiving new nodes now, except if\r
+ // addElement changed the currentNode.\r
+ if ( candidate == currentNode )\r
+ currentNode = currentNode.parent;\r
+\r
+ pendingInline = pendingInline.concat( newPendingInline );\r
+ }\r
+\r
+ if ( tagName == 'body' )\r
+ fixForBody = false;\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
+ {\r
+ text = CKEDITOR.tools.ltrim( text );\r
+\r
+ if ( text.length === 0 )\r
+ return;\r
+ }\r
+\r
+ sendPendingBRs();\r
+ checkPending();\r
+\r
+ if ( fixForBody\r
+ && ( !currentNode.type || currentNode.name == 'body' )\r
+ && CKEDITOR.tools.trim( text ) )\r
+ {\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
+ text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );\r
+\r
+ currentNode.add( new CKEDITOR.htmlParser.text( text ) );\r
+ };\r
+\r
+ parser.onCDATA = function( cdata )\r
+ {\r
+ currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );\r
+ };\r
+\r
+ parser.onComment = function( comment )\r
+ {\r
+ sendPendingBRs();\r
+ checkPending();\r
+ currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );\r
+ };\r
+\r
+ // Parse it.\r
+ parser.parse( fragmentHtml );\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, make sure return point is properly restored.\r
+ while ( currentNode != fragment )\r
+ addElement( currentNode, currentNode.parent, 1 );\r
+\r
+ return fragment;\r
+ };\r
+\r
+ CKEDITOR.htmlParser.fragment.prototype =\r
+ {\r
+ /**\r
+ * Adds a node to this fragment.\r
+ * @param {Object} node The node to be added. It can be any of of the\r
+ * following types: {@link CKEDITOR.htmlParser.element},\r
+ * {@link CKEDITOR.htmlParser.text} and\r
+ * {@link CKEDITOR.htmlParser.comment}.\r
+ * @example\r
+ */\r
+ add : function( node )\r
+ {\r
+ var len = this.children.length,\r
+ previous = len > 0 && this.children[ len - 1 ] || null;\r
+\r
+ if ( previous )\r
+ {\r
+ // If the block to be appended is following text, trim spaces at\r
+ // the right of it.\r
+ if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT )\r
+ {\r
+ previous.value = CKEDITOR.tools.rtrim( previous.value );\r
+\r
+ // If we have completely cleared the previous node.\r
+ if ( previous.value.length === 0 )\r
+ {\r
+ // Remove it from the list and add the node again.\r
+ this.children.pop();\r
+ this.add( node );\r
+ return;\r
+ }\r
+ }\r
+\r
+ previous.next = node;\r
+ }\r
+\r
+ node.previous = previous;\r
+ node.parent = this;\r
+\r
+ this.children.push( node );\r
+\r
+ this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );\r
+ },\r
+\r
+ /**\r
+ * Writes the fragment HTML to a CKEDITOR.htmlWriter.\r
+ * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.\r
+ * @example\r
+ * var writer = new CKEDITOR.htmlWriter();\r
+ * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' );\r
+ * fragment.writeHtml( writer )\r
+ * alert( writer.getHtml() ); "<p><b>Example</b></p>"\r
+ */\r
+ writeHtml : function( writer, filter )\r
+ {\r
+ var isChildrenFiltered;\r
+ this.filterChildren = function()\r
+ {\r
+ var writer = new CKEDITOR.htmlParser.basicWriter();\r
+ this.writeChildrenHtml.call( this, writer, filter, true );\r
+ var html = writer.getHtml();\r
+ this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children;\r
+ isChildrenFiltered = 1;\r
+ };\r
+\r
+ // Filtering the root fragment before anything else.\r
+ !this.name && filter && filter.onFragment( this );\r
+\r
+ this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter );\r
+ },\r
+\r
+ writeChildrenHtml : function( writer, filter )\r
+ {\r
+ for ( var i = 0 ; i < this.children.length ; i++ )\r
+ this.children[i].writeHtml( writer, filter );\r
+ }\r
+ };\r
+})();\r