PageRenderTime 52ms CodeModel.GetById 14ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 1ms

/source/Plug-in/fck/editor/_source/classes/fckdomrange.js

http://prosporous.googlecode.com/
JavaScript | 905 lines | 593 code | 149 blank | 163 comment | 198 complexity | c3ca361322c5800f5f3c22e3e7b0b438 MD5 | raw file
  1/*
  2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
  3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
  4 *
  5 * == BEGIN LICENSE ==
  6 *
  7 * Licensed under the terms of any of the following licenses at your
  8 * choice:
  9 *
 10 *  - GNU General Public License Version 2 or later (the "GPL")
 11 *    http://www.gnu.org/licenses/gpl.html
 12 *
 13 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 14 *    http://www.gnu.org/licenses/lgpl.html
 15 *
 16 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 17 *    http://www.mozilla.org/MPL/MPL-1.1.html
 18 *
 19 * == END LICENSE ==
 20 *
 21 * Class for working with a selection range, much like the W3C DOM Range, but
 22 * it is not intended to be an implementation of the W3C interface.
 23 */
 24
 25var FCKDomRange = function( sourceWindow )
 26{
 27	this.Window = sourceWindow ;
 28	this._Cache = {} ;
 29}
 30
 31FCKDomRange.prototype =
 32{
 33
 34	_UpdateElementInfo : function()
 35	{
 36		var innerRange = this._Range ;
 37
 38		if ( !innerRange )
 39			this.Release( true ) ;
 40		else
 41		{
 42			// For text nodes, the node itself is the StartNode.
 43			var eStart	= innerRange.startContainer ;
 44			var eEnd	= innerRange.endContainer ;
 45
 46			var oElementPath = new FCKElementPath( eStart ) ;
 47			this.StartNode			= eStart.nodeType == 3 ? eStart : eStart.childNodes[ innerRange.startOffset ] ;
 48			this.StartContainer		= eStart ;
 49			this.StartBlock			= oElementPath.Block ;
 50			this.StartBlockLimit	= oElementPath.BlockLimit ;
 51
 52			if ( eStart != eEnd )
 53				oElementPath = new FCKElementPath( eEnd ) ;
 54
 55			// The innerRange.endContainer[ innerRange.endOffset ] is not
 56			// usually part of the range, but the marker for the range end. So,
 57			// let's get the previous available node as the real end.
 58			var eEndNode = eEnd ;
 59			if ( innerRange.endOffset == 0 )
 60			{
 61				while ( eEndNode && !eEndNode.previousSibling )
 62					eEndNode = eEndNode.parentNode ;
 63
 64				if ( eEndNode )
 65					eEndNode = eEndNode.previousSibling ;
 66			}
 67			else if ( eEndNode.nodeType == 1 )
 68				eEndNode = eEndNode.childNodes[ innerRange.endOffset - 1 ] ;
 69
 70			this.EndNode			= eEndNode ;
 71			this.EndContainer		= eEnd ;
 72			this.EndBlock			= oElementPath.Block ;
 73			this.EndBlockLimit		= oElementPath.BlockLimit ;
 74		}
 75
 76		this._Cache = {} ;
 77	},
 78
 79	CreateRange : function()
 80	{
 81		return new FCKW3CRange( this.Window.document ) ;
 82	},
 83
 84	DeleteContents : function()
 85	{
 86		if ( this._Range )
 87		{
 88			this._Range.deleteContents() ;
 89			this._UpdateElementInfo() ;
 90		}
 91	},
 92
 93	ExtractContents : function()
 94	{
 95		if ( this._Range )
 96		{
 97			var docFrag = this._Range.extractContents() ;
 98			this._UpdateElementInfo() ;
 99			return docFrag ;
100		}
101	},
102
103	CheckIsCollapsed : function()
104	{
105		if ( this._Range )
106			return this._Range.collapsed ;
107	},
108
109	Collapse : function( toStart )
110	{
111		if ( this._Range )
112			this._Range.collapse( toStart ) ;
113
114		this._UpdateElementInfo() ;
115	},
116
117	Clone : function()
118	{
119		var oClone = FCKTools.CloneObject( this ) ;
120
121		if ( this._Range )
122			oClone._Range = this._Range.cloneRange() ;
123
124		return oClone ;
125	},
126
127	MoveToNodeContents : function( targetNode )
128	{
129		if ( !this._Range )
130			this._Range = this.CreateRange() ;
131
132		this._Range.selectNodeContents( targetNode ) ;
133
134		this._UpdateElementInfo() ;
135	},
136
137	MoveToElementStart : function( targetElement )
138	{
139		this.SetStart(targetElement,1) ;
140		this.SetEnd(targetElement,1) ;
141	},
142
143	// Moves to the first editing point inside a element. For example, in a
144	// element tree like "<p><b><i></i></b> Text</p>", the start editing point
145	// is "<p><b><i>^</i></b> Text</p>" (inside <i>).
146	MoveToElementEditStart : function( targetElement )
147	{
148		var child ;
149
150		while ( ( child = targetElement.firstChild ) && child.nodeType == 1 && FCKListsLib.EmptyElements[ child.nodeName.toLowerCase() ] == null )
151			targetElement = child ;
152
153		this.MoveToElementStart( targetElement ) ;
154	},
155
156	InsertNode : function( node )
157	{
158		if ( this._Range )
159			this._Range.insertNode( node ) ;
160	},
161
162	CheckIsEmpty : function()
163	{
164		if ( this.CheckIsCollapsed() )
165			return true ;
166
167		// Inserts the contents of the range in a div tag.
168		var eToolDiv = this.Window.document.createElement( 'div' ) ;
169		this._Range.cloneContents().AppendTo( eToolDiv ) ;
170
171		FCKDomTools.TrimNode( eToolDiv ) ;
172
173		return ( eToolDiv.innerHTML.length == 0 ) ;
174	},
175
176	/**
177	 * Checks if the start boundary of the current range is "visually" (like a
178	 * selection caret) at the beginning of the block. It means that some
179	 * things could be brefore the range, like spaces or empty inline elements,
180	 * but it would still be considered at the beginning of the block.
181	 */
182	CheckStartOfBlock : function()
183	{
184		var cache = this._Cache ;
185		var bIsStartOfBlock = cache.IsStartOfBlock ;
186
187		if ( bIsStartOfBlock != undefined )
188			return bIsStartOfBlock ;
189
190		// Take the block reference.
191		var block = this.StartBlock || this.StartBlockLimit ;
192
193		var container	= this._Range.startContainer ;
194		var offset		= this._Range.startOffset ;
195		var currentNode ;
196
197		if ( offset > 0 )
198		{
199			// First, check the start container. If it is a text node, get the
200			// substring of the node value before the range offset.
201			if ( container.nodeType == 3 )
202			{
203				var textValue = container.nodeValue.substr( 0, offset ).Trim() ;
204
205				// If we have some text left in the container, we are not at
206				// the end for the block.
207				if ( textValue.length != 0 )
208					return cache.IsStartOfBlock = false ;
209			}
210			else
211				currentNode = container.childNodes[ offset - 1 ] ;
212		}
213
214		// We'll not have a currentNode if the container was a text node, or
215		// the offset is zero.
216		if ( !currentNode )
217			currentNode = FCKDomTools.GetPreviousSourceNode( container, true, null, block ) ;
218
219		while ( currentNode )
220		{
221			switch ( currentNode.nodeType )
222			{
223				case 1 :
224					// It's not an inline element.
225					if ( !FCKListsLib.InlineChildReqElements[ currentNode.nodeName.toLowerCase() ] )
226						return cache.IsStartOfBlock = false ;
227
228					break ;
229
230				case 3 :
231					// It's a text node with real text.
232					if ( currentNode.nodeValue.Trim().length > 0 )
233						return cache.IsStartOfBlock = false ;
234			}
235
236			currentNode = FCKDomTools.GetPreviousSourceNode( currentNode, false, null, block ) ;
237		}
238
239		return cache.IsStartOfBlock = true ;
240	},
241
242	/**
243	 * Checks if the end boundary of the current range is "visually" (like a
244	 * selection caret) at the end of the block. It means that some things
245	 * could be after the range, like spaces, empty inline elements, or a
246	 * single <br>, but it would still be considered at the end of the block.
247	 */
248	CheckEndOfBlock : function( refreshSelection )
249	{
250		var isEndOfBlock = this._Cache.IsEndOfBlock ;
251
252		if ( isEndOfBlock != undefined )
253			return isEndOfBlock ;
254
255		// Take the block reference.
256		var block = this.EndBlock || this.EndBlockLimit ;
257
258		var container	= this._Range.endContainer ;
259		var offset			= this._Range.endOffset ;
260		var currentNode ;
261
262		// First, check the end container. If it is a text node, get the
263		// substring of the node value after the range offset.
264		if ( container.nodeType == 3 )
265		{
266			var textValue = container.nodeValue ;
267			if ( offset < textValue.length )
268			{
269				textValue = textValue.substr( offset ) ;
270
271				// If we have some text left in the container, we are not at
272				// the end for the block.
273				if ( textValue.Trim().length != 0 )
274					return this._Cache.IsEndOfBlock = false ;
275			}
276		}
277		else
278			currentNode = container.childNodes[ offset ] ;
279
280		// We'll not have a currentNode if the container was a text node, of
281		// the offset is out the container children limits (after it probably).
282		if ( !currentNode )
283			currentNode = FCKDomTools.GetNextSourceNode( container, true, null, block ) ;
284
285		var hadBr = false ;
286
287		while ( currentNode )
288		{
289			switch ( currentNode.nodeType )
290			{
291				case 1 :
292					var nodeName = currentNode.nodeName.toLowerCase() ;
293
294					// It's an inline element.
295					if ( FCKListsLib.InlineChildReqElements[ nodeName ] )
296						break ;
297
298					// It is the first <br> found.
299					if ( nodeName == 'br' && !hadBr )
300					{
301						hadBr = true ;
302						break ;
303					}
304
305					return this._Cache.IsEndOfBlock = false ;
306
307				case 3 :
308					// It's a text node with real text.
309					if ( currentNode.nodeValue.Trim().length > 0 )
310						return this._Cache.IsEndOfBlock = false ;
311			}
312
313			currentNode = FCKDomTools.GetNextSourceNode( currentNode, false, null, block ) ;
314		}
315
316		if ( refreshSelection )
317			this.Select() ;
318
319		return this._Cache.IsEndOfBlock = true ;
320	},
321
322	// This is an "intrusive" way to create a bookmark. It includes <span> tags
323	// in the range boundaries. The advantage of it is that it is possible to
324	// handle DOM mutations when moving back to the bookmark.
325	// Attention: the inclusion of nodes in the DOM is a design choice and
326	// should not be changed as there are other points in the code that may be
327	// using those nodes to perform operations. See GetBookmarkNode.
328	// For performance, includeNodes=true if intended to SelectBookmark.
329	CreateBookmark : function( includeNodes )
330	{
331		// Create the bookmark info (random IDs).
332		var oBookmark =
333		{
334			StartId	: (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S',
335			EndId	: (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'
336		} ;
337
338		var oDoc = this.Window.document ;
339		var eStartSpan ;
340		var eEndSpan ;
341		var oClone ;
342
343		// For collapsed ranges, add just the start marker.
344		if ( !this.CheckIsCollapsed() )
345		{
346			eEndSpan = oDoc.createElement( 'span' ) ;
347			eEndSpan.style.display = 'none' ;
348			eEndSpan.id = oBookmark.EndId ;
349			eEndSpan.setAttribute( '_fck_bookmark', true ) ;
350
351			// For IE, it must have something inside, otherwise it may be
352			// removed during DOM operations.
353//			if ( FCKBrowserInfo.IsIE )
354				eEndSpan.innerHTML = '&nbsp;' ;
355
356			oClone = this.Clone() ;
357			oClone.Collapse( false ) ;
358			oClone.InsertNode( eEndSpan ) ;
359		}
360
361		eStartSpan = oDoc.createElement( 'span' ) ;
362		eStartSpan.style.display = 'none' ;
363		eStartSpan.id = oBookmark.StartId ;
364		eStartSpan.setAttribute( '_fck_bookmark', true ) ;
365
366		// For IE, it must have something inside, otherwise it may be removed
367		// during DOM operations.
368//		if ( FCKBrowserInfo.IsIE )
369			eStartSpan.innerHTML = '&nbsp;' ;
370
371		oClone = this.Clone() ;
372		oClone.Collapse( true ) ;
373		oClone.InsertNode( eStartSpan ) ;
374
375		if ( includeNodes )
376		{
377			oBookmark.StartNode = eStartSpan ;
378			oBookmark.EndNode = eEndSpan ;
379		}
380
381		// Update the range position.
382		if ( eEndSpan )
383		{
384			this.SetStart( eStartSpan, 4 ) ;
385			this.SetEnd( eEndSpan, 3 ) ;
386		}
387		else
388			this.MoveToPosition( eStartSpan, 4 ) ;
389
390		return oBookmark ;
391	},
392
393	// This one should be a part of a hypothetic "bookmark" object.
394	GetBookmarkNode : function( bookmark, start )
395	{
396		var doc = this.Window.document ;
397
398		if ( start )
399			return bookmark.StartNode || doc.getElementById( bookmark.StartId ) ;
400		else
401			return bookmark.EndNode || doc.getElementById( bookmark.EndId ) ;
402	},
403
404	MoveToBookmark : function( bookmark, preserveBookmark )
405	{
406		var eStartSpan	= this.GetBookmarkNode( bookmark, true ) ;
407		var eEndSpan	= this.GetBookmarkNode( bookmark, false ) ;
408
409		this.SetStart( eStartSpan, 3 ) ;
410
411		if ( !preserveBookmark )
412			FCKDomTools.RemoveNode( eStartSpan ) ;
413
414		// If collapsed, the end span will not be available.
415		if ( eEndSpan )
416		{
417			this.SetEnd( eEndSpan, 3 ) ;
418
419			if ( !preserveBookmark )
420				FCKDomTools.RemoveNode( eEndSpan ) ;
421		}
422		else
423			this.Collapse( true ) ;
424
425		this._UpdateElementInfo() ;
426	},
427
428	// Non-intrusive bookmark algorithm
429	CreateBookmark2 : function()
430	{
431		// If there is no range then get out of here.
432		// It happens on initial load in Safari #962 and if the editor it's hidden also in Firefox
433		if ( ! this._Range )
434			return { "Start" : 0, "End" : 0 } ;
435
436		// First, we record down the offset values
437		var bookmark =
438		{
439			"Start" : [ this._Range.startOffset ],
440			"End" : [ this._Range.endOffset ]
441		} ;
442		// Since we're treating the document tree as normalized, we need to backtrack the text lengths
443		// of previous text nodes into the offset value.
444		var curStart = this._Range.startContainer.previousSibling ;
445		var curEnd = this._Range.endContainer.previousSibling ;
446
447		// Also note that the node that we use for "address base" would change during backtracking.
448		var addrStart = this._Range.startContainer ;
449		var addrEnd = this._Range.endContainer ;
450		while ( curStart && curStart.nodeType == 3 )
451		{
452			bookmark.Start[0] += curStart.length ;
453			addrStart = curStart ;
454			curStart = curStart.previousSibling ;
455		}
456		while ( curEnd && curEnd.nodeType == 3 )
457		{
458			bookmark.End[0] += curEnd.length ;
459			addrEnd = curEnd ;
460			curEnd = curEnd.previousSibling ;
461		}
462
463		// If the object pointed to by the startOffset and endOffset are text nodes, we need
464		// to backtrack and add in the text offset to the bookmark addresses.
465		if ( addrStart.nodeType == 1 && addrStart.childNodes[bookmark.Start[0]] && addrStart.childNodes[bookmark.Start[0]].nodeType == 3 )
466		{
467			var curNode = addrStart.childNodes[bookmark.Start[0]] ;
468			var offset = 0 ;
469			while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
470			{
471				curNode = curNode.previousSibling ;
472				offset += curNode.length ;
473			}
474			addrStart = curNode ;
475			bookmark.Start[0] = offset ;
476		}
477		if ( addrEnd.nodeType == 1 && addrEnd.childNodes[bookmark.End[0]] && addrEnd.childNodes[bookmark.End[0]].nodeType == 3 )
478		{
479			var curNode = addrEnd.childNodes[bookmark.End[0]] ;
480			var offset = 0 ;
481			while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
482			{
483				curNode = curNode.previousSibling ;
484				offset += curNode.length ;
485			}
486			addrEnd = curNode ;
487			bookmark.End[0] = offset ;
488		}
489
490		// Then, we record down the precise position of the container nodes
491		// by walking up the DOM tree and counting their childNode index
492		bookmark.Start = FCKDomTools.GetNodeAddress( addrStart, true ).concat( bookmark.Start ) ;
493		bookmark.End = FCKDomTools.GetNodeAddress( addrEnd, true ).concat( bookmark.End ) ;
494		return bookmark;
495	},
496
497	MoveToBookmark2 : function( bookmark )
498	{
499		// Reverse the childNode counting algorithm in CreateBookmark2()
500		var curStart = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.Start.slice( 0, -1 ), true ) ;
501		var curEnd = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.End.slice( 0, -1 ), true ) ;
502
503		// Generate the W3C Range object and update relevant data
504		this.Release( true ) ;
505		this._Range = new FCKW3CRange( this.Window.document ) ;
506		var startOffset = bookmark.Start[ bookmark.Start.length - 1 ] ;
507		var endOffset = bookmark.End[ bookmark.End.length - 1 ] ;
508		while ( curStart.nodeType == 3 && startOffset > curStart.length )
509		{
510			if ( ! curStart.nextSibling || curStart.nextSibling.nodeType != 3 )
511				break ;
512			startOffset -= curStart.length ;
513			curStart = curStart.nextSibling ;
514		}
515		while ( curEnd.nodeType == 3 && endOffset > curEnd.length )
516		{
517			if ( ! curEnd.nextSibling || curEnd.nextSibling.nodeType != 3 )
518				break ;
519			endOffset -= curEnd.length ;
520			curEnd = curEnd.nextSibling ;
521		}
522		this._Range.setStart( curStart, startOffset ) ;
523		this._Range.setEnd( curEnd, endOffset ) ;
524		this._UpdateElementInfo() ;
525	},
526
527	MoveToPosition : function( targetElement, position )
528	{
529		this.SetStart( targetElement, position ) ;
530		this.Collapse( true ) ;
531	},
532
533	/*
534	 * Moves the position of the start boundary of the range to a specific position
535	 * relatively to a element.
536	 *		@position:
537	 *			1 = After Start		<target>^contents</target>
538	 *			2 = Before End		<target>contents^</target>
539	 *			3 = Before Start	^<target>contents</target>
540	 *			4 = After End		<target>contents</target>^
541	 */
542	SetStart : function( targetElement, position, noInfoUpdate )
543	{
544		var oRange = this._Range ;
545		if ( !oRange )
546			oRange = this._Range = this.CreateRange() ;
547
548		switch( position )
549		{
550			case 1 :		// After Start		<target>^contents</target>
551				oRange.setStart( targetElement, 0 ) ;
552				break ;
553
554			case 2 :		// Before End		<target>contents^</target>
555				oRange.setStart( targetElement, targetElement.childNodes.length ) ;
556				break ;
557
558			case 3 :		// Before Start		^<target>contents</target>
559				oRange.setStartBefore( targetElement ) ;
560				break ;
561
562			case 4 :		// After End		<target>contents</target>^
563				oRange.setStartAfter( targetElement ) ;
564		}
565
566		if ( !noInfoUpdate )
567			this._UpdateElementInfo() ;
568	},
569
570	/*
571	 * Moves the position of the start boundary of the range to a specific position
572	 * relatively to a element.
573	 *		@position:
574	 *			1 = After Start		<target>^contents</target>
575	 *			2 = Before End		<target>contents^</target>
576	 *			3 = Before Start	^<target>contents</target>
577	 *			4 = After End		<target>contents</target>^
578	 */
579	SetEnd : function( targetElement, position, noInfoUpdate )
580	{
581		var oRange = this._Range ;
582		if ( !oRange )
583			oRange = this._Range = this.CreateRange() ;
584
585		switch( position )
586		{
587			case 1 :		// After Start		<target>^contents</target>
588				oRange.setEnd( targetElement, 0 ) ;
589				break ;
590
591			case 2 :		// Before End		<target>contents^</target>
592				oRange.setEnd( targetElement, targetElement.childNodes.length ) ;
593				break ;
594
595			case 3 :		// Before Start		^<target>contents</target>
596				oRange.setEndBefore( targetElement ) ;
597				break ;
598
599			case 4 :		// After End		<target>contents</target>^
600				oRange.setEndAfter( targetElement ) ;
601		}
602
603		if ( !noInfoUpdate )
604			this._UpdateElementInfo() ;
605	},
606
607	Expand : function( unit )
608	{
609		var oNode, oSibling ;
610
611		switch ( unit )
612		{
613			// Expand the range to include all inline parent elements if we are
614			// are in their boundary limits.
615			// For example (where [ ] are the range limits):
616			//	Before =>		Some <b>[<i>Some sample text]</i></b>.
617			//	After =>		Some [<b><i>Some sample text</i></b>].
618			case 'inline_elements' :
619				// Expand the start boundary.
620				if ( this._Range.startOffset == 0 )
621				{
622					oNode = this._Range.startContainer ;
623
624					if ( oNode.nodeType != 1 )
625						oNode = oNode.previousSibling ? null : oNode.parentNode ;
626
627					if ( oNode )
628					{
629						while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
630						{
631							this._Range.setStartBefore( oNode ) ;
632
633							if ( oNode != oNode.parentNode.firstChild )
634								break ;
635
636							oNode = oNode.parentNode ;
637						}
638					}
639				}
640
641				// Expand the end boundary.
642				oNode = this._Range.endContainer ;
643				var offset = this._Range.endOffset ;
644
645				if ( ( oNode.nodeType == 3 && offset >= oNode.nodeValue.length ) || ( oNode.nodeType == 1 && offset >= oNode.childNodes.length ) || ( oNode.nodeType != 1 && oNode.nodeType != 3 ) )
646				{
647					if ( oNode.nodeType != 1 )
648						oNode = oNode.nextSibling ? null : oNode.parentNode ;
649
650					if ( oNode )
651					{
652						while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
653						{
654							this._Range.setEndAfter( oNode ) ;
655
656							if ( oNode != oNode.parentNode.lastChild )
657								break ;
658
659							oNode = oNode.parentNode ;
660						}
661					}
662				}
663
664				break ;
665
666			case 'block_contents' :
667			case 'list_contents' :
668				var boundarySet = FCKListsLib.BlockBoundaries ;
669				if ( unit == 'list_contents' || FCKConfig.EnterMode == 'br' )
670					boundarySet = FCKListsLib.ListBoundaries ;
671
672				if ( this.StartBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' )
673					this.SetStart( this.StartBlock, 1 ) ;
674				else
675				{
676					// Get the start node for the current range.
677					oNode = this._Range.startContainer ;
678
679					// If it is an element, get the node right before of it (in source order).
680					if ( oNode.nodeType == 1 )
681					{
682						var lastNode = oNode.childNodes[ this._Range.startOffset ] ;
683						if ( lastNode )
684							oNode = FCKDomTools.GetPreviousSourceNode( lastNode, true ) ;
685						else
686							oNode = oNode.lastChild || oNode ;
687					}
688
689					// We must look for the left boundary, relative to the range
690					// start, which is limited by a block element.
691					while ( oNode
692							&& ( oNode.nodeType != 1
693								|| ( oNode != this.StartBlockLimit
694									&& !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
695					{
696						this._Range.setStartBefore( oNode ) ;
697						oNode = oNode.previousSibling || oNode.parentNode ;
698					}
699				}
700
701				if ( this.EndBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' && this.EndBlock.nodeName.toLowerCase() != 'li' )
702					this.SetEnd( this.EndBlock, 2 ) ;
703				else
704				{
705					oNode = this._Range.endContainer ;
706					if ( oNode.nodeType == 1 )
707						oNode = oNode.childNodes[ this._Range.endOffset ] || oNode.lastChild ;
708
709					// We must look for the right boundary, relative to the range
710					// end, which is limited by a block element.
711					while ( oNode
712							&& ( oNode.nodeType != 1
713								|| ( oNode != this.StartBlockLimit
714									&& !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
715					{
716						this._Range.setEndAfter( oNode ) ;
717						oNode = oNode.nextSibling || oNode.parentNode ;
718					}
719
720					// In EnterMode='br', the end <br> boundary element must
721					// be included in the expanded range.
722					if ( oNode && oNode.nodeName.toLowerCase() == 'br' )
723						this._Range.setEndAfter( oNode ) ;
724				}
725
726				this._UpdateElementInfo() ;
727		}
728	},
729
730	/**
731	 * Split the block element for the current range. It deletes the contents
732	 * of the range and splits the block in the collapsed position, resulting
733	 * in two sucessive blocks. The range is then positioned in the middle of
734	 * them.
735	 *
736	 * It returns and object with the following properties:
737	 *		- PreviousBlock	: a reference to the block element that preceeds
738	 *		  the range after the split.
739	 *		- NextBlock : a reference to the block element that preceeds the
740	 *		  range after the split.
741	 *		- WasStartOfBlock : a boolean indicating that the range was
742	 *		  originaly at the start of the block.
743	 *		- WasEndOfBlock : a boolean indicating that the range was originaly
744	 *		  at the end of the block.
745	 *
746	 * If the range was originaly at the start of the block, no split will happen
747	 * and the PreviousBlock value will be null. The same is valid for the
748	 * NextBlock value if the range was at the end of the block.
749	 */
750	SplitBlock : function()
751	{
752		if ( !this._Range )
753			this.MoveToSelection() ;
754
755		// The range boundaries must be in the same "block limit" element.
756		if ( this.StartBlockLimit == this.EndBlockLimit )
757		{
758			// Get the current blocks.
759			var eStartBlock		= this.StartBlock ;
760			var eEndBlock		= this.EndBlock ;
761			var oElementPath	= null ;
762
763			if ( FCKConfig.EnterMode != 'br' )
764			{
765				if ( !eStartBlock )
766				{
767					eStartBlock = this.FixBlock( true ) ;
768					eEndBlock	= this.EndBlock ;	// FixBlock may have fixed the EndBlock too.
769				}
770
771				if ( !eEndBlock )
772					eEndBlock = this.FixBlock( false ) ;
773			}
774
775			// Get the range position.
776			var bIsStartOfBlock	= ( eStartBlock != null && this.CheckStartOfBlock() ) ;
777			var bIsEndOfBlock	= ( eEndBlock != null && this.CheckEndOfBlock() ) ;
778
779			// Delete the current contents.
780			if ( !this.CheckIsEmpty() )
781				this.DeleteContents() ;
782
783			if ( eStartBlock && eEndBlock && eStartBlock == eEndBlock )
784			{
785				if ( bIsEndOfBlock )
786				{
787					oElementPath = new FCKElementPath( this.StartContainer ) ;
788					this.MoveToPosition( eEndBlock, 4 ) ;
789					eEndBlock = null ;
790				}
791				else if ( bIsStartOfBlock )
792				{
793					oElementPath = new FCKElementPath( this.StartContainer ) ;
794					this.MoveToPosition( eStartBlock, 3 ) ;
795					eStartBlock = null ;
796				}
797				else
798				{
799					// Extract the contents of the block from the selection point to the end of its contents.
800					this.SetEnd( eStartBlock, 2 ) ;
801					var eDocFrag = this.ExtractContents() ;
802
803					// Duplicate the block element after it.
804					eEndBlock = eStartBlock.cloneNode( false ) ;
805					eEndBlock.removeAttribute( 'id', false ) ;
806
807					// Place the extracted contents in the duplicated block.
808					eDocFrag.AppendTo( eEndBlock ) ;
809
810					FCKDomTools.InsertAfterNode( eStartBlock, eEndBlock ) ;
811
812					this.MoveToPosition( eStartBlock, 4 ) ;
813
814					// In Gecko, the last child node must be a bogus <br>.
815					// Note: bogus <br> added under <ul> or <ol> would cause lists to be incorrectly rendered.
816					if ( FCKBrowserInfo.IsGecko &&
817							! eStartBlock.nodeName.IEquals( ['ul', 'ol'] ) )
818						FCKTools.AppendBogusBr( eStartBlock ) ;
819				}
820			}
821
822			return {
823				PreviousBlock	: eStartBlock,
824				NextBlock		: eEndBlock,
825				WasStartOfBlock : bIsStartOfBlock,
826				WasEndOfBlock	: bIsEndOfBlock,
827				ElementPath		: oElementPath
828			} ;
829		}
830
831		return null ;
832	},
833
834	// Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
835	FixBlock : function( isStart )
836	{
837		// Bookmark the range so we can restore it later.
838		var oBookmark = this.CreateBookmark() ;
839
840		// Collapse the range to the requested ending boundary.
841		this.Collapse( isStart ) ;
842
843		// Expands it to the block contents.
844		this.Expand( 'block_contents' ) ;
845
846		// Create the fixed block.
847		var oFixedBlock = this.Window.document.createElement( FCKConfig.EnterMode ) ;
848
849		// Move the contents of the temporary range to the fixed block.
850		this.ExtractContents().AppendTo( oFixedBlock ) ;
851		FCKDomTools.TrimNode( oFixedBlock ) ;
852
853		// Insert the fixed block into the DOM.
854		this.InsertNode( oFixedBlock ) ;
855
856		// Move the range back to the bookmarked place.
857		this.MoveToBookmark( oBookmark ) ;
858
859		return oFixedBlock ;
860	},
861
862	Release : function( preserveWindow )
863	{
864		if ( !preserveWindow )
865			this.Window = null ;
866
867		this.StartNode = null ;
868		this.StartContainer = null ;
869		this.StartBlock = null ;
870		this.StartBlockLimit = null ;
871		this.EndNode = null ;
872		this.EndContainer = null ;
873		this.EndBlock = null ;
874		this.EndBlockLimit = null ;
875		this._Range = null ;
876		this._Cache = null ;
877	},
878
879	CheckHasRange : function()
880	{
881		return !!this._Range ;
882	},
883
884	GetTouchedStartNode : function()
885	{
886		var range = this._Range ;
887		var container = range.startContainer ;
888
889		if ( range.collapsed || container.nodeType != 1 )
890			return container ;
891
892		return container.childNodes[ range.startOffset ] || container ;
893	},
894
895	GetTouchedEndNode : function()
896	{
897		var range = this._Range ;
898		var container = range.endContainer ;
899
900		if ( range.collapsed || container.nodeType != 1 )
901			return container ;
902
903		return container.childNodes[ range.endOffset - 1 ] || container ;
904	}
905} ;