1////////////////////////////////////////////////////////////////////////////////
2//
3// ADOBE SYSTEMS INCORPORATED
4// Copyright 2007-2010 Adobe Systems Incorporated
5// All Rights Reserved.
6//
7// NOTICE:  Adobe permits you to use, modify, and distribute this file
8// in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11package flashx.textLayout.edit
12{
13	import flash.utils.getQualifiedClassName;
14
15	import flashx.textLayout.conversion.ConverterBase;
16	import flashx.textLayout.debug.assert;
17	import flashx.textLayout.elements.ContainerFormattedElement;
18	import flashx.textLayout.elements.DivElement;
19	import flashx.textLayout.elements.FlowElement;
20	import flashx.textLayout.elements.FlowGroupElement;
21	import flashx.textLayout.elements.FlowLeafElement;
22	import flashx.textLayout.elements.LinkElement;
23	import flashx.textLayout.elements.ListItemElement;
24	import flashx.textLayout.elements.ParagraphElement;
25	import flashx.textLayout.elements.SpanElement;
26	import flashx.textLayout.elements.SubParagraphGroupElementBase;
27	import flashx.textLayout.elements.TCYElement;
28	import flashx.textLayout.elements.TextFlow;
29	import flashx.textLayout.formats.ITextLayoutFormat;
30	import flashx.textLayout.formats.TextLayoutFormat;
31	import flashx.textLayout.tlf_internal;
32
33	use namespace tlf_internal;
34
35	[ExcludeClass]
36	/**
37	 * Encapsulates all methods necessary for dynamic editing of a text.  The methods are all static member functions of this class.
38     * @private - because we can't make it tlf_internal. Used by the operations package
39	 */
40	public class TextFlowEdit
41	{
42		tlf_internal static function deleteRange(textFlow:TextFlow, startPos:int, endPos:int):ParagraphElement
43		{
44			var mergePara:ParagraphElement;
45
46			// If the range to be deleted contains the paragraph end, we may have to merge up the paragraphs when we're done.
47			if (endPos > startPos)
48			{
49				var firstLeafInRange:FlowLeafElement = textFlow.findLeaf(startPos);
50				var lastLeafInRange:FlowLeafElement = textFlow.findLeaf(endPos - 1);
51				var firstParagraphInRange:ParagraphElement = firstLeafInRange.getParagraph();
52				var lastParagraphInRange:ParagraphElement = lastLeafInRange.getParagraph();
53				var firstParaStart:int = firstParagraphInRange.getAbsoluteStart();
54				var lastParaEnd:int = lastParagraphInRange.getAbsoluteStart() + lastParagraphInRange.textLength;
55				// If the selection is inside a single paragraph, merge only if the terminator is included and the start of the paragraph is not.
56				// If the two paragraphs are different, merge unless the start and end match exactly
57				// Don't merge if the paragraph is an empty paragraph in a list item that has other content (it will just come back again in normalize)
58				var doMerge:Boolean = false;
59				if (firstParagraphInRange == lastParagraphInRange)
60					doMerge = (endPos == lastParaEnd && startPos != firstParaStart);
61				else
62					doMerge = (startPos != firstParaStart);
63				if (doMerge)
64				{
65					var followingLeaf:FlowLeafElement = textFlow.findLeaf(endPos);
66					if (followingLeaf)
67					{
68						mergePara = followingLeaf.getParagraph();
69						if (mergePara.textLength == 1 && mergePara.parent is ListItemElement && mergePara.parent.numChildren > 1)
70							mergePara = null;
71					}
72				}
73			}
74
75			deleteRangeInternal(textFlow, startPos, endPos - startPos);
76
77			if (mergePara)
78			{
79				var previousLeaf:FlowLeafElement = mergePara.getFirstLeaf().getPreviousLeaf();
80				mergePara = previousLeaf ? previousLeaf.getParagraph() : null;
81			}
82
83			return mergePara;
84		}
85
86		private static function deleteRangeInternal(element:FlowGroupElement, relativeStart:int, numToDelete:int):void
87		{
88			var pendingDeleteStart:int = -1;
89			var pendingDeleteCount:int = 0;
90
91			var childIndex:int = element.findChildIndexAtPosition(relativeStart);
92			while (numToDelete > 0 && childIndex < element.numChildren)
93			{
94				var child:FlowElement = element.getChildAt(childIndex);
95				if (relativeStart <= child.parentRelativeStart && numToDelete >= child.textLength)	// remove the entire child
96				{
97					if (pendingDeleteStart < 0)
98						pendingDeleteStart = childIndex;
99					pendingDeleteCount++;
100					numToDelete -= child.textLength;
101				}
102				else // deleting part of the child
103				{
104					if (pendingDeleteStart >= 0)
105					{
106						element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount);
107						childIndex -= pendingDeleteCount;
108						pendingDeleteStart = -1;
109						pendingDeleteCount = 0;
110					}
111					var childStart:int = child.parentRelativeStart;
112					var childRelativeStart:int = Math.max(relativeStart - childStart, 0);
113					var childNumToDelete:int = Math.min(child.textLength - childRelativeStart, numToDelete);
114					if (child is SpanElement)
115					{
116						var span:SpanElement = child as SpanElement;
117						span.replaceText(childRelativeStart, childRelativeStart + childNumToDelete, "");
118						numToDelete -= childNumToDelete;
119					}
120					else
121					{
122						CONFIG::debug { assert (child is FlowGroupElement, "Expected FlowGroupElement"); }
123						deleteRangeInternal(child as FlowGroupElement, childRelativeStart, childNumToDelete);
124						numToDelete -= childNumToDelete;
125					}
126				}
127				childIndex++
128			}
129			if (pendingDeleteStart >= 0)
130				element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount);
131		}
132
133		// Find the lowest possible FlowElement ancestor of element that can accept prospectiveChild as a child element.
134		private static function findLowestPossibleParent(element:FlowGroupElement, prospectiveChild:FlowElement):FlowGroupElement
135		{
136			while (element && !element.canOwnFlowElement(prospectiveChild))
137				element = element.parent;
138			return element;
139		}
140
141		private static function removePasteAttributes(element:FlowElement):void
142		{
143			if (!element)
144				return;
145
146			if (element is FlowGroupElement && element.format)
147			{
148				var flowGroupElement:FlowGroupElement = FlowGroupElement(element);
149				if (element.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) !== undefined)
150					removePasteAttributes(flowGroupElement.getChildAt(flowGroupElement.numChildren - 1));
151			}
152			element.setStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE, undefined);
153		}
154
155		// Apply the formatting attributes from the (soon to be) previous element to the insertThis element(s). Used when we're about to
156		// insert the element(s) and we want it to adopt the formatting of its context.
157		private static function applyFormatToElement(destinationElement:FlowGroupElement, childIndex:int, insertThis:Object):void
158		{
159			var formatSourceSibling:FlowElement;
160
161			// find the previous sibling and use its formats for the new siblings
162			if (childIndex > 0)
163				formatSourceSibling = destinationElement.getChildAt(childIndex - 1);
164			else
165				formatSourceSibling = destinationElement.getChildAt(0);
166			if (formatSourceSibling)
167			{
168				var spanFormat:ITextLayoutFormat;
169				if (formatSourceSibling is FlowGroupElement)	// take all levels from the sibling down to the root into account
170				{
171					var element:FlowElement = FlowGroupElement(formatSourceSibling).getLastLeaf();
172					var concatFormat:TextLayoutFormat;
173					while (element != formatSourceSibling.parent)
174					{
175						if (element.format)
176						{
177							if (!concatFormat)
178								concatFormat = new TextLayoutFormat(element.format);
179							else
180								concatFormat.concatInheritOnly(element.format);
181						}
182						element = element.parent;
183					}
184					spanFormat = concatFormat;
185				}
186				else
187					spanFormat = formatSourceSibling.format;
188
189				if (insertThis is Array)
190				{
191					for each (var scrapElement:FlowElement in insertThis)
192						if (scrapElement is FlowLeafElement)
193							scrapElement.format = spanFormat;
194						else
195							scrapElement.format = formatSourceSibling.format;
196				}
197				else if (insertThis is FlowLeafElement)
198					insertThis.format = spanFormat;
199				else
200					insertThis.format = formatSourceSibling.format;
201			}
202		}
203		/**
204		 * Replaces the range of text positions that the <code>startPos</code> and
205		 * <code>endPos</code> parameters specify with the <code>textScrap</code> parameter in
206		 * <code>theFlow</code>.
207		 * <p>To delete elements, pass <code>null</code> for <code>newTextFlow</code>.</p>
208		 * <p>To insert an element, pass the same value for <code>startPos</code> and <code>endPos</code>.
209		 * <p>The new element will be inserted before the specified index.</p>
210		 * <p>To append the TextFlow, pass <code>theFlow.length</code> for <code>startPos</code> and <code>endPos</code>.</p>
211		 *
212		 * @param textFlow The TextFlow that is being inserted into.
213		 * @param absoluteStart The index value of the first position of the replacement range in the TextFlow.
214		 * @param textScrap The TextScrap to be pasted into theFlow.
215		 */
216		public static function insertTextScrap(textFlow:TextFlow, absoluteStart:int, textScrap:TextScrap, applyFormat:Boolean):int
217		{
218			if (!textScrap)
219				return absoluteStart;
220
221			var scrapFlow:TextFlow = textScrap.textFlow.deepCopy() as TextFlow;
222			var scrapLeaf:FlowLeafElement = scrapFlow.getFirstLeaf();
223
224			var destinationLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart);
225			var insertPosition:int = absoluteStart;
226
227			var firstParagraph:Boolean = true;
228			var doSplit:Boolean = false;
229
230			while (scrapLeaf)
231			{
232				removePasteAttributes(scrapLeaf);
233				var scrapElement:FlowElement = scrapLeaf;		// highest level complete element in the scrap
234
235				// On the first paragraph, it always merges in to the destination paragraph if the destination paragraph has content
236				var destinationParagraph:ParagraphElement = destinationLeaf.getParagraph();
237				if (firstParagraph && (destinationParagraph.textLength > 1 || applyFormat))
238				{
239					var scrapParagraph:ParagraphElement = scrapLeaf.getParagraph();
240					if (!scrapParagraph.format || scrapParagraph.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined)
241						doSplit = true;
242					scrapElement = scrapParagraph.getChildAt(0);
243				}
244				else
245				{
246					if (applyFormat && firstParagraph)
247					{
248						destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
249						var currentIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart());
250						applyFormatToElement(destinationElement, currentIndex, scrapElement);
251					}
252					// Normally the root element of the scrap is marked as partial, but if not, just assume that its partial (we never paste the TextFlow element)
253					while (scrapElement && scrapElement.parent && (!scrapElement.parent.format || scrapElement.parent.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) && !(scrapElement.parent is TextFlow))
254						scrapElement = scrapElement.parent;
255				}
256
257
258				// Find the lowest level parent in the TextFlow that can accept the scrapElement as a child.
259				// If necessary, copy higher up the scrapElement hierarchy to find a match.
260				var destinationElement:FlowGroupElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
261				while (!destinationElement)
262				{
263					// Nothing in the TextFlow element hierarchy can accept the incoming scrap element.
264					// Go up the scrapElement's hierarchy of partial nodes until we find one that can be inserted.
265					scrapElement = scrapElement.parent;
266					CONFIG::debug { assert(scrapElement != null, "Couldn't find scrapElement that could be pasted"); }
267					destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
268				}
269				CONFIG::debug { assert(destinationElement != null, "insertTextScrap failed to find a FlowElement that can take the scrap element"); }
270
271				removePasteAttributes(scrapElement);
272
273				var destinationStart:int = destinationElement.getAbsoluteStart();
274				if (firstParagraph && doSplit)
275				{
276					// Split the paragraph, and merge the scrap paragraph to the end of the first paragraph of the destination
277					CONFIG::debug { assert(destinationElement is ParagraphElement, "We should be splitting a paragraph"); }
278					ModelEdit.splitElement(textFlow, destinationElement, insertPosition - destinationStart);
279					var scrapParent:FlowGroupElement = scrapElement.parent;
280					var scrapChildren:Array = scrapParent.mxmlChildren;
281					scrapParent.replaceChildren(0, scrapParent.numChildren);
282					if (scrapParent.parent)
283						scrapParent.parent.removeChild(scrapParent);
284					if (applyFormat)
285						applyFormatToElement(destinationElement, destinationElement.numChildren, scrapChildren);
286					destinationElement.replaceChildren(destinationElement.numChildren, destinationElement.numChildren, scrapChildren);
287					scrapElement = destinationElement.getChildAt(destinationElement.numChildren - 1);		// last span pasted, so we'll paste next after this
288					firstParagraph = false;
289				}
290				else
291				{
292					// We're going to add scrapElement as a child of destinationElement at the insertPosition.
293					// Split the children of destinationElement if necessary.
294					var childIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart());
295					var child:FlowElement = destinationElement.getChildAt(childIndex);
296					var childStart:int = child.getAbsoluteStart();
297					if (insertPosition == childStart + child.textLength)
298						++childIndex;
299					else if (insertPosition > childStart)
300					{
301						if (child is FlowLeafElement)
302							child.splitAtPosition(insertPosition - childStart);
303						else
304							ModelEdit.splitElement(textFlow, child as FlowGroupElement, insertPosition - childStart);
305						++childIndex;
306					}
307					if (applyFormat)
308						applyFormatToElement(destinationElement, childIndex, scrapElement);
309					destinationElement.replaceChildren(childIndex, childIndex, scrapElement);
310				}
311
312
313				// Advance to the next destination leaf
314				destinationLeaf = (scrapElement is FlowLeafElement) ? FlowLeafElement(scrapElement).getNextLeaf() : FlowGroupElement(scrapElement).getLastLeaf().getNextLeaf();
315				insertPosition = destinationLeaf ? destinationLeaf.getAbsoluteStart() : textFlow.textLength - 1;
316
317				scrapLeaf = scrapFlow.getFirstLeaf();
318			}
319
320			return insertPosition;
321		}
322
323		/**
324		 * Creates a TCY run out of the selected positions.
325		 * @param theFlow The TextFlow of interest.
326		 * @param startPos The index value of the first position of the TextFlow to be turned into a TCY run.
327		 * @param endPos The index value following the end position of the TextFlow to be turned into a TCY run.
328		 */
329		public static function makeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
330		{
331			var madeTCY:Boolean = true;
332			var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
333			if(!curPara)
334				return false;
335			while(curPara)
336			{
337				var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
338				var curEndPos:int = Math.min(paraEnd, endPos);
339
340				//we have an entire para selected and the para only contains a kParaTerminator char, which cannot be
341				//made into TCY.
342				if(canInsertSPBlock(theFlow, startPos, curEndPos, TCYElement) && curPara.textLength > 1)
343				{
344					var new_tcyElem:TCYElement = new TCYElement();
345
346					//don't hide an error!
347					madeTCY = madeTCY && insertNewSPBlock(theFlow, startPos, curEndPos, new_tcyElem, TCYElement);
348				}
349				else
350					madeTCY = false;
351
352				if(paraEnd < endPos)
353				{
354					curPara = theFlow.findAbsoluteParagraph(curEndPos);
355					startPos = curEndPos;
356				}
357				else
358					curPara = null;
359			}
360
361			return madeTCY;
362		}
363
364		/**
365		 * Creates one or more LinkElements out of the selected positions. It will go through
366		 * every paragraph within the selected position and make links.
367		 * @param theFlow The TextFlow of interest.
368		 * @param startPos The index value of the first position of the TextFlow to be turned into a link.
369		 * @param endPos The index value following the end position of the TextFlow to be turned into a link.
370		 * @param urlString The url string to be associated with the link.
371		 */
372		public static function makeLink(theFlow:TextFlow, startPos:int, endPos:int, urlString:String, target:String):Boolean
373		{
374			var madeLink:Boolean = true;
375			var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
376			if(!curPara)
377				return false;
378
379			while(curPara)
380			{
381				var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
382				var curEndPos:int = Math.min(paraEnd, endPos);
383				var linkEndPos:int = (curEndPos == paraEnd) ? (curEndPos - 1) : curEndPos;
384				if (linkEndPos > startPos)
385				{
386					//if the end of the paragraph is < endPos, we are going across bounds
387					if(!canInsertSPBlock(theFlow, startPos, linkEndPos, LinkElement))
388					{
389						return false;
390					}
391
392					var newLinkElement:LinkElement = new LinkElement();
393					newLinkElement.href = urlString;
394					newLinkElement.target = target;
395
396					//don't hide an error!
397					madeLink = madeLink && insertNewSPBlock(theFlow, startPos, linkEndPos, newLinkElement, LinkElement);
398				}
399				if(paraEnd < endPos)
400				{
401					curPara = theFlow.findAbsoluteParagraph(curEndPos);
402					startPos = curEndPos;
403				}
404				else
405					curPara = null;
406			}
407
408			return madeLink;
409		}
410
411
412		/**
413		 * Removes the TCY block at the selected positions.
414		 * @param theFlow The TextFlow of interest.
415		 * @param startPos The index value of the first position of the TextFlow.
416		 * @param endPos The index value following the end position of the TextFlow.
417		 */
418		public static function removeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
419		{
420			if (endPos <= startPos)
421			{
422				return false;
423			}
424
425			return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, TCYElement);
426		}
427
428		/**
429		 * Removes all LinkElements under the selected positions. It will go through
430		 * every paragraph within the selected position and remove any link.
431		 * @param theFlow The TextFlow of interest.
432		 * @param startPos The index value of the first position of the TextFlow.
433		 * @param endPos The index value following the end position of the TextFlow.
434		 */
435		public static function removeLink(theFlow:TextFlow, startPos:int, endPos:int):Boolean
436		{
437			if (endPos <= startPos)
438			{
439				return false;
440			}
441
442			return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, LinkElement);
443		}
444
445		/**
446		 * @private
447		 * insertNewSPBlock - add a SubParagraphGroupElementBase (spg) to <code>theFlow</code> at the indicies specified by <code>startPos</code> and
448		 * <code>endPos</code>.  The <code>newSPB</code> will take ownership of any FlowElements within the range and will split them
449		 * as needed.  If the parent of the FlowGroupElement indicated by <code>startPos</code> is the same as <code>spgClass</code> then
450		 * the method fails and returns false because a spg cannot own children of the same class as itself.  Any spg of type <code>spgClass</code>
451		 * found within the indicies, however, is subsumed into <code>newSPB</code>, effectively replacing it.
452		 *
453		 * @param theFlow:TextFlow - The TextFlow that is the destination for the newSPB
454		 * @param startPos:int - The absolute index value of the first position of the range in the TextFlow to perform the insertion.
455		 * @param endPos:int - The index value following the end position of the range in the TextFlow to perform the insertion.
456		 * @param newSPB:SubParagraphGroupElementBase - The new SubParagraphElement which is to be added into theFlow.
457		 * @param spgClass:Class - the class of the fbe we intend to add.
458		 *
459		 * Examples: Simple and complex where insertion is of <code>spgClass</code> b.  Selection is l~o
460		 *		1) <a><span>ghijklmnop</span></a>
461		 * 		2) <a><span>ghij</span><b><span>klm</span></b><span>nop</span></a>
462		 * 		3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a>
463		 *
464		 */
465		tlf_internal static function insertNewSPBlock(theFlow:TextFlow, startPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):Boolean
466		{
467			var curPos:int = startPos;
468			var curFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
469			var elementIdx:int = 0;
470
471			CONFIG::debug{ assert(curFBE != null, "No FBE at location curPos(" + curPos + ")!");}
472
473			//if we are at the last "real" glyph of the paragraph, include the terminator.
474			var paraEl:ParagraphElement = curFBE.getParagraph();
475			if(endPos == (paraEl.getAbsoluteStart() + paraEl.textLength - 1))
476				++endPos;
477
478			//before processing this any further, we need to make sure that we are not
479			//splitting a spg which is contained within the same type of spg as the curFBE's parent.
480			//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
481			//to be made within the tcyElement as the link would not function.  As a rule, a SubParagraphElement
482			//cannot own a child of the same class.
483			//
484			//However, if the curFBE is parented by a spg and the objects have the same start and end, then we are doing
485			//a replace and we're not splitting the parent. Check if the bounds are the same and if so, don't skip it...
486			var parentStart:int = curFBE.parent.getAbsoluteStart();
487			var curFBEStart:int = curFBE.getAbsoluteStart();
488			if(curFBE.parent && curFBE.parent is spgClass &&
489				!(parentStart == curFBEStart && parentStart + curFBE.parent.textLength == curFBEStart + curFBE.textLength))
490			{
491				return false;
492			}
493
494			//entire FBE is selected and is not a paragraph, get its parent.
495			if(!(curFBE is ParagraphElement) && curPos == curFBEStart && curPos + curFBE.textLength <= endPos)
496			{
497				elementIdx = curFBE.parent.getChildIndex(curFBE);
498				curFBE = curFBE.parent;
499			}
500			//first, if the curFBE is of the same class as the newSPB, then we need to split it to allow for insertion
501			//of the new one IF the start position is > the start of the curFBE
502			//
503			//running example after this block:
504			//	1) <a><span>ghijk</span><span>lmnop</span></a>
505			//	2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
506			//	3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a> - no change
507			if(curPos >= curFBEStart)
508			{
509				if(!(curFBE is spgClass))
510					elementIdx = findAndSplitElement(curFBE, elementIdx, curPos, true);
511				else
512				{
513					elementIdx = findAndSplitElement(curFBE.parent, curFBE.parent.getChildIndex(curFBE), curPos, false);
514					curFBE = curFBE.parent;
515				}
516			}
517
518			//now that the curFBE has been split, we want to insert the newSPB into the flow and then start absorbing the
519			//contents...
520			//running example after this block:
521			//	1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
522			//	2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
523			//	3) <a><span>ghijk</span><b></b><c><span>lmn</span></c><span>op</span></a> - no change
524			//
525		//	we need another use case here where selection is entire sbp and selection runs from the head of a spg to
526		//	part way through it - so that a) does into parent and b) goes into spg
527
528			// if this is case 2, then the new element must go into the parent...
529			if(curFBE is spgClass)
530			{
531				curFBEStart = curFBE.getAbsoluteStart();
532				elementIdx = curFBE.parent.getChildIndex(curFBE);
533				if(curPos > curFBEStart)//we're splitting the element, not replacing it...
534					elementIdx += 1;
535
536				//if the spg, curFBE is entirely selected then we want to use the parent, not the item itself.
537				while(endPos >= curFBEStart + curFBE.textLength)
538				{
539					//we need access to the parent, which contains both the start and end not the FBE we just split
540					curFBE = curFBE.parent;
541				}
542				curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
543			}
544			else
545			{
546				//we're inserting into the curFBE
547				curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
548			}
549
550			//see subsumeElementsToSPBlock to see effects on running example
551			subsumeElementsToSPBlock(curFBE, elementIdx + 1, curPos, endPos, newSPB, spgClass);
552
553			return true;
554		}
555
556
557		/**
558		 * @private
559		 * splitElement - split <code>elem</code> at the relative index of <code>splitPos</code>.  If <code>splitSubBlockContents</code>
560		 * is true, split the contents of <code>elem</code> if it is a SubParagraphGroupElementBase, otherwise just split <code>elem</code>
561		 *
562		 * @param elem:FlowElement - the FlowElement to split
563		 * @param splitPos:int - The elem relative index indicating where to split
564		 * @param splitSubBlockContents:Boolean - boolean indicating whether a SubParagraphGroupElementBase is to be split OR that it's contents
565		 * should be split.  For example, are we splitting a link or are we splitting the child of the link
566		 *
567		 * <spg><span>ABCDEF</span></spg>
568		 *
569		 * if <code>splitPos</code> indicated index between C and D, then if <code>splitSubBlockContents</code> equals true,
570		 * result is:
571		 *
572		 * <spg><span>ABC</span><span>DEF</span></spg>
573		 *
574		 * if <code>splitSubBlockContents</code> equals false, result is:
575		 *
576		 * <spg><span>ABC</span></spg><spg><span>DEF</span></spg>
577		 */
578		tlf_internal static function splitElement(elem:FlowElement, splitPos:int, splitSubBlockContents:Boolean):void
579		{
580			CONFIG::debug{ assert(splitPos < elem.textLength, "trying to splic FlowElement at illegal index!"); }
581			if (elem is SpanElement)
582			{
583				SpanElement(elem).splitAtPosition(splitPos);
584			}
585			else if(elem is SubParagraphGroupElementBase && splitSubBlockContents)
586			{
587				var subBlock:SubParagraphGroupElementBase = SubParagraphGroupElementBase(elem);
588				// Split the SpanElement of the block at splitPos.  If the item at the splitPos is not a SpanElement, no action occurs.
589				var tempElem:SpanElement = subBlock.findLeaf(splitPos) as SpanElement;
590				if (tempElem)
591					tempElem.splitAtPosition(splitPos - tempElem.getElementRelativeStart(subBlock));
592			}
593			else if (elem is FlowGroupElement)
594			{
595				FlowGroupElement(elem).splitAtPosition(splitPos);
596			}
597			else
598			{
599				CONFIG::debug { assert(false, "Trying to split on an illegal FlowElement"); }
600			}
601		}
602
603		/**
604		 * @private
605		 * findAndSplitElement - starting at the child <code>elementIdx</code> of <code>fbe</code>, iterate
606		 * through the elements untill we find the one located at the aboslute index of <code>startIdx</code>. Upon
607		 * locating the child, split either the element itself OR its children based on the value of <code>splitSubBlockContents</code>
608		 *
609		 * @param fbe:FlowGroupElement - the FBE into which the newSPB is being inserted.
610		 * @param elementIdx:int - The index into the <code>fbe's</code> child list to start
611		 * @param startIdx:int - The absolute index value into the TextFlow.
612		 * @param splitSubBlockContents:Boolean - boolean indicating whether a subElement is to be split OR that it's contents
613		 * should be split.  For example, are we splitting a link or are we splitting the child of the link
614		 *
615		 * <p>ZYX<link>ABCDEF</link>123</p>
616		 *
617		 * if we are inserting a TCY into the link, splitSubBlockContents should be false. We want to split the span ABCDEF such that result is:
618		 * <p>ZYX<link>AB<tcy>CD</tcy>EF</link>123</p>
619		 *
620		 * if we are creating a new link from X to B, then we want the link to split and splitSubBlockContents should be false:
621		 *
622		 * <p>ZY<link>XAB</link><link>CDEF</link>123</p>
623		 *
624		 * @return int - the index of the last child of <code>fbe</code> processed.
625		 */
626		tlf_internal static function findAndSplitElement(fbe:FlowGroupElement, elementIdx:int, startIdx:int, splitSubBlockContents:Boolean):int
627		{
628			var curFlowEl:FlowElement = null;
629			var curIndexInPar:int = startIdx - fbe.getAbsoluteStart();
630
631			while(elementIdx < fbe.numChildren)
632			{
633				curFlowEl = fbe.getChildAt(elementIdx);
634				if (curIndexInPar == curFlowEl.parentRelativeStart)
635					return elementIdx;
636				if ((curIndexInPar > curFlowEl.parentRelativeStart) && (curIndexInPar < curFlowEl.parentRelativeEnd))
637				{
638					splitElement(curFlowEl, curIndexInPar - curFlowEl.parentRelativeStart, splitSubBlockContents);
639				}
640				++elementIdx;
641			}
642			return elementIdx;
643		}
644
645		/**
646		 * @private
647		 * subsumeElementsToSPBlock - incorporates all elements of <code>parentFBE</code> into
648		 * the <code>newSPB</code> between the <code>curPos</code> and <code>endPos</code>.  If a child of
649		 * <code>parentFBE</code> is of type <code>spgClass</code> then the child's contents are removed from the child,
650		 * added to the <code>newSPB</code>, the child is then removed from the <code>parentFBE</code>
651		 *
652		 * @param parentFBE:FlowGroupElement - the FBE into which the newSPB is being inserted.
653		 * @param startPos:int - The index value of the first position of the replacement range in the TextFlow.
654		 * @param endPos:int - The index value following the end position of the replacement range in the TextFlow.
655		 * @param newSPB:SubParagraphGroupElementBase - the new SubParagraphGroupElementBase we intend to insert.
656		 * @param spgClass:Class - the class of the fbe we intend to insert.
657		 *
658		 * @return int - the aboslute index in the text flow after insertion.
659		 *
660		 *  Examples: Simple and complex where insertion is of <code>spgClass</class> b.  Selection is l~o
661		 *		1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
662		 *		2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
663		 *
664		 * 	parentFBE = <a>
665		 *  elementIdx = 1) 2, 2) 3
666		 *  curPos = 5
667		 *  endPos = 9
668		 *  newSPB is of type <b>
669		 */
670		tlf_internal static function subsumeElementsToSPBlock(parentFBE:FlowGroupElement, elementIdx:int, curPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):int
671		{
672			var curFlowEl:FlowElement = null;
673
674			//if we have an invalid index, then skip out.  elementIdx will always point one
675			//element beyond the one we are inserting....
676			if(elementIdx >= parentFBE.numChildren)
677				return curPos;
678
679			while (curPos < endPos)
680			{
681				//running example: curFlowEl is the element immediately after the inserted newSPB:
682				//	1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
683				//		points to span-lmnop
684				//	2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
685				//		points to b-lm
686				curFlowEl = parentFBE.getChildAt(elementIdx);
687
688				//if the curFlowEl is of the Class we are adding (spgClass), and the entire thing is selected,
689				//then we are adding the entire block, but not spliting it - perform the split on the next block
690
691				//I think this can be safely removed from here as ownership of contents is handled below.
692				//leaving in commented out code in case we need to revert - gak 05.01.08
693			/*	if(curFlowEl is spgClass && curPos == curFlowEl.getAbsoluteStart() && curFlowEl.getAbsoluteStart() + curFlowEl.textLength <= endPos)
694				{
695					curPos = parentFBE.getAbsoluteStart() + parentFBE.textLength;
696					continue;
697				}*/
698
699				//if the endPos is less than the length of the curFlowEl, then we need to split it.
700				//if the curFlowEl is NOT of class type spgClass, then we need to break it
701				//
702				//Use case: splitting a link in two (or three as will be the result with head and tail sharing
703				//attributes...
704				//running example 1 hits this, but 2 does not. Using variation of 2:
705				//
706				// example: 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
707				// 			2a) <a><span>fo</span><b></b><b><span>obar</span></b></a> - selection: from o~a
708				//
709				// after this code:
710				//			1) <a><span>ghijk</span><b></b><span>lmno</span><span>p</span></a>
711				//			2a) <a><span>fo</span><b></b><b><span>oba</span></b><b><span>or</span></b></a>
712				if ((curPos + curFlowEl.textLength) > endPos)
713				{
714					splitElement(curFlowEl, endPos - curFlowEl.getAbsoluteStart(), !(curFlowEl is spgClass));  //changed to curFlowEl from newSPB as newSPB should be of type spgClass
715				}
716
717				//add the length before replacing the elements
718				curPos += curFlowEl.textLength;
719
720				//running example: after parentFBE.replaceChildren
721				//
722				//	1) curFlowEl = <span>lmno</span>
723				//		<a><span>ghijk</span><b></b>{curFlowEl}<span>p</span></a>
724				//
725				//	2) curFlowEl = <b><span>lm</span></b>
726				//		<a><span>ghij</span><b><span>k</span></b><b></b>{curFlowEl}<span>nop</span></a>
727				parentFBE.replaceChildren(elementIdx, elementIdx + 1);
728
729				//if the curFlowEl is of type spgClass, then we need to take its children and
730				//add them to the newSPB because a spg cannot contain a child of the same class
731				//as itself
732				//
733				// exmaple: 2) curFlowEl = <b><span>lm</span></b>
734				if (curFlowEl is spgClass)
735				{
736					var subBlock:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase;
737					//elementCount == 1 - <span>lm</span>
738					while (subBlock.numChildren > 0)
739					{
740						//fe[0] = <span>lm</span>
741						var fe:FlowElement = subBlock.getChildAt(0);
742						//<span></span>
743						subBlock.replaceChildren(0, 1);
744						//<b><span>lm</span></b>
745						newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, fe);
746					}
747					//when compelete, example 2 is:
748					//2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
749				}
750				else
751				{
752					//example 1, curFlowEl is <span>lmno</span>, so this is not hit
753					//
754					// extending element <a> from foo~other
755					// <a>foo</a><b>bar<a>other</a><b>
756					// curFlowEl = <b>bar<a>other</a><b>
757					//
758					// since <b> is a spg, we need to walk it's contents and remove any <a> elements
759					if(curFlowEl is SubParagraphGroupElementBase)
760					{
761						//we need to dive into this spgClass and remove any fbes of type spgClass
762						//pass in the curFlowEl as the newSPB, remove any spgs of type spgClass, then
763						//perform the replace on the newSPB passed in here
764						//
765						//ignore the return value of the recursive call as the length has already been
766						//accounted for above
767						flushSPBlock(curFlowEl as SubParagraphGroupElementBase, spgClass);
768					}
769					newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, curFlowEl);
770
771					if(newSPB.numChildren == 1 && curFlowEl is SubParagraphGroupElementBase)
772					{
773						var childSPGE:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase;
774						//running example:
775						//a.precedence = 800, tcy.precedence = kMinSPGEPrecedence
776						//this = <tcy><a><span>fooBar</span></a><tcy>
777						//childSPGE = <a><span>fooBar</span></a>
778						if(childSPGE.textLength == newSPB.textLength && (curPos >= endPos))
779						{
780							CONFIG::debug { assert(childSPGE.precedence != newSPB.precedence, "normalizeRange found two equal SPGEs"); }
781
782							//if the child's precedence is higher than mine, I need to swap
783							if(childSPGE.precedence > newSPB.precedence)
784							{
785								//first, remove the child
786								//this = <tcy></tcy>
787								newSPB.replaceChildren(0,1);
788
789								//we need to flop this object for the child
790								while(childSPGE.numChildren > 0)
791								{
792									//tempFE = <span>fooBar</span>
793									var tempFE:FlowElement = childSPGE.getChildAt(0);
794									//child = <a></a>
795									childSPGE.replaceChildren(0,1);
796									//this = <tcy><span>fooBar</span></tcy>
797									newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, tempFE);
798								}
799
800								var myIdx:int = newSPB.parent.getChildIndex(newSPB);
801								CONFIG::debug{ assert(myIdx >= 0, "Invalid index!  How can a SubParagraphGroupElementBase normalizing not have a parent!"); }
802
803								//add childSPGE in my place
804								newSPB.parent.replaceChildren(myIdx, myIdx + 1, childSPGE)
805
806								//childSPGE = <tcy><a><span>fooBar</span></a></tcy>
807								childSPGE.replaceChildren(0,0,newSPB);
808							}
809						}
810					}
811				}
812
813			}
814
815			return curPos;
816		}
817
818		/**
819		 * @private
820		 * findAndRemoveFlowGroupElement
821		 *
822		 * @param theFlow The TextFlow that is containing the elements to remove.
823		 * @param startPos The index value of the first position of the range in the TextFlow where we want to perform removal.
824		 * @param endPos The index value following the end position of the range in the TextFlow where we want to perform removal.
825		 * @param fbeClass Class the class of the fbe we intend to remove.
826		 *
827		 * Walks through the elements of <code>theFlow</code> looking for any FlowGroupElement of type <code>fbeClass</class>
828		 * On finding one, it removes the FBE's contents and adds them back into the FBE's parent.  If the class of object is
829		 * embedded within another spg and this removal would break the parent spg, then the method does nothing.
830		 *
831		 * Example:
832		 * 	<link>ABC<tcy>DEF</tcy>GHI</link>
833		 * 	Selection is on E and removal of link is attempted.
834		 * 	Because E is a child of a spg (tcy), and removing the link from E would split the parent spg (link),
835		 *  the action is disallowed.
836		 *
837		 * Running example:
838		 * 	1) <link><tcy><span>foo</span></tcy><span>bar</span></link>
839		 * @return Boolean - true if items are removed or none are found.  false if operation is illegal.
840		 */
841		tlf_internal static function findAndRemoveFlowGroupElement(theFlow:TextFlow, startPos:int, endPos:int, fbeClass:Class):Boolean
842		{
843			var curPos:int = startPos;
844			var curEl:FlowElement;
845
846			//walk through the elements
847			while (curPos < endPos)
848			{
849				var containerFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
850
851				//if the start of the parent is the same as the start of the current containerFBE, then
852				//we potentially have the wrong object.  We need to walk up the parents until we get to
853				//the one which starts at our start AND is the topmost object at that index.
854				//example: <a><b>foo</b> bar</a> - getting the object at "f" will yield the <b> element, not <a>
855				while(containerFBE.parent && containerFBE.parent.getAbsoluteStart() == containerFBE.getAbsoluteStart() &&
856					!(containerFBE.parent is ParagraphElement) && !(containerFBE is ParagraphElement)) //don't go beyond paragraph
857				{
858					containerFBE = containerFBE.parent;
859				}
860
861				//if the absoluteFBE is the item we are trying to remove, we need to work with its parent, so
862				//reassign containerFBE.  For example, if an entire link were selected, we'd need to get it's parent to
863				//perform the removal
864				if(containerFBE is fbeClass)
865					containerFBE = containerFBE.parent;
866
867				//before processing this any further, we need to make sure that we are not
868				//splitting a spg which is contained within the same type of spg as the curFBE's parent.
869				//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
870				//to be broken within the tcyElement as the link would have to split the TCY.
871				var ancestorOfFBE:FlowGroupElement = containerFBE.parent;
872				while(ancestorOfFBE != null && !(ancestorOfFBE is fbeClass))
873				{
874					if(ancestorOfFBE.parent is fbeClass)
875					{
876						return false;
877					}
878					ancestorOfFBE = ancestorOfFBE.parent;
879				}
880
881				//if this is a sbe block contained in another sbe, and it is entire within the
882				//selection bounds, we need to use the parent sbe's container.  If it is splitting
883				//the child sbe, we don't allow this and it is handled later...
884				var containerFBEStart:int = containerFBE.getAbsoluteStart();
885				if(ancestorOfFBE is fbeClass && (containerFBEStart >= curPos && containerFBEStart + containerFBE.textLength <= endPos))
886					containerFBE = ancestorOfFBE.parent;
887
888				var childIdx:int = containerFBE.findChildIndexAtPosition(curPos - containerFBEStart);
889				curEl = containerFBE.getChildAt(childIdx);
890				if(curEl is fbeClass)
891				{
892					CONFIG::debug{ assert(curEl is SubParagraphGroupElementBase, "Wrong FBE type!  Trying to remove illeage FBE!"); }
893					var curFBE:FlowGroupElement = curEl as FlowGroupElement;
894
895					//get it's parent and the index of the curFBE
896					var parentBlock:FlowGroupElement = curFBE.parent;
897					var idxInParent:int = parentBlock.getChildIndex(curFBE);
898
899					//if the curPos is not at the head of the SPB, then we need to split it here
900					//curFBE will point to the FBE starting at curPos
901					if(curPos > curFBE.getAbsoluteStart())
902					{
903						splitElement(curFBE, curPos - curFBE.getAbsoluteStart(), false);
904						curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
905						continue;
906					}
907
908					//if curFBE goes beyond the endPos, then we need to split off the tail.
909					if (curFBE.getAbsoluteStart() + curFBE.textLength > endPos)
910					{
911						splitElement(curFBE, endPos - curFBE.getAbsoluteStart(), false);
912					}
913
914					//apply the length of the curFBE to the curPos tracker.  Do this before
915					//removing the contents or it will be 0!
916					curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
917
918					//walk all the contents of the FBE into it's parent container
919					while (curFBE.numChildren > 0)
920					{
921						var childFE:FlowElement = curFBE.getChildAt(0);
922						curFBE.replaceChildren(0, 1);
923						parentBlock.replaceChildren(idxInParent, idxInParent, childFE);
924						idxInParent++;
925					}
926
927					//remove the curFBE
928					parentBlock.replaceChildren(idxInParent, idxInParent + 1);
929				}
930				else if(curEl is SubParagraphGroupElementBase) //check all the parents...
931				{
932					var curSPB:SubParagraphGroupElementBase = SubParagraphGroupElementBase(curEl);
933					if(curSPB.numChildren == 1)
934						curPos = curSPB.getAbsoluteStart() + curSPB.textLength;
935					else
936					{
937						curEl = curSPB.getChildAt(curSPB.findChildIndexAtPosition(curPos - curSPB.getAbsoluteStart()));
938						curPos = curEl.getAbsoluteStart() + curEl.textLength;
939					}
940				}
941				else
942				{
943					//the current block isn't the type we're looking for, so just go to the end of the
944					//FlowElement and continue
945					curPos = curEl.getAbsoluteStart() + curEl.textLength;
946				}
947
948			}
949
950			return true;
951		}
952
953		/**
954		 * @private
955		 * canInsertSPBlock
956		 *
957		 * validate that we a valid selection to allow for insertion of a subBlock.  The rules are as
958		 * follows:
959		 * 	endPos > start
960		 * 	the new block will not span multiple paragraphs
961		 *  if the block is going into a SubParagraphGroupElementBase, it must not split the block:
962		 * 		example:  Text 		- ABCDEFG with a link on CDE
963		 * 		legal new Block		- D, CD, CDE, [n-chars]CDE[n1-chars]
964		 * 		illegal new Block 	- [1 + n-chars]C[D], [D]E[1 + n-chars]
965		 * 			exception - if the newBlock is the same class as the one we are trying to split
966		 * 			then we can truncate the original and add its contents to the new one, or extend it
967		 * 			as appropriate
968		 *
969		 * @param theFlow The TextFlow that is containing the elements to validate.
970		 * @param startPos The index value of the first position of the range in the TextFlow to test.
971		 * @param endPos The index value following the end position of the range in the TextFlow to test.
972		 * @param blockClass Class the class of the fbe we intend to insert.
973		 */
974		tlf_internal static function canInsertSPBlock(theFlow:TextFlow, startPos:int, endPos:int, blockClass:Class):Boolean
975		{
976			if(endPos <= startPos)
977				return false;
978
979			var anchorFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(startPos);
980			if(anchorFBE.getParentByType(blockClass))
981				anchorFBE = anchorFBE.getParentByType(blockClass) as FlowGroupElement;
982
983			var tailFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(endPos - 1);
984			if(tailFBE.getParentByType(blockClass))
985				tailFBE = tailFBE.getParentByType(blockClass) as FlowGroupElement;
986
987			//if these are the same FBEs then we are safe to insert a SubParagraphGroupElementBase
988			if(anchorFBE == tailFBE)
989				return true;
990			//make sure that the two FBEs belong to the same paragraph!
991			else if(anchorFBE.getParagraph() != tailFBE.getParagraph())
992				return false;
993			else if(anchorFBE is blockClass && tailFBE is blockClass)//they're the same class, OK to merge, split, etc...
994				return true;
995			else if(anchorFBE is SubParagraphGroupElementBase && !(anchorFBE is blockClass))
996			{
997				var anchorStart:int = anchorFBE.getAbsoluteStart();
998				if(startPos > anchorStart && endPos > anchorStart + anchorFBE.textLength)
999					return false;
1000			}
1001			else if((anchorFBE.parent is SubParagraphGroupElementBase || tailFBE.parent is SubParagraphGroupElementBase)
1002				&& anchorFBE.parent != tailFBE.parent)
1003			{
1004				//if either FBE parent is a SPGE and they are not the same, prevent the split.
1005				return false;
1006			}
1007
1008			//if we got here, then the anchorFBE is OK, check the tail.  If endPos is pointing to the
1009			//0th character of a FlowGroupElement, we don't need to worry about the tail.
1010			if(tailFBE is SubParagraphGroupElementBase && !(tailFBE is blockClass) && endPos > tailFBE.getAbsoluteStart())
1011			{
1012				var tailStart:int = tailFBE.getAbsoluteStart();
1013				if(startPos < tailStart && endPos < tailStart + tailFBE.textLength)
1014					return false;
1015			}
1016			return true;
1017		}
1018
1019		/**
1020		 * @private flushSPBlock recursively walk a spg looking for elements of type spgClass.  On finding one,
1021		 * remove it's children and then remove the object itself.  Since spg's cannot hold children of the same type
1022		 * as themselves, recursion is only needed for spg's of a class other than that of spgClass.
1023		 *
1024		 * example: subPB = <b>bar<a>other</a><b> extending an <a> element to include all of "other"
1025		 */
1026		tlf_internal static function flushSPBlock(subPB:SubParagraphGroupElementBase, spgClass:Class):void
1027		{
1028			var subParaIter:int = 0;
1029
1030			//example, subPB has 2 elements, <span>bar</span> and <a><span>other</span></a>
1031			while(subParaIter < subPB.numChildren)
1032			{
1033				//subParaIter == 0, subFE = <span>bar</span> skip the FE and move to next
1034				//subParaIter == 1, subFE = <a><span>other</span></a> - is a spgClass
1035				var subFE:FlowElement = subPB.getChildAt(subParaIter);
1036				if(subFE is spgClass)
1037				{
1038					//subParaIter == 1, subFE = <a><span>other</span></a>
1039					var subChildFBE:FlowGroupElement = subFE as FlowGroupElement;
1040					while(subChildFBE.numChildren > 0)
1041					{
1042						//subFEChild = <span>other</span>
1043						var subFEChild:FlowElement = subChildFBE.getChildAt(0);
1044						//subFEChild = <a></a>
1045						subChildFBE.replaceChildren(0, 1);
1046						//subPB = <b>barother<a></a><b>
1047						subPB.replaceChildren(subParaIter, subParaIter, subFEChild);
1048					}
1049
1050					//increment so that subParaIter points to the element we just emptied
1051					++subParaIter;
1052					//remove the empty child
1053					//subPB = <b>barother<b>
1054					subPB.replaceChildren(subParaIter, subParaIter + 1);
1055				}
1056				else if(subFE is SubParagraphGroupElementBase)
1057				{
1058					flushSPBlock(subFE as SubParagraphGroupElementBase, spgClass);
1059					++subParaIter;
1060				}
1061				else
1062					++subParaIter;//go to next child
1063			}
1064		}
1065
1066		/** returns next paragraph in reading order after para. Used for merging paragraphs after delete.  */
1067		tlf_internal static function findNextParagraph(para:ParagraphElement):ParagraphElement
1068		{
1069			if (para)
1070			{
1071				var leaf:FlowLeafElement = para.getLastLeaf();
1072				leaf = leaf.getNextLeaf();
1073				if (leaf)
1074					return leaf.getParagraph();
1075			}
1076			return null;
1077		/*	var sibParagraph:ParagraphElement;
1078			if (para && para.parent)
1079			{
1080				var child:FlowGroupElement = para;
1081				var parent:FlowGroupElement = para.parent;
1082
1083				var myidx:int = parent.getChildIndex(child);
1084
1085				// go up the chain till not on last child
1086				while(myidx == parent.numChildren-1)
1087				{
1088					child = parent;
1089					parent = parent.parent;
1090					myidx = parent.getChildIndex(child);
1091				}
1092				if (myidx != parent.numChildren-1)
1093				{
1094					// go down the first child descendents till reach a paragraph
1095					var sibElement:FlowGroupElement = parent.getChildAt(myidx+1) as FlowGroupElement;
1096					while(sibElement && !(sibElement is ParagraphElement))
1097					{
1098						sibElement = sibElement.getChildAt(0) as FlowGroupElement;
1099					}
1100					sibParagraph = sibElement as ParagraphElement;
1101				}
1102			}
1103			return sibParagraph; */
1104		}
1105
1106		/** if parent is a singleton element, deletes it, then repeats deletion of singletons up the parent chain.  Used after paragraph merge. */
1107		tlf_internal static function removeEmptyParentChain(parent:FlowGroupElement):IMemento
1108		{
1109			var mementoList:MementoList = new MementoList(parent.getTextFlow());
1110			while(parent && (parent.numChildren == 0))
1111			{
1112				var grandParent:FlowGroupElement = parent.parent;
1113				if(grandParent)
1114				{
1115					var parentIdx:int = grandParent.getChildIndex(parent);
1116					mementoList.push(ModelEdit.removeElements(grandParent.getTextFlow(), grandParent, parentIdx, 1));
1117					//grandParent.replaceChildren(parentIdx, parentIdx+1);
1118				}
1119				parent = grandParent;
1120			}
1121			return mementoList;
1122		}
1123
1124		/** Joins this paragraph's next sibling to this if it is a paragraph */
1125		static public function joinNextParagraph(para:ParagraphElement, inSameParent:Boolean):IMemento
1126		{
1127			var nextPara:ParagraphElement = findNextParagraph(para);
1128			if (nextPara && (!inSameParent || para.parent == nextPara.parent))
1129				return joinToElement(para, nextPara);
1130			return null;
1131		}
1132
1133		/** Joins this paragraph's next sibling to this if it is a paragraph */
1134		static public function joinToNextParagraph(para:ParagraphElement, inSameParent:Boolean):MementoList
1135		{
1136			var sibParagraph:ParagraphElement = findNextParagraph(para);
1137			if (sibParagraph && (!inSameParent || para.parent == sibParagraph.parent))
1138				return joinToNextElement(para, sibParagraph);
1139			return null;
1140		}
1141
1142		/** Joins this element2 to element1 -- all children of element2 added to end of element1 */
1143		static public function joinToElement(element1:FlowGroupElement, element2:FlowGroupElement):IMemento
1144		{
1145			var list:MementoList;
1146
1147			if (element1 && element2)
1148			{
1149		/*		list = new MementoList(element1.getTextFlow());
1150
1151				var elementList:Array = element2.mxmlChildren;
1152
1153				list.push(ModelEdit.removeElements(element2.getTextFlow(), element2, 0, element2.numChildren)); // remove children of the second element
1154
1155				for(var i:int=0; i<elementList.length; ++i) // add them to the first element
1156				{
1157					list.push(ModelEdit.addElement(element1.getTextFlow(), elementList[i], element1, element1.numChildren));
1158				}
1159				// remove (empty) element2 and chain of any empty parents
1160				list.push(removeEmptyParentChain(element2));
1161				return list;
1162				*/
1163				return ModelEdit.joinElement(element2.getTextFlow(), element1, element2);
1164			}
1165			return list;
1166		}
1167
1168		/** Joins this element1 to element2 -- all children of element1 added to front of element2 */
1169		static public function joinToNextElement(element1:FlowGroupElement, element2:FlowGroupElement):MementoList
1170		{
1171			var list:MementoList;
1172
1173			if (element1 && element2)
1174			{
1175				list = new MementoList(element1.getTextFlow());
1176
1177				var elementList:Array = element1.mxmlChildren;
1178				list.push(ModelEdit.removeElements(element1.getTextFlow(), element1, 0, element1.numChildren)); // remove children of the first element
1179				for(var i:int=elementList.length - 1; i>=0; --i) // add them to the second element
1180				{
1181					list.push(ModelEdit.addElement(element2.getTextFlow(), elementList[i], element2, 0));
1182				}
1183				// remove (empty) element1 and chain of any empty parents
1184				list.push(removeEmptyParentChain(element1));
1185				return list;
1186			}
1187			return list;
1188		}
1189
1190
1191	}
1192}
1193