+++ /dev/null
-/*\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
-/**\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
- // 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, 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 <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
- 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
- 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()\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' && element.name != 'textarea' )\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.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
- 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
- // 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 ( isRemoveEmpty( element ) )\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
- else if ( tagName == 'textarea' )\r
- inTextarea = true;\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 ( 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 text contents except <pre> and <textarea>.\r
- if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre && !inTextarea )\r
- {\r
- text = CKEDITOR.tools.ltrim( text );\r
-\r
- if ( text.length === 0 )\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
- 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 && !inTextarea )\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
- * @param {Number} [index] From where the insertion happens.\r
- * @example\r
- */\r
- add : function( node, index )\r
- {\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
- // 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.splice( index, 0, 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