Mimimum syndical pour en faire un produit zope / cmf.
[ckeditor.git] / skins / ckeditor / _source / core / htmlparser / fragment.js
diff --git a/skins/ckeditor/_source/core/htmlparser/fragment.js b/skins/ckeditor/_source/core/htmlparser/fragment.js
new file mode 100644 (file)
index 0000000..bfa5cc5
--- /dev/null
@@ -0,0 +1,497 @@
+/*\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
+       // 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
+       /**\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
+                       // 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
+                       {\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 text contents except <pre>.\r
+                       if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !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
+                *      @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( '&lt;P&gt;&lt;B&gt;Example' );\r
+                * fragment.writeHtml( writer )\r
+                * alert( writer.getHtml() );  "&lt;p&gt;&lt;b&gt;Example&lt;/b&gt;&lt;/p&gt;"\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