X-Git-Url: https://scm.cri.ensmp.fr/git/ckeditor.git/blobdiff_plain/256592bf803e851aa7fc953e08a6e9e58d970f8c..871bad8291b6dbc29d489d95d185458caab25158:/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 index 0000000..bfa5cc5 --- /dev/null +++ b/skins/ckeditor/_source/core/htmlparser/fragment.js @@ -0,0 +1,497 @@ +/* +Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML DOM structure. + * @constructor + * @example + */ +CKEDITOR.htmlParser.fragment = function() +{ + /** + * The nodes contained in the root of this fragment. + * @type Array + * @example + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); + * alert( fragment.children.length ); "2" + */ + this.children = []; + + /** + * Get the fragment parent. Should always be null. + * @type Object + * @default null + * @example + */ + this.parent = null; + + /** @private */ + this._ = + { + isBlockLike : true, + hasInlineStarted : false + }; +}; + +(function() +{ + // Block-level elements whose internal structure should be respected during + // parser fixing. + 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 ); + + // IE < 8 don't output the close tag on definition list items. (#6975) + var optionalCloseTags = CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? { dd : 1, dt :1 } : {}; + + var listBlocks = { ol:1, ul:1 }; + + // Dtd of the fragment element, basically it accept anything except for intermediate structure, e.g. orphan
element, spaces should be touched differently. + inPre = false; + + function checkPending( newTagName ) + { + var pendingBRsSent; + + if ( pendingInline.length > 0 ) + { + for ( var i = 0 ; i < pendingInline.length ; i++ ) + { + var pendingElement = pendingInline[ i ], + pendingName = pendingElement.name, + pendingDtd = CKEDITOR.dtd[ pendingName ], + currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ]; + + if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) ) + { + if ( !pendingBRsSent ) + { + sendPendingBRs(); + pendingBRsSent = 1; + } + + // Get a clone for the pending element. + pendingElement = pendingElement.clone(); + + // Add it to the current node and make it the current, + // so the new element will be added inside of it. + pendingElement.parent = currentNode; + currentNode = pendingElement; + + // Remove the pending element (back the index by one + // to properly process the next entry). + pendingInline.splice( i, 1 ); + i--; + } + } + } + } + + function sendPendingBRs() + { + while ( pendingBRs.length ) + currentNode.add( pendingBRs.shift() ); + } + + /* + * Beside of simply append specified element to target, this function also takes + * care of other dirty lifts like forcing block in body, trimming spaces at + * the block boundaries etc. + * + * @param {Element} element The element to be added as the last child of {@link target}. + * @param {Element} target The parent element to relieve the new node. + * @param {Boolean} [moveCurrent=false] Don't change the "currentNode" global unless + * there's a return point node specified on the element, otherwise move current onto {@link target} node. + */ + function addElement( element, target, moveCurrent ) + { + // Ignore any element that has already been added. + if ( element.previous !== undefined ) + return; + + target = target || currentNode || fragment; + + // Current element might be mangled by fix body below, + // save it for restore later. + var savedCurrent = currentNode; + + // If the target is the fragment and this inline element can't go inside + // body (if fixForBody). + if ( fixForBody && ( !target.type || target.name == 'body' ) ) + { + var elementName, realElementName; + if ( element.attributes + && ( realElementName = + element.attributes[ 'data-cke-real-element-type' ] ) ) + elementName = realElementName; + else + elementName = element.name; + + if ( elementName && !( elementName in CKEDITOR.dtd.$body || elementName == 'body' || element.isOrphan ) ) + { + // Create ain the fragment. + currentNode = target; + parser.onTagOpen( fixForBody, {} ); + + // The new target now is the
. + element.returnPoint = target = currentNode; + } + } + + // Rtrim empty spaces on block end boundary. (#3585) + if ( element._.isBlockLike + && element.name != 'pre' ) + { + + var length = element.children.length, + lastChild = element.children[ length - 1 ], + text; + if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT ) + { + if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) ) + element.children.length = length -1; + else + lastChild.value = text; + } + } + + target.add( element ); + + if ( element.returnPoint ) + { + currentNode = element.returnPoint; + delete element.returnPoint; + } + else + currentNode = moveCurrent ? target : savedCurrent; + } + + parser.onTagOpen = function( tagName, attributes, selfClosing, optionalClose ) + { + var element = new CKEDITOR.htmlParser.element( tagName, attributes ); + + // "isEmpty" will be always "false" for unknown elements, so we + // must force it if the parser has identified it as a selfClosing tag. + if ( element.isUnknown && selfClosing ) + element.isEmpty = true; + + // Check for optional closed elements, including browser quirks and manually opened blocks. + element.isOptionalClose = tagName in optionalCloseTags || optionalClose; + + // This is a tag to be removed if empty, so do not add it immediately. + if ( CKEDITOR.dtd.$removeEmpty[ tagName ] ) + { + pendingInline.push( element ); + return; + } + else if ( tagName == 'pre' ) + inPre = true; + else if ( tagName == 'br' && inPre ) + { + currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) ); + return; + } + + if ( tagName == 'br' ) + { + pendingBRs.push( element ); + return; + } + + while( 1 ) + { + var currentName = currentNode.name; + + var currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] + || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) + : rootDtd; + + // If the element cannot be child of the current element. + if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) + { + // Current node doesn't have a close tag, time for a close + // as this element isn't fit in. (#7497) + if ( currentNode.isOptionalClose ) + parser.onTagClose( currentName ); + // Fixing malformed nested lists by moving it into a previous list item. (#3828) + else if ( tagName in listBlocks + && currentName in listBlocks ) + { + var children = currentNode.children, + lastChild = children[ children.length - 1 ]; + + // Establish the list item if it's not existed. + if ( !( lastChild && lastChild.name == 'li' ) ) + addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode ); + + !element.returnPoint && ( element.returnPoint = currentNode ); + currentNode = lastChild; + } + // Establish new list root for orphan list items. + else if ( tagName in CKEDITOR.dtd.$listItem && currentName != tagName ) + parser.onTagOpen( tagName == 'li' ? 'ul' : 'dl', {}, 0, 1 ); + // We're inside a structural block like table and list, AND the incoming element + // is not of the same type (e.g.
td1 td2 ), we simply add this new one before it, + // and most importantly, return back to here once this element is added, + // e.g.
td1 | td2 |
. + if ( ( !currentNode._.hasInlineStarted || pendingBRs.length ) && !inPre ) + { + text = CKEDITOR.tools.ltrim( text ); + + if ( text.length === 0 ) + return; + } + + sendPendingBRs(); + checkPending(); + + if ( fixForBody + && ( !currentNode.type || currentNode.name == 'body' ) + && CKEDITOR.tools.trim( text ) ) + { + this.onTagOpen( fixForBody, {}, 0, 1 ); + } + + // Shrinking consequential spaces into one single for all elements + // text contents. + if ( !inPre ) + text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' ); + + currentNode.add( new CKEDITOR.htmlParser.text( text ) ); + }; + + parser.onCDATA = function( cdata ) + { + currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) ); + }; + + parser.onComment = function( comment ) + { + sendPendingBRs(); + checkPending(); + currentNode.add( new CKEDITOR.htmlParser.comment( comment ) ); + }; + + // Parse it. + parser.parse( fragmentHtml ); + + // Send all pending BRs except one, which we consider a unwanted bogus. (#5293) + sendPendingBRs( !CKEDITOR.env.ie && 1 ); + + // Close all pending nodes, make sure return point is properly restored. + while ( currentNode != fragment ) + addElement( currentNode, currentNode.parent, 1 ); + + return fragment; + }; + + CKEDITOR.htmlParser.fragment.prototype = + { + /** + * Adds a node to this fragment. + * @param {Object} node The node to be added. It can be any of of the + * following types: {@link CKEDITOR.htmlParser.element}, + * {@link CKEDITOR.htmlParser.text} and + * {@link CKEDITOR.htmlParser.comment}. + * @param {Number} [index] From where the insertion happens. + * @example + */ + add : function( node, index ) + { + isNaN( index ) && ( index = this.children.length ); + + var previous = index > 0 ? this.children[ index - 1 ] : null; + if ( previous ) + { + // If the block to be appended is following text, trim spaces at + // the right of it. + if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT ) + { + previous.value = CKEDITOR.tools.rtrim( previous.value ); + + // If we have completely cleared the previous node. + if ( previous.value.length === 0 ) + { + // Remove it from the list and add the node again. + this.children.pop(); + this.add( node ); + return; + } + } + + previous.next = node; + } + + node.previous = previous; + node.parent = this; + + this.children.splice( index, 0, node ); + + this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike ); + }, + + /** + * Writes the fragment HTML to a CKEDITOR.htmlWriter. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + * @example + * var writer = new CKEDITOR.htmlWriter(); + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' ); + * fragment.writeHtml( writer ) + * alert( writer.getHtml() ); "<p><b>Example</b></p>" + */ + writeHtml : function( writer, filter ) + { + var isChildrenFiltered; + this.filterChildren = function() + { + var writer = new CKEDITOR.htmlParser.basicWriter(); + this.writeChildrenHtml.call( this, writer, filter, true ); + var html = writer.getHtml(); + this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children; + isChildrenFiltered = 1; + }; + + // Filtering the root fragment before anything else. + !this.name && filter && filter.onFragment( this ); + + this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter ); + }, + + writeChildrenHtml : function( writer, filter ) + { + for ( var i = 0 ; i < this.children.length ; i++ ) + this.children[i].writeHtml( writer, filter ); + } + }; +})();