TextRangeBase.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Documents / TextRangeBase.cs / 1305600 / TextRangeBase.cs

                            //---------------------------------------------------------------------------- 
//
// File: TextRangeBase.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: Provides an abstract level of TextRange implementation 
//      Implemented as a static class containing a set of methods 
//      implementing members of abstract ITextRange interface.
//      These members are supposed to be called from concrete 
//      classes TextRange and TextSelection - to ensure that the
//      both have the same base implementation.
//
//      TextSelection is allowed to add additional actions over 
//      base ones. TextRange must do pure call redirections,
//      otherwise TextSelection inheritance from TextRange 
//      will be broken. 
//
//      Only methods that require virtualization for TextSelection 
//      implementation go here. All other methods of ITextRange
//      are implemented directly in TextRange clas in appropriate
//      ITextRange.Member.
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents 
{
    using MS.Internal; 
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Globalization; 
    using System.Text;
    using System.Xml; 
    using System.IO; 
    using MS.Internal.Documents;
    using System.Windows.Controls; // TextBlock 

    /// 
    /// A class a portion of text content.
    /// Can be contigous or disjoint; supports rectangular table ranges. 
    /// Provides an API for text and table editing operations.
    ///  
    internal static class TextRangeBase 
    {
        //----------------------------------------------------- 
        //
        // ITextRange Methods
        //
        //----------------------------------------------------- 

        #region ITextRange Methods 
 
        //......................................................
        // 
        // Selection Building
        //
        //......................................................
 
        /// 
        ///  
        // 
        internal static bool Contains(ITextRange thisRange, ITextPointer textPointer)
        { 
            NormalizeRange(thisRange);

            if (textPointer == null)
            { 
                throw new ArgumentNullException("textPointer");
            } 
 
            if (textPointer.TextContainer != thisRange.Start.TextContainer)
            { 
                throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree), "textPointer");
            }

            // Correct position normalization on range boundary so that 
            // our test would not depend on what side of formatting tags
            // pointer is located. 
            if (textPointer.CompareTo(thisRange.Start) < 0) 
            {
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Forward); 
            }
            else if (textPointer.CompareTo(thisRange.End) > 0)
            {
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Backward); 
            }
 
            // Check if at least one segment contains this position. 
            for (int i = 0; i < thisRange._TextSegments.Count; i++)
            { 
                if (thisRange._TextSegments[i].Contains(textPointer))
                {
                    return true;
                } 
            }
 
            return false; 
        }
 
        /// 
        /// Base implementation of ITextRange.Select method.
        /// 
        ///  
        /// The range which is an object of this operation.
        ///  
        ///  
        /// One of two boundary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as an "anchor" position, so the selection will always include
        /// the cell at this position
        /// 
        ///  
        /// The other of two buondary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as a "moving" position, so the selection may not include the cell 
        /// at this position - when the cell has bigger index than the anchor
        /// one and when it is positioned at the very beginning of a cell, 
        /// (if any of these two conditions if false then the cell at this position is included).
        /// 
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2)
        { 
            Select(thisRange, position1, position2, /*includeCellAtMovingPosition:*/false);
        } 
 
        /// 
        /// Base implementation of ITextRange.Select method. 
        /// 
        /// 
        /// The range which is an object of this operation.
        ///  
        /// 
        /// One of two boundary positions for building selection. 
        /// In case of table cell-crossing selection it is considered 
        /// as an "anchor" position, so the selection will always include
        /// the cell at this position 
        /// 
        /// 
        /// The other of two buondary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as a "moving" position, so the selection may not include the cell
        /// at this position - when the cell has bigger index than the anchor 
        /// one and when it is positioned at the very beginning of a cell, 
        /// and when includeCellAtMovingPosition==false (if any of these three
        /// conditions if false then the cell at this position is included). 
        /// 
        /// 
        /// True indicates that a cell at a movingPosition must be included
        /// into a selection even when it is at cell start. 
        /// False indicates that when a movingPosition is at cell start
        /// and the cell has bigger index than anchor cell, then selection 
        /// should not include it - it only indicates cell crossing. 
        /// When we build a table range from existing range's Start/End pair
        /// we must use false for this parameter - because the end position 
        /// of a table range is not included into it - by construction.
        /// When you use independent position - say, from hit-testing -
        /// then you typically use "true" for this parameter, unnless
        /// you intentially cross cell boundary - as for one cell celection. 
        /// 
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition) 
        { 
            if (thisRange._TextSegments == null)
            { 
                // This is initializing call from TextRange constructor.
                // No need in change notifications, no need in position verification.
                TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/false);
            } 
            else
            { 
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position1, "position1"); 
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position2, "position2");
 
                TextRangeBase.BeginChange(thisRange);
                try
                {
                    TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/true); 
                }
                finally 
                { 
                    TextRangeBase.EndChange(thisRange);
                } 
            }
        }

        ///  
        /// Selects a word containing this position
        ///  
        ///  
        /// 
        /// A TextPointer containing a word to select. 
        /// 
        internal static void SelectWord(ITextRange thisRange, ITextPointer position)
        {
            if (position == null) 
            {
                throw new ArgumentNullException("position"); 
            } 

            // Move position to character boundary (also respect atomics) 
            //

            ITextPointer normalizedPosition = position.CreatePointer();
            normalizedPosition.MoveToInsertionPosition(LogicalDirection.Backward); 

            TextSegment wordRange = TextPointerBase.GetWordRange(normalizedPosition); 
 
            TextRangeBase.Select(thisRange, wordRange.Start, wordRange.End);
        } 

        // Returns a word within which empty selection is located.
        // Returns TextSegment.Null if selection is not empty or
        // if the position is between or at word boundary. 
        internal static TextSegment GetAutoWord(ITextRange thisRange)
        { 
            TextSegment autoWordRange = TextSegment.Null; 

            if (thisRange.IsEmpty && // 
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Forward) && //
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Backward))
            {
                // 

                autoWordRange = TextPointerBase.GetWordRange(thisRange.Start); 
                string autoWord = TextRangeBase.GetTextInternal(autoWordRange.Start, autoWordRange.End).TrimEnd(' '); 

                string textFromWordStart = TextRangeBase.GetTextInternal(autoWordRange.Start, thisRange.Start); 

                if (textFromWordStart.Length >= autoWord.Length)
                {
                    // The caret is beyond the end of a word (in a whitespace area) 
                    autoWordRange = TextSegment.Null;
                } 
            } 

            return autoWordRange; 
        }

        /// 
        /// Selects a paragraph around the given position. 
        /// 
        ///  
        ///  
        /// A position identifying a paragraph to select.
        ///  
        internal static void SelectParagraph(ITextRange thisRange, ITextPointer position)
        {
            if (position == null)
            { 
                throw new ArgumentNullException("position");
            } 
 
            ITextPointer start;
            ITextPointer end; 
            FindParagraphOrListItemBoundaries(position, out start, out end);

            // Select the paragraph contents
            TextRangeBase.Select(thisRange, start, end); 
        }
 
        // Apply initial typing heuristics -- adjust range for typing 
        // when it spans one or more TableCells.
        // 
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method.
        internal static void ApplyInitialTypingHeuristics(ITextRange thisRange) 
        {
            // When table cells selected, clear the start cell and collapse selection into it 
            if (thisRange.IsTableCellRange) 
            {
                TableCell cell; 
                if (thisRange.Start is TextPointer &&
                    (cell = TextRangeEditTables.GetTableCellFromPosition((TextPointer)thisRange.Start)) != null)
                {
                    // Select the first cell content to make springload formatting happen below 
                    thisRange.Select(cell.ContentStart, cell.ContentEnd);
                } 
                else 
                {
                    thisRange.Select(thisRange.Start, thisRange.Start); 
                }
            }
        }
 
        // Apply typing heuristics
        //  - extend for overtype. 
        //  - prevent paragraph merges when only the leading edge of that 
        //    last paragraph is selected.
        // 
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method.
        internal static void ApplyFinalTypingHeuristics(ITextRange thisRange, bool overType) 
        {
            // Expand empty selection forward in overtype mode 
            if (overType && thisRange.IsEmpty && 
                !TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Forward))
            { 
                //


                ITextPointer nextPosition = thisRange.End.CreatePointer(); 
                nextPosition.MoveToNextInsertionPosition(LogicalDirection.Forward);
                if (!TextRangeEditTables.IsTableStructureCrossed(thisRange.Start, nextPosition)) 
                { 
                    TextRange range = new TextRange(thisRange.Start, nextPosition);
                    Invariant.Assert(!range.IsTableCellRange); 

                    range.Text = String.Empty;
                }
            } 

            // If the range is non-empty, and its end just passes a paragraph break, 
            // pull the end back to stop a paragraph merge on the next keystroke. 
            if (!thisRange.IsEmpty &&
                (TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Backward) || 
                 TextPointerBase.IsAfterLastParagraph(thisRange.End)))
            {
                ITextPointer newEnd = thisRange.End.GetNextInsertionPosition(LogicalDirection.Backward);
                thisRange.Select(thisRange.Start, newEnd); 
            }
        } 
 
        /// 
        ///  
        /// 
        internal static void ApplyTypingHeuristics(ITextRange thisRange, bool overType)
        {
            BeginChange(thisRange); 
            try
            { 
                ApplyInitialTypingHeuristics(thisRange); 
                ApplyFinalTypingHeuristics(thisRange, overType);
            } 
            finally
            {
                EndChange(thisRange);
            } 
        }
 
        internal static void FindParagraphOrListItemBoundaries(ITextPointer position, out ITextPointer start, out ITextPointer end) 
        {
            // Identify a maximum portion of text around navigator 
            // which may be wrapped by Paragraph
            start = position.CreatePointer();
            end = position.CreatePointer();
            SkipParagraphContent(start, LogicalDirection.Backward); 
            SkipParagraphContent(end, LogicalDirection.Forward);
        } 
 
        // Moves the navigator in the given direction over all characters,
        // embedded objects and formatting tags. 
        //

        private static void SkipParagraphContent(ITextPointer navigator, LogicalDirection direction)
        { 
            TextPointerContext nextContext = navigator.GetPointerContext(direction);
 
            while (true) 
            {
                if (nextContext == TextPointerContext.None // 
                    || //
                    // Entering non-inline content
                    (nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Forward || //
                    nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Backward) && // 
                    !typeof(Inline).IsAssignableFrom(navigator.GetElementType(direction)) //
                    || 
                    // Exiting non-inline content 
                    (nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Forward || //
                    nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Backward) && // 
                    !typeof(Inline).IsAssignableFrom(navigator.ParentType))
                {
                    // End of paragraph content reached. Stop here.
                    break; 
                }
 
                //Need to bail out if MoveToNextContentPosition fails 
                if (!navigator.MoveToNextContextPosition(direction))
                { 
                    break;
                }
                nextContext = navigator.GetPointerContext(direction);
            } 
        }
 
        // Calculates a value of a given property on this range 
        internal static object GetPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty)
        { 
            if (TextSchema.IsCharacterProperty(formattingProperty))
            {
                return GetCharacterPropertyValue(thisRange, formattingProperty);
            } 
            else
            { 
                Invariant.Assert(TextSchema.IsParagraphProperty(formattingProperty), "The property is expected to be one of either character or paragraph formatting one"); 
                return GetParagraphPropertyValue(thisRange, formattingProperty);
            } 
        }

        // Calculates character formatting property
        private static object GetCharacterPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty) 
        {
            // 
 
            object startValue = GetCharacterValueFromPosition(thisRange.Start, formattingProperty);
 
            // Need to run over all text runs to check that the value is the same for all of them.
            // We'll stop on the first different value if any; and return MixedValue.Instance
            for (int i = 0; i < thisRange._TextSegments.Count; i++)
            { 
                TextSegment textSegment = thisRange._TextSegments[i];
 
                ITextPointer position = textSegment.Start.CreatePointer(); 
                bool moved = true;
                while (moved && position.CompareTo(textSegment.End) < 0) 
                {
                    // Check whether the value in this text run is the same as at the beginning
                    object value = GetCharacterValueFromPosition(position, formattingProperty);
                    if (!TextSchema.ValuesAreEqual(value, startValue)) 
                    {
                        return DependencyProperty.UnsetValue; // 
                    } 

                    // Skip text run 
                    if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                    {
                        moved = position.MoveToNextContextPosition(LogicalDirection.Forward);
                    } 

                    // try to skip formatting tags 
                    moved = position.MoveToInsertionPosition(LogicalDirection.Forward); 

                    if (!moved) 
                    {
                        // Go to the next run, of there was no formatting boundary
                        moved = position.MoveToNextInsertionPosition(LogicalDirection.Forward);
                    } 
                }
            } 
 
            return startValue;
        } 

        // Gets a non-inherited property from a given position
        private static object GetCharacterValueFromPosition(ITextPointer pointer, DependencyProperty formattingProperty)
        { 
            object value = null;
 
            if (formattingProperty != Inline.TextDecorationsProperty) 
            {
                value = pointer.GetValue(formattingProperty); 
            }
            else
            {
                if (pointer is TextPointer) // Implement only for concrete TextCotainer returning null otherwise - for optimization 
                {
                    DependencyObject element = ((TextPointer)pointer).Parent as TextElement; 
                    while (value == null && (element is Inline || element is Paragraph || element is TextBlock)) 
                    {
                        value = element.GetValue(formattingProperty); 

                        element = element is TextElement ? ((TextElement)element).Parent : null;
                    }
                } 
            }
 
            return value; 
        }
 
        // Calculates paragraph formatting property
        // Returns DependencyProperty.UnsetValue if different areas of the range have different value for this property.
        private static object GetParagraphPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty)
        { 
            object startValue = null;
 
            // Need to run over all text runs to check that the value is the same for all of them. 
            // We'll stop on the first different value if any; and return MixedValue.Instance
            for (int i = 0; i < thisRange._TextSegments.Count; i++) 
            {
                TextSegment textSegment = thisRange._TextSegments[i];

                ITextPointer position = textSegment.Start.CreatePointer(); 

                // Find position scoped by paragraph - in backward direction - to get start value 
                while (!typeof(Paragraph).IsAssignableFrom(position.ParentType) && 
                    position.MoveToNextContextPosition(LogicalDirection.Backward)) ;
 
                // Traverse the segment to find all other paragraph positions
                bool moved = true;
                while (moved && position.CompareTo(textSegment.End) <= 0)
                { 
                    if (typeof(Paragraph).IsAssignableFrom(position.ParentType))
                    { 
                        object value = position.GetValue(formattingProperty); 
                        if (startValue == null)
                        { 
                            startValue = value;
                        }

                        if (!TextSchema.ValuesAreEqual(value, startValue)) 
                        {
                            return DependencyProperty.UnsetValue; 
                        } 

                        position.MoveToElementEdge(ElementEdge.AfterEnd); 
                    }
                    moved = position.MoveToNextContextPosition(LogicalDirection.Forward);
                }
            } 

            // Most properties does not allow null as a value, 
            // so if we still have null try to get a value from range start position. 
            // For some properties (like TextDecorations) it still may remain null.
            if (startValue == null) 
            {
                startValue = thisRange.Start.GetValue(formattingProperty);
            }
 
            return startValue;
        } 
 
        // Returns true if this range start and end pointers cross a paragraph boundary, false otherwise.
        internal static bool IsParagraphBoundaryCrossed(ITextRange thisRange) 
        {
            ITextPointer startNavigator = thisRange.Start.CreatePointer();
            ITextPointer endNavigator = thisRange.End.CreatePointer();
 
            if (TextPointerBase.IsAfterLastParagraph(endNavigator))
            { 
                endNavigator.MoveToInsertionPosition(LogicalDirection.Backward); 
            }
 
            // Walk upto the closest block ancestor
            while (typeof(Inline).IsAssignableFrom(startNavigator.ParentType))
            {
                startNavigator.MoveToElementEdge(ElementEdge.AfterEnd); 
            }
            while (typeof(Inline).IsAssignableFrom(endNavigator.ParentType)) 
            { 
                endNavigator.MoveToElementEdge(ElementEdge.AfterEnd);
            } 

            // start and end are within the scope of the same paragraph?
            return !startNavigator.HasEqualScope(endNavigator);
        } 

        //......................................................... 
        // 
        //  Change Notifications
        // 
        //.........................................................

        /// 
        ///  
        /// 
        internal static void BeginChange(ITextRange thisRange) 
        { 
            BeginChangeWorker(thisRange, String.Empty);
        } 

        /// 
        /// 
        ///  
        internal static void BeginChangeNoUndo(ITextRange thisRange)
        { 
            BeginChangeWorker(thisRange, null); 
        }
 
        /// 
        /// 
        /// 
        internal static void EndChange(ITextRange thisRange) 
        {
            EndChange(thisRange, false /* disableScroll */, false /* skipEvents */ ); 
        } 

        ///  
        /// 
        /// 
        internal static void EndChange(ITextRange thisRange, bool disableScroll, bool skipEvents)
        { 
            ChangeBlockUndoRecord changeBlockUndoRecord;
            bool isChanged; 
            ITextContainer textContainer; 

            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "Unmatched EndChange call!"); 

            textContainer = thisRange.Start.TextContainer;

            try 
            {
                // 
                // Complete the content changed block. 
                //
                try 
                {
                    // Raise first public event -- TextContainer.EndChange.
                    textContainer.EndChange(skipEvents);
                } 
                finally
                { 
                    // Always drop the ChangeBlockLevel, no matter what happens. 
                    // This ensures that we won't ignore future events if the
                    // application recovers from an exception. 
                    thisRange._ChangeBlockLevel--;

                    // Clear out thisRange.IsChanged now so that it isn't
                    // left dangling if TextContainer.EndChange throws 
                    // an exception.
                    isChanged = thisRange._IsChanged; 
                    if (thisRange._ChangeBlockLevel == 0) 
                    {
                        thisRange._IsChanged = false; 
                    }
                }

                // 
                // Complete the range repositioned block.
                // 
                if (thisRange._ChangeBlockLevel == 0 && isChanged) 
                {
                    // Raise the second public event -- TextRange.Changed. 
                    thisRange.NotifyChanged(disableScroll, skipEvents);
                }
            }
            finally 
            {
                // Make sure we close the undo record no matter what happened. 
                changeBlockUndoRecord = (ChangeBlockUndoRecord)thisRange._ChangeBlockUndoRecord; 
                if (changeBlockUndoRecord != null && thisRange._ChangeBlockLevel == 0)
                { 
                    try
                    {
                        changeBlockUndoRecord.OnEndChange();
                    } 
                    finally
                    { 
                        thisRange._ChangeBlockUndoRecord = null; 
                    }
                } 
            }
        }

        internal static void NotifyChanged(ITextRange thisRange, bool disableScroll) 
        {
            thisRange.FireChanged(); 
        } 

        #endregion ITextRange Methods 

        // ....................................................................
        //
        // Static Helpers for dealing with content without range instantiation 
        //
        // .................................................................... 
 
        #region TextRange Helpers
 
        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition)
        { 
            Char[] charArray = null; // used for extracting text runs
 
            return GetTextInternal(startPosition, endPosition, ref charArray); 
        }
 
        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        //
        // Use this overload when looping over large quantities of text to avoid 
        // re-allocating a temporary buffer.
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition, ref Char[] charArray) 
        { 
            // Buffer for building a resulting plain text
            StringBuilder textBuffer = new StringBuilder(); 

            // Stack of List context - needed for efficient bullet generation
            Stack listItemCounter = null;
 
            ITextPointer navigator = startPosition.CreatePointer();
 
            Invariant.Assert(startPosition.CompareTo(endPosition) <= 0, "expecting: startPosition <= endPosition"); 

            while (navigator.CompareTo(endPosition) < 0) 
            {
                Type elementType;

                TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward); 
                switch (symbolType)
                { 
                    case TextPointerContext.Text: 
                        PlainConvertTextRun(textBuffer, navigator, endPosition, ref charArray);
                        break; 
                    case TextPointerContext.ElementEnd:
                        elementType = navigator.ParentType;

                        if (typeof(Paragraph).IsAssignableFrom(elementType) || 
                            typeof(BlockUIContainer).IsAssignableFrom(elementType))
                        { 
                            PlainConvertParagraphEnd(textBuffer, navigator); 
                        }
                        else if (typeof(LineBreak).IsAssignableFrom(elementType)) 
                        {
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                            textBuffer.Append(Environment.NewLine);
                        } 
                        else if (typeof(List).IsAssignableFrom(elementType))
                        { 
                            PlainConvertListEnd(navigator, ref listItemCounter); 
                        }
                        else 
                        {
                            // All other closing tags - just skip them
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        } 
                        break;
                    case TextPointerContext.EmbeddedElement : 
                        textBuffer.Append('\u0020'); // Substitute SPACE for embedded objects. 
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break; 
                    case TextPointerContext.ElementStart :
                        elementType = navigator.GetElementType(LogicalDirection.Forward);
                        if (typeof(AnchoredBlock).IsAssignableFrom(elementType))
                        { 
                            // Floaters and figures must start from a new line
                            textBuffer.Append(Environment.NewLine); 
                        } 
                        else if (typeof(List).IsAssignableFrom(elementType) && navigator is TextPointer)
                        { 
                            // New list level opens
                            PlainConvertListStart(navigator, ref listItemCounter);
                        }
                        else if (typeof(ListItem).IsAssignableFrom(elementType)) 
                        {
                            // List items must be preceeded by a list marker 
                            PlainConvertListItemStart(textBuffer, navigator, ref listItemCounter); 
                        }
                        else 
                        {
                            PlainConvertAccessKey(textBuffer, navigator);
                        }
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
                        break;
 
                    default: 
                        Invariant.Assert(false, "Unexpected vlue for TextPointerContext");
                        break; 
                }
            }

            return textBuffer.ToString(); 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing Text runs 
        private static void PlainConvertTextRun(StringBuilder textBuffer, ITextPointer navigator, ITextPointer endPosition, ref Char[] charArray)
        { 
            // Copy this text run into destination
            int runLength = navigator.GetTextRunLength(LogicalDirection.Forward);
            charArray = EnsureCharArraySize(charArray, runLength);
            runLength = TextPointerBase.GetTextWithLimit(navigator, LogicalDirection.Forward, charArray, 0, runLength, endPosition); 
            textBuffer.Append(charArray, 0, runLength);
            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Paragraph elements. 
        // Outputs \n - for regular paragraphs and TableRow ends or \t for TableCell ends.
        private static void PlainConvertParagraphEnd(StringBuilder textBuffer, ITextPointer navigator)
        {
            // Check for a special case for a single paragraph within a TableCell 
            // which must be serialized as "\t" character.
            navigator.MoveToElementEdge(ElementEdge.BeforeStart); 
            bool theParagraphIsTheFirstInCollection = navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart; 
            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
            navigator.MoveToElementEdge(ElementEdge.AfterEnd); 
            TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward);

            if (theParagraphIsTheFirstInCollection && symbolType == TextPointerContext.ElementEnd &&
                typeof(TableCell).IsAssignableFrom(navigator.ParentType)) 
            {
                // This is an end of a table cell 
 
                navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                symbolType = navigator.GetPointerContext(LogicalDirection.Forward); 
                if (symbolType == TextPointerContext.ElementStart)
                {
                    // Next table cell starts after this one. Use '\t' as a cell separator
                    textBuffer.Append('\t'); 
                }
                else 
                { 
                    // This was the last cell in a row. Use '\r\n' as a line separator
                    textBuffer.Append(Environment.NewLine); 
                }
            }
            else
            { 
                // Ordinary paragraph end
                textBuffer.Append(Environment.NewLine); 
            } 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing ElementStart for List elements.
        // Initializes a stack of list item counters and pushes a new zero for the opened list level.
        private static void PlainConvertListStart(ITextPointer navigator, ref Stack listItemCounter)
        { 
            List list = (List)navigator.GetAdjacentElement(LogicalDirection.Forward);
 
            // Initialize list context 
            if (listItemCounter == null)
            { 
                listItemCounter = new Stack(1);
            }
            listItemCounter.Push(0);
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Listelements 
        // Pops a current value from a stack of list item indices. 
        private static void PlainConvertListEnd(ITextPointer navigator, ref Stack listItemCounter)
        { 
            // Note that we do not expect List tag balansing:
            // We can get more List closing tags than we had opening ones -
            // it happens when range starts in the middle of a list.
            if (listItemCounter != null && listItemCounter.Count > 0) 
            {
                listItemCounter.Pop(); 
            } 
            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for ListItem elements
        // Uses s stack of list items indices and updates it for following list items.
        private static void PlainConvertListItemStart(StringBuilder textBuffer, ITextPointer navigator, ref Stack listItemCounter) 
        {
            if (navigator is TextPointer) // can do somethinng useful only in concrete TextContainer - not in an abstract one 
            { 
                List list = (List)((TextPointer)navigator).Parent;
                ListItem listItem = (ListItem)navigator.GetAdjacentElement(LogicalDirection.Forward); 

                // Initialize list context
                if (listItemCounter == null)
                { 
                    listItemCounter = new Stack(1);
                } 
                if (listItemCounter.Count == 0) 
                {
                    // List is taken from its middle position. Need to identify starting item number 
                    listItemCounter.Push(((IList)listItem.SiblingListItems).IndexOf(listItem));
                }

                // Get list item number 
                Invariant.Assert(listItemCounter.Count > 0, "expectinng listItemCounter.Count > 0");
                int listItemIndex = listItemCounter.Pop(); 
                int indexBase = list != null ? list.StartIndex : 0; 
                TextMarkerStyle markerStyle = list != null ? list.MarkerStyle : TextMarkerStyle.Disc;
 
                WriteListMarker(textBuffer, markerStyle, listItemIndex + indexBase);

                // Advance
                listItemIndex++; 
                listItemCounter.Push(listItemIndex);
            } 
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for AccessKey elements 
        // Uses s stack of list items indices and updates it for following list items.
        private static void PlainConvertAccessKey(StringBuilder textBuffer, ITextPointer navigator)
        {
            // Creating an "_" prefix for AccessKey character (represented as a Run with special serialization attribution) 
            object element = navigator.GetAdjacentElement(LogicalDirection.Forward);
            if (AccessText.HasCustomSerialization(element)) 
            { 
                textBuffer.Append(AccessText.AccessKeyMarker);
            } 
        }

        // Helper for GetTextInternal, manages a char buffer.
        // NOTE: Does not preserve the content of a buffer 
        private static Char[] EnsureCharArraySize(Char[] charArray, int textLength)
        { 
            if (charArray == null) 
            {
                charArray = new char[textLength + 10]; 
            }
            else if (charArray.Length < textLength)
            {
                int newLength = charArray.Length * 2; 
                if (newLength < textLength)
                { 
                    newLength = textLength + 10; 
                }
 
                charArray = new Char[newLength];
            }
            return charArray;
        } 

        // Writes a text representation of a list marker 
        private static void WriteListMarker(StringBuilder textBuffer, TextMarkerStyle listMarkerStyle, int listItemNumber) 
        {
            string markerText = null; 
            Char[] charArray = null;

            switch (listMarkerStyle)
            { 
                case TextMarkerStyle.None :
                    markerText = ""; 
                    break; 
                case TextMarkerStyle.Disc :
                    markerText = "\x2022"; // Bullet // not a "\x9f"; 
                    break;
                case TextMarkerStyle.Circle :
                    markerText = "\x25CB"; // White Circle // not a "\xa1";
                    break; 
                case TextMarkerStyle.Square :
                    markerText = "\x25A1"; // White Box // not a "\x71"; 
                    break; 
                case TextMarkerStyle.Box :
                    markerText = "\x25A0"; // Black Box // not a "\xa7"; 
                    break;

                case TextMarkerStyle.Decimal:
                    charArray = ConvertNumberToString(listItemNumber, false, DecimalNumerics); 
                    break;
 
                case TextMarkerStyle.LowerLatin: 
                    charArray = ConvertNumberToString(listItemNumber, true, LowerLatinNumerics);
                    break; 

                case TextMarkerStyle.UpperLatin:
                    charArray = ConvertNumberToString(listItemNumber, true, UpperLatinNumerics);
                    break; 

                case TextMarkerStyle.LowerRoman: 
                    markerText = ConvertNumberToRomanString(listItemNumber, false); 
                    break;
 
                case TextMarkerStyle.UpperRoman:
                    markerText = ConvertNumberToRomanString(listItemNumber, true);
                    break;
            } 

            if (markerText != null) 
            { 
                textBuffer.Append(markerText);
            } 
            else if (charArray != null)
            {
                textBuffer.Append(charArray, 0, charArray.Length);
            } 
            textBuffer.Append('\t');
        } 
 
        private const char NumberSuffix = '.';
 
        private const string DecimalNumerics = "0123456789";
        private const string LowerLatinNumerics = "abcdefghijklmnopqrstuvwxyz";
        private const string UpperLatinNumerics = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
        private static string[][] RomanNumerics = new string[][]
        { 
            new string[] { "m??", "cdm", "xlc", "ivx" }, 
            new string[] { "M??", "CDM", "XLC", "IVX" }
        }; 

        /// 
        /// Convert a number to string, consisting of digits followed by the NumberSuffix character.
        ///  
        /// Number to convert.
        /// True if there is no zero digit (e.g., alpha numbering). 
        /// Set of digits (e.g., 0-9 or a-z). 
        /// Returns the number string as an array of characters.
        private static char[] ConvertNumberToString(int number, bool oneBased, string numericSymbols) 
        {
            //  Whether zero-based or one-based numbering is used affects how we
            //  count and how we determine the maximum number of values for a
            //  given number of digits. 
            //
            //  The following table illustrates how counting differs. In both 
            //  cases we're using base-2 numbering (i.e., two distinct digits), 
            //  but with 1-based counting each of those two digits can be a
            //  significant leading digit. 
            //
            //            0-based     1-based
            //    ----------------------------
            //      0           0          -- 
            //      1           1           a
            //      2          10           b 
            //      3          11          aa 
            //      4         100          ab
            //      5         101          ba 
            //      6         110          bb
            //      7         111         aaa
            //      8        1000         aab
            //      9        1001         aba 
            //     10        1010         abb
            //     11        1011         baa 
            //     12        1100         bab 
            //     13        1101         bba
            //     14        1110         bbb 
            //     15        1111        aaaa
            //     16       10000        aaab
            //
            //  For zero-based counting, adding a leading zero does not change 
            //  the value of a number. Thus, the set of all N-digit numbers is
            //  a proper subset of the set of (N+1)-digit numbers. Thus the set 
            //  of values that can be represented by N *or fewer* digits is the 
            //  same as the number of combinations of exactly N digits, i.e.,
            // 
            //      b ^ N
            //
            //  where b is the base of the numbering system.
            // 
            //  For one-based counting, there is no zero digit. Thus, the set
            //  of N-digit numbers and the set of (N+1)-digit numbers are 
            //  disjoint sets. Thus, while the number of combinations of 
            //  *exactly* N digits is still b ^ N, the maximum value that
            //  can be represented by N *or fewer* digits is: 
            //
            //  Max(N)
            //      where N = 1   :   b
            //      where N > 1   :   (b ^ N) + Max(N - 1) 
            //
            if (oneBased) 
            { 
                // Subtract 1 from 1-based numbers so we can use zero-based
                // indexing. The formula for Max(N) given above should now be 
                // thought of as a limit rather than a maximum.
                --number;
            }
 
            Invariant.Assert(number >= 0, "expecting: number >= 0");
 
            char[] result; 

            int b = numericSymbols.Length; 
            if (number < b)
            {
                // Optimize common case of single-digit numbers.
                result = new char[2]; // digit + suffix 
                result[0] = numericSymbols[number];
                result[1] = NumberSuffix; 
            } 
            else
            { 
                // Disjoint is 1 if and only if the set of numbers with N
                // digits and the set of numbers with (N+1) digits are
                // disjoint (see comment above). Otherwise it is zero.
                int disjoint = oneBased ? 1 : 0; 

                // Count digits. 
                int digits = 1; 
                for (int limit = b, pow = b; number >= limit; ++digits)
                { 
                    pow *= b;
                    limit = pow + (limit * disjoint);
                }
 
                // Build string in reverse order starting with suffix.
                result = new char[digits + 1]; // digits + suffix 
                result[digits] = NumberSuffix; 
                for (int i = digits - 1; i >= 0; --i)
                { 
                    result[i] = numericSymbols[number % b];
                    number = (number / b) - disjoint;
                }
            } 

            return result; 
        } 

        ///  
        /// Convert 1-based number to a Roman numeric string
        /// followed by NumberSuffix character.
        /// 
        ///  
        /// Roman number is 1-based. The Roman numeric string is a series of symbols. Following
        /// is the list of symbols and its value. 
        /// 
        ///     Symbol      Value
        ///         I           1 
        ///         V           5
        ///         X          10
        ///         L          50
        ///         C         100 
        ///         D         500
        ///         M        1000 
        /// 
        /// The rule of Roman number prohibits the use of more than 3 consecutive identical symbol
        /// but using subtraction of symbol standing for multiples of 10, so the value 4 is written 
        /// as IV (5-1) rather than IIII.
        ///
        /// Due to the writing rule and the fact that the symbol represents not the numeral digit
        /// but the value of the number. Roman number system cannot represent value larger than 3999. 
        ///
        /// See, http://www.ccsn.nevada.edu/math/ancient_systems.htm 
        /// 
        /// However, there exists a more relaxing use of Roman numbers to represent values 4000 and
        /// 4999 by using 4 consecutive M. The value 4999 is than written as 'MMMMCMXCIX'. Such use 
        /// however is not widely accepted.
        ///
        /// See, http://www.guernsey.net/~sgibbs/roman.html
        /// 
        /// For values larger than 3999, an overscore is used on the symbol to indicate 1000 multiplication.
        ///                                    ___ 
        /// So, value 7000 would be written as VII. This writing rule has a fair amount of disagreement 
        /// since it is widely understood that it is not invented by the Romans and they rarely had a
        /// need for large numbers during their time. Furthermore, accepting this writing rule just 
        /// for the sake of being able to write larger number would create a new limitation of the values
        /// greater than 3,999,999. Unicode 4.0 does not encode these overscore symbols.
        ///
        /// See, http://www.gwydir.demon.co.uk/jo/roman/number.htm 
        ///      http://www.novaroma.org/via_romana/numbers.html
        /// 
        /// Implementation-wise, IE adopts a general limitation of 3999 and simply convert the value 
        /// into a regular numeric form.
        /// 
        /// We'll follow the mainstream and adopt the 3999 limit. The fallback would also do would IE does.
        ///
        /// 
        private static string ConvertNumberToRomanString( 
            int number,
            bool uppercase 
            ) 
        {
            if (number > 3999) 
            {
                // Roman numeric string not supported
                return number.ToString(CultureInfo.InvariantCulture);
            } 

            StringBuilder builder = new StringBuilder(); 
 
            AddRomanNumeric(builder, number / 1000, RomanNumerics[uppercase ? 1 : 0][0]);
            number %= 1000; 
            AddRomanNumeric(builder, number / 100, RomanNumerics[uppercase ? 1 : 0][1]);
            number %= 100;
            AddRomanNumeric(builder, number / 10, RomanNumerics[uppercase ? 1 : 0][2]);
            number %= 10; 
            AddRomanNumeric(builder, number, RomanNumerics[uppercase ? 1 : 0][3]);
 
            builder.Append(NumberSuffix); 

            return builder.ToString(); 
        }


        ///  
        /// Convert number 0 - 9 into Roman numeric
        ///  
        /// string builder 
        /// number to convert
        /// Roman numeric char for one five and ten 
        private static void AddRomanNumeric(
            StringBuilder builder,
            int number,
            string oneFiveTen 
            )
        { 
            Invariant.Assert(number >= 0 && number <= 9, "expecting: number >= 0 && number <= 9"); 

            if (number >= 1 && number <= 9) 
            {
                if (number == 4 || number == 9)
                    builder.Append(oneFiveTen[0]);
 
                if (number == 9)
                { 
                    builder.Append(oneFiveTen[2]); 
                }
                else 
                {
                    if (number >= 4)
                        builder.Append(oneFiveTen[1]);
 
                    for (int i = number % 5; i > 0 && i < 4; i--)
                        builder.Append(oneFiveTen[0]); 
                } 
            }
        } 

        #endregion TextRange Helpers

        //------------------------------------------------------ 
        //
        // ITextRange Properties 
        // 
        //-----------------------------------------------------
 
        #region ITextRange Properties

        //......................................................
        // 
        //  Boundary Positions
        // 
        //...................................................... 

        internal static ITextPointer GetStart(ITextRange thisRange) 
        {
            NormalizeRange(thisRange);

            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for Start position"); 
            return thisRange._TextSegments[0].Start;
        } 
 
        internal static ITextPointer GetEnd(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);

            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for End position");
            return thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 
        }
 
        internal static bool GetIsEmpty(ITextRange thisRange) 
        {
            NormalizeRange(thisRange); 

            // We assume that if a range is empty then it uses the same instance
            // of TextPointer for both Start and End positions.
            Invariant.Assert( 
                (thisRange._TextSegments.Count == 1 &&
                (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End) 
                == 
                (thisRange.Start.CompareTo(thisRange.End) == 0),
                "Range emptiness assumes using one instance of TextPointer for both start and end"); 

            return (thisRange._TextSegments.Count == 1 &&
                 (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End);
        } 

        internal static List GetTextSegments(ITextRange thisRange) 
        { 
            // NOTE: We cannot normalize thisRange because it will rebuild a collection
            // of textSegments and will cause range move notification, leading to stack overflow. 
            //


            return thisRange._TextSegments; 
        }
 
        //...................................................... 
        //
        //  Content - rich and plain 
        //
        //......................................................

        // Implementation of a getter for a ITextRange.Text property 
        internal static string GetText(ITextRange thisRange)
        { 
            NormalizeRange(thisRange); 

            if (!thisRange.IsTableCellRange) 
            {
                //

 
                // Extend the range from its start position to include initial list marker (if any).
                // We do not do this auto-extension inside GetTextInternal to avoid undesirable 
                // "bulleting" effects on random plain text (say, in Run.TextProperty serialization). 
                // THis is TextRange.get_Text-specific feature.
                ITextPointer start = thisRange.Start; 
                while (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                    !typeof(AnchoredBlock).IsAssignableFrom(start.ParentType))
                {
                    // Any start tag is harmless except for AnchoredBlocks that would produce extra NewLines. So don't cross them. 
                    start = start.GetNextContextPosition(LogicalDirection.Backward);
                } 
 
                return TextRangeBase.GetTextInternal(start, thisRange.End);
            } 
            else
            {
                string text;
 
                //
                text = String.Empty; 
                for (int i = 0; i < thisRange._TextSegments.Count; i++) 
                {
                    TextSegment textSegment; 

                    textSegment = thisRange._TextSegments[i];

                    text += TextRangeBase.GetTextInternal(textSegment.Start, textSegment.End); 
                    //
                } 
 
                return text;
            } 
        }

        // Implementation of a setter fot ITextRange.Text property
        internal static void SetText(ITextRange thisRange, string textData) 
        {
            NormalizeRange(thisRange); 
 
            if (textData == null)
            { 
                throw new ArgumentNullException("textData");
            }

            ITextPointer explicitInsertPosition = null; 

            TextRangeBase.BeginChange(thisRange); 
            try 
            {
                // Delete content covered by this range 
                if (!thisRange.IsEmpty)
                {
                    if (thisRange.Start is TextPointer &&
                        ((TextPointer)thisRange.Start).Parent == ((TextPointer)thisRange.End).Parent && 
                        ((TextPointer)thisRange.Start).Parent is Run &&
                        textData.Length > 0) 
                    { 
                        // When textrange start/end are parented by the same Run, we can optimize
                        // and delete content without any checks. 
                        //
                        // Note that NOT doing so has a serious side effect in this case.
                        // Low-level code in TextRangeEdit does not preserve an empty run
                        // with no formatting properties after deletion. 
                        // We dont want to loose the empty Run,
                        // when we are just about to set the range text to non-empty string. 
                        // Otherwise, newly inserted text might have undesirable formatting properties 
                        // applied due to an insertion position within an adjacent Run.
 
                        if (thisRange.Start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text &&
                            thisRange.End.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                        {
                            // If we're deleting with surrounding text, make sure we insert later between the surrounding text. 
                            // Because we will invalidate layout with the delete, it's possible that thisRange.Start
                            // will normalize itself to a different character offset on the next reference. 
                            // This is because -- unfortunately -- when layout is valid we use ITextView.IsAtCaretUnitBoundary 
                            // to normalize unicode offsets, but when layout is dirty we use a different code path
                            // that ignores the current font and simply checks Unicode values for surrogates and 
                            // combining marks.  See bug 1683515 for an example.
                            explicitInsertPosition = thisRange.Start;
                        }
 
                        TextContainer textContainer = ((TextPointer)thisRange.Start).TextContainer;
                        textContainer.DeleteContentInternal((TextPointer)thisRange.Start, (TextPointer)thisRange.End); 
                    } 
                    else
                    { 
                        thisRange.Start.DeleteContentToPosition(thisRange.End);
                    }

                    if (thisRange.Start is TextPointer) 
                    {
                        TextRangeEdit.MergeFlowDirection((TextPointer)thisRange.Start); 
                    } 

                    thisRange.Select(thisRange.Start, thisRange.Start); 
                }

                // Insert text at end position
                // Note that the non-emptiness check below is not an optimization: 
                // In case of empty text the code block in it would change an empty range
                // orientation, which is undesirable side effect. 
                // Also if the inserted text is empty we need to avoid ensuring insertion position, 
                // which can create paragraphs etc.
                if (textData.Length > 0) 
                {
                    ITextPointer insertPosition = (explicitInsertPosition == null) ? thisRange.Start : explicitInsertPosition;

                    // Ensure last paragraph existence and prepare ends for the new selection 
                    bool pastedFragmentEndsWithNewLine = textData.EndsWith("\n", StringComparison.Ordinal);
 
                    // We are going to insert paragraph implicitly when the block content becomes totally empty. 
                    // Store the fact that implicit paragraph was inserted to exclude ane extra paragraph break
                    // from the end of pasted fragment 
                    bool implicitParagraphInserted = insertPosition is TextPointer &&
                        TextSchema.IsValidChild(/*position*/insertPosition, /*childType*/typeof(Block)) &&
                        (insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None ||
                        insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) && 
                        (insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None ||
                        insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd); 
 
                    // Make sure that the range is positioned at insertion position
                    if (insertPosition is TextPointer && explicitInsertPosition == null) 
                    {
                        TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition((TextPointer)insertPosition);
                        thisRange.Select(insertionPosition, insertionPosition);
                        insertPosition = thisRange.Start; 
                    }
                    Invariant.Assert(TextSchema.IsInTextContent(insertPosition), "range.Start is expected to be in text content"); 
 
                    ITextPointer newStart = insertPosition.GetFrozenPointer(LogicalDirection.Backward);
                    ITextPointer newEnd = insertPosition.CreatePointer(LogicalDirection.Forward); 

                    if ((newStart is TextPointer) && ((TextPointer)newStart).Paragraph != null)
                    {
                        // Rich text - '\n' must be replaced by Paragraphs 
                        TextPointer insertionPosition = (TextPointer)newStart.CreatePointer(LogicalDirection.Forward);
                        string[] textParagraphs = textData.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None); 
 
                        int length = textParagraphs.Length;
                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) 
                        {
                            length--;
                        }
 
                        for (int i = 0; i < length; i++)
                        { 
                            insertionPosition.InsertTextInRun(textParagraphs[i]); 
                            if (i < length - 1)
                            { 
                                if (insertionPosition.HasNonMergeableInlineAncestor)
                                {
                                    // We cannot split a Hyperlink or other non-mergeable Inline element,
                                    // so insert a space character instead (similar to embedded object). 
                                    // Note that this means, SetText would loose
                                    // paragraph break information in this case. 
                                    insertionPosition.InsertTextInRun(" "); 
                                }
                                else 
                                {
                                    // insertionPosition gets repositioned to just inside
                                    // the following Paragraph.
                                    insertionPosition = insertionPosition.InsertParagraphBreak(); 
                                }
                                // Keep newEnd in [....] with the paragraph break. 
                                // We can't rely on LogicalDirection alone for 
                                // anything other than simple text inserts.
                                newEnd = insertionPosition; 
                            }
                        }

                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) 
                        {
                            // We must include ending paragraph break into a resulting range 
                            newEnd = newEnd.GetNextInsertionPosition(LogicalDirection.Forward); 
                            if (newEnd == null)
                            { 
                                newEnd = newStart.TextContainer.End; // set end of range to IsAfterLastParagraph position
                            }

                            // Note: As a result of this logic with implicitParagraphInserted && pastedFragmentEndsWithNewLine 
                            // we have the following behavior:
                            // Given that: 
                            //    range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd); 
                            // the statement:
                            //    range.Text = "foo\r\n"; 
                            // has the effect of leaving flowDocument with this content (note: just one paragraph):
                            //    foo
                            // and range selecting the whole content:
                            //    range.Text == "foo\r\n" 
                            //
                            // the statement: 
                            //    range.Text = "foo"; 
                            // results with the same content in flowDocument (one paragraph)
                            // but the range is not extended beyond last paragraph end: 
                            //    range.Text == "foo".
                        }
                    }
                    else 
                    {
                        // Non-paragraph text - insert without '\n' conversion 
                        newStart.InsertTextInRun(textData); 
                    }
 
                    // Select the range
                    TextRangeBase.SelectPrivate(thisRange, newStart, newEnd, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/true);
                }
            } 
            finally
            { 
                TextRangeBase.EndChange(thisRange); 
            }
        } 

        internal static string GetXml(ITextRange thisRange)
        {
            NormalizeRange(thisRange); 

            // Create XmlWriter 
            StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture); 
            XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
 
            TextRangeSerialization.WriteXaml(xmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null);

            return stringWriter.ToString();
        } 

        internal static bool CanSave(ITextRange thisRange, string dataFormat) 
        { 
            NormalizeRange(thisRange);
 
            bool canSave = (
                dataFormat == DataFormats.Text ||
                dataFormat == DataFormats.Xaml ||
                (SecurityHelper.CheckUnmanagedCodePermission() && ( 
                    dataFormat == DataFormats.XamlPackage ||
                    dataFormat == DataFormats.Rtf))); 
 
            return canSave;
        } 

        internal static bool CanLoad(ITextRange thisRange, string dataFormat)
        {
            NormalizeRange(thisRange); 

            bool canLoad = ( 
                dataFormat == DataFormats.Text || 
                dataFormat == DataFormats.Xaml ||
                (SecurityHelper.CheckUnmanagedCodePermission() && ( 
                    dataFormat == DataFormats.XamlPackage ||
                    dataFormat == DataFormats.Rtf)));

            return canLoad; 
        }
 
        internal static void Save(ITextRange thisRange, Stream stream, string dataFormat, bool preserveTextElements) 
        {
            if (stream == null) 
            {
                throw new ArgumentNullException("stream");
            }
            if (dataFormat == null) 
            {
                throw new ArgumentNullException("dataFormat"); 
            } 

            NormalizeRange(thisRange); 

            if (dataFormat == DataFormats.Text)
            {
                string text = thisRange.Text; 
                StreamWriter textStreamWriter = new StreamWriter(stream);
                textStreamWriter.Write(text); 
                textStreamWriter.Flush(); 
            }
            else if (dataFormat == DataFormats.Xaml) 
            {
                StreamWriter xamlStreamWriter = new StreamWriter(stream);
                XmlTextWriter xamlXmlWriter = new XmlTextWriter(xamlStreamWriter);
                // Passing null as wpfPayload parameter we request to produce 
                // xaml without images - all of them will be repllaced by whitespaces.
                TextRangeSerialization.WriteXaml(xamlXmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null, preserveTextElements); 
                xamlXmlWriter.Flush(); 
            }
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission()) 
            {
                // Non-null stream here means unconditional request to create a WPF package for the range
                // independently whether there are images in it or not.
                WpfPayload.SaveRange(thisRange, ref stream, /*useFlowDocumentAsRoot:*/false, preserveTextElements); 
            }
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission()) 
            { 
                Stream wpfPayloadMemory = null;
                // Passing null as a wpfPayloadStream we allow to not create wpf package 
                // when it is not needed (there is no images in the range)
                string xamlText = WpfPayload.SaveRange(thisRange, ref wpfPayloadMemory, /*useFlowDocumentAsRoot:*/false);
                // Convert xaml to rtf text to set rtf data into data object.
                string rtfText = TextEditorCopyPaste.ConvertXamlToRtf(xamlText, wpfPayloadMemory); 
                StreamWriter rtfStreamWriter = new StreamWriter(stream);
                rtfStreamWriter.Write(rtfText); 
                rtfStreamWriter.Flush(); 
            }
            else 
            {
                // Unsupported format - thows exception
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat");
            } 
        }
 
        internal static void Load(TextRange thisRange, Stream stream, string dataFormat) 
        {
            if (stream == null) 
            {
                throw new ArgumentNullException("stream");
            }
            if (dataFormat == null) 
            {
                throw new ArgumentNullException("dataFormat"); 
            } 

            NormalizeRange(thisRange); 

            // Reset the stream position to the beginning
            if (stream.CanSeek)
            { 
                stream.Seek(0, SeekOrigin.Begin);
            } 
 
            if (dataFormat == DataFormats.Text)
            { 
                StreamReader textStreamReader = new StreamReader(stream);
                string text = textStreamReader.ReadToEnd();
                thisRange.Text = text;
            } 
            else if (dataFormat == DataFormats.Xaml)
            { 
                StreamReader xamlStreamReader = new StreamReader(stream); 
                string xamlText = xamlStreamReader.ReadToEnd();
                thisRange.Xml = xamlText; 
            }
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission())
            {
                object element = WpfPayload.LoadElement(stream); 
                if (!(element is Section) && !(element is Span))
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream"); 
                }
                thisRange.SetXmlVirtual((TextElement)element); 
            }
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission())
            {
                // 
                StreamReader rtfStreamReader = new StreamReader(stream);
                string rtfText = rtfStreamReader.ReadToEnd(); 
                MemoryStream memoryStream = TextEditorCopyPaste.ConvertRtfToXaml(rtfText); 
                if (memoryStream == null)
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream");
                }
                TextElement textElement = WpfPayload.LoadElement(memoryStream) as TextElement;
                if (!(textElement is Section) && !(textElement is Span)) 
                {
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream"); 
                } 
                thisRange.SetXmlVirtual(textElement);
            } 
            else
            {
                // Unsupported format - thows exception
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat"); 
            }
        } 
 
        // Ref count of open change blocks -- incremented/decremented
        // around BeginChange/EndChange calls. 
        internal static int GetChangeBlockLevel(ITextRange thisRange)
        {
            return thisRange._ChangeBlockLevel;
        } 

        //...................................................... 
        // 
        //  Embedded Object Selection
        // 
        //......................................................

        internal static UIElement GetUIElementSelected(ITextRange range)
        { 
            ITextPointer start = range.Start.CreatePointer();
            TextPointerContext context = start.GetPointerContext(LogicalDirection.Forward); 
            while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) 
            {
                start.MoveToNextContextPosition(LogicalDirection.Forward); 
                context = start.GetPointerContext(LogicalDirection.Forward);
            }
            if (context == TextPointerContext.EmbeddedElement)
            { 
                ITextPointer end = range.End.CreatePointer();
                context = end.GetPointerContext(LogicalDirection.Backward); 
                while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) 
                {
                    end.MoveToNextContextPosition(LogicalDirection.Backward); 
                    context = end.GetPointerContext(LogicalDirection.Backward);
                }
                if (context == TextPointerContext.EmbeddedElement && start.GetOffsetToPosition(end) == 1)
                { 
                    return start.GetAdjacentElement(LogicalDirection.Forward) as UIElement;
                } 
            } 
            return null;
        } 

        //......................................................
        //
        //  Table Selection Properties 
        //
        //...................................................... 
 
        internal static bool GetIsTableCellRange(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);

            return thisRange._IsTableCellRange;
        } 

        #endregion ITextRange Properties 
 
        //------------------------------------------------------
        // 
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // Worker for the BeginChange/BeginChangeNoUndo variants. 
        // If description is null, no default undo unit is opened.
        private static void BeginChangeWorker(ITextRange thisRange, string description) 
        {
            ITextContainer textContainer = thisRange.Start.TextContainer;

            if (description != null && thisRange._ChangeBlockUndoRecord == null && thisRange._ChangeBlockLevel == 0) 
            {
                thisRange._ChangeBlockUndoRecord = new ChangeBlockUndoRecord(textContainer, description); 
            } 

            Invariant.Assert(thisRange._ChangeBlockLevel > 0 || !thisRange._IsChanged, "_changed must be false on new move sequence"); 
            thisRange._ChangeBlockLevel++;

            if (description != null)
            { 
                textContainer.BeginChange();
            } 
            else 
            {
                textContainer.BeginChangeNoUndo(); 
            }
        }

        // Creates a one-segment collection from a pair of text positions 
        // The segment normalization is done by the following rules:
        // 1. If start and end pointers have equal positions, or became equal after normalization, 
        //    then the segment uses one instance of ITextPointer for both ends, 
        //    which guarantees the segment emptiness in the subsequente editing
        //    around it. This single pointer takes orientation from start parameter 
        //    and normalized in that direction.
        // 2. In case when a segment is non-empty, two positions will be created
        //    and normalized inward - towards a segment contents;
        //    Their gravities will be directed outward (start - Backward, end -  Forward), 
        //    so that any insertion happend at segment edge position goes inside a segment
        //    - the behavior we need to inserting stuff into TextRanges. 
        private static void CreateNormalizedTextSegment(ITextRange thisRange, ITextPointer start, ITextPointer end) 
        {
            ValidationHelper.VerifyPositionPair(start, end); 

            // Normalize the segment
            if (start.CompareTo(end) == 0)
            { 
                // When the range is empty we must keep it that way during normalization
                if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection)) 
                { 
                    start = GetNormalizedPosition(thisRange, start, start.LogicalDirection);
                    end = start; 
                }
            }
            else
            { 
                start = GetNormalizedPosition(thisRange, start, LogicalDirection.Forward);
                if (!TextPointerBase.IsAfterLastParagraph(end)) 
                { 
                    // NOTE: Position after the last paragraph is special.
                    // Even though this position is not valid insertion position, 
                    // we allow ranges to reach it. This is necessary to be able
                    // to "select all" content, and select the last paragraph.
                    end = GetNormalizedPosition(thisRange, end, LogicalDirection.Backward);
                } 

                // Collapse range in case of overlapped normalization result 
                if (start.CompareTo(end) >= 0) 
                {
                    // The range is effectuvely empty, so collapse it to single pointer instance 
                    if (start.LogicalDirection == LogicalDirection.Backward)
                    {
                        // Choose a position normalized backward,
                        start = end.GetFrozenPointer(LogicalDirection.Backward); 

                        // NOTE that otherwise we will use start position, 
                        // which is oriented and normalizd Forward 
                    }
                    end = start; 
                }
                else
                {
                    // Handle Floater/Figure boundaries: non-empty ranges never cross them 
                    if (start is TextPointer)
                    { 
                        TextPointer adjustedStart = (TextPointer)start; 
                        TextPointer adjustedEnd = (TextPointer)end;
                        NormalizeAnchoredBlockBoundaries(ref adjustedStart, ref adjustedEnd); 
                        start = adjustedStart;
                        end = adjustedEnd;
                    }
 
                    Invariant.Assert(start.CompareTo(end) <= 0, "expecting start <= end");
 
                    // Normalize the segment, start and end may have become equal now. 
                    if (start.CompareTo(end) == 0)
                    { 
                        // When the range is empty we must keep it that way during normalization
                        if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection))
                        {
                            start = GetNormalizedPosition(thisRange, start, start.LogicalDirection); 
                            end = start;
                        } 
                    } 

                    // 



 

 
 

 
                }
            }

            // Set this text segment as a selected range 
            //
            thisRange._TextSegments = new List(1); 
            thisRange._TextSegments.Add(new TextSegment(start, end)); 
            thisRange._IsTableCellRange = false;
        } 

        private static bool IsAtNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction)
        {
            bool isAtNormalizedPosition; 

            if (thisRange.IgnoreTextUnitBoundaries) 
            { 
                isAtNormalizedPosition = TextPointerBase.IsAtFormatNormalizedPosition(position, direction);
            } 
            else
            {
                isAtNormalizedPosition = TextPointerBase.IsAtInsertionPosition(position, direction);
            } 

            return isAtNormalizedPosition; 
        } 

        private static ITextPointer GetNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction) 
        {
            ITextPointer normalizedPosition;

            if (thisRange.IgnoreTextUnitBoundaries) 
            {
                normalizedPosition = position.GetFormatNormalizedPosition(direction); 
            } 
            else
            { 
                normalizedPosition = position.GetInsertionPosition(direction);
            }

            return normalizedPosition; 
        }
 
        // Helper for CreateNormalizedTextSegment 
        // Checks whether start and end cross any Floater/Figure boundaries.
        // If yes, normalizes the position(s) so that a non-empty range never crosses Floater/Figure boundaries. 
        // Returns true if the range crosses AnchoredBlock boundary and was adjusted to not do so.
        // Returns false if it does not cross AnchoredBlock boundary and start/end stay where they were.
        internal static void NormalizeAnchoredBlockBoundaries(ref TextPointer start, ref TextPointer end)
        { 
            // Check AnchoredBlocks ancestors at start
            TextElement outerAnchoredBlock = start.Parent as TextElement; 
            while (outerAnchoredBlock != null) 
            {
                // Find the next ancestor AncoredBlock 
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType()))
                {
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement;
                } 

                if (outerAnchoredBlock != null) 
                { 
                    // Anchored block found. Check whether the other position belongs to it.
                    AnchoredBlock innerAnchoredBlock = null; 
                    TextElement innerElement = end.Parent as TextElement;
                    while (innerElement != null && innerElement != outerAnchoredBlock)
                    {
                        if (innerElement is AnchoredBlock) 
                        {
                            innerAnchoredBlock = (AnchoredBlock)innerElement; 
                        } 
                        innerElement = innerElement.Parent as TextElement;
                    } 
                    if (innerElement == outerAnchoredBlock)
                    {
                        // Common ancestor AnchoredBlock is found.
                        if (innerAnchoredBlock != null) 
                        {
                            end = innerAnchoredBlock.ElementEnd; 
                        } 
                        return;
                    } 

                    // The AnchoredElement found at start position does not include end.
                    // Expand start to include the whole outerAnchoredBlock
                    start = outerAnchoredBlock.ElementStart; 

                    // and go to the next possible AnchoredBlock level 
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }
            } 

            // Check AnchoredBlocks ancestors at end
            outerAnchoredBlock = end.Parent as TextElement;
            while (outerAnchoredBlock != null) 
            {
                // Find the next ancestor AncoredBlock 
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType())) 
                {
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }

                if (outerAnchoredBlock != null)
                { 
                    // Anchored block found. Check whether the other position belongs to it.
                    AnchoredBlock innerAnchoredBlock = null; 
                    TextElement innerElement = start.Parent as TextElement; 
                    while (innerElement != null && innerElement != outerAnchoredBlock)
                    { 
                        if (innerElement is AnchoredBlock)
                        {
                            innerAnchoredBlock = (AnchoredBlock)innerElement;
                        } 
                        innerElement = innerElement.Parent as TextElement;
                    } 
                    if (innerElement == outerAnchoredBlock) 
                    {
                        // Common ancestor AnchoredBlock is found. 
                        if (innerAnchoredBlock != null)
                        {
                            start = innerAnchoredBlock.ElementStart;
                        } 
                        return;
                    } 
 
                    // The AnchoredElement found at end position does not include start.
                    // Expand end to include the whole outerAnchoredBlock 
                    end = outerAnchoredBlock.ElementEnd;

                    // and go to the next possible AnchoredBlock level
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }
            } 
        } 

        // Method used in all public entry points to 
        // ensure that thisRange is really normalized appropriately.
        private static void NormalizeRange(ITextRange thisRange)
        {
            if (thisRange._ContentGeneration == thisRange._TextSegments[0].Start.TextContainer.Generation) 
            {
                // There were no content changes since range has been built, 
                // so no normalization needed. 
                return;
            } 

            ITextPointer start = thisRange._TextSegments[0].Start;
            ITextPointer end = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End;
 
            if (thisRange._IsTableCellRange)
            { 
                Invariant.Assert(thisRange._TextSegments[0].Start is TextPointer); 

                // Table range - normalization may lead to full range rebuild 
                TextRangeEditTables.IdentifyValidBoundaries(thisRange, out start, out end);

                //
 
                SelectPrivate(thisRange, start, end, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/false);
            } 
            else 
            {
                // Text range - normalization on both ends must be ensured 
                bool needNormalization = false;

                if ((object)start == (object)end)
                { 
                    if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection))
                    { 
                        // Empty range can be normalized in any direction, 
                        // so we use direction-neutral predicate
                        needNormalization = true; 
                    }
                }
                else if (start.CompareTo(end) == 0)
                { 
                    // The range which initially was not empty is not collapsed,
                    // so we need to re-create it to use one TextPointer instance 
                    // instead of two. 
                    // Note that gravity for the start pointer is Backward
                    // in this case - so that resulting caret will be normalized/ 
                    // oriented backward.
                    needNormalization = true;
                }
                else if ( 
                    !TextPointerBase.IsAtInsertionPosition(start, LogicalDirection.Forward) ||
                    !TextPointerBase.IsAtInsertionPosition(end, LogicalDirection.Backward)) 
                { 
                    // If for a non-empty range, start/end are not at insertion position,
                    // we need to normalize it. 
                    needNormalization = true;
                }

                if (needNormalization) 
                {
                    CreateNormalizedTextSegment(thisRange, start, end); 
                } 
            }
 
            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work.
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation;
        } 

        // Implementation body of range Select method 
        // When the range is being built for the very first time OR a table range is being rebuilt during normalization, 
        // we do not fire any range notifications.
        // NOTE: Because this method is called from NormalizeRange method we should totally avoid calling 
        // any ITextRange methods involving normalization (such as Start/End)
        private static void SelectPrivate(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition, bool markRangeChanged)
        {
            List textSegments; 
            bool isTableCellRange;
 
            Invariant.Assert(position1 != null, "null check: position1"); 
            Invariant.Assert(position2 != null, "null check: position2");
 
            if (position1 is TextPointer)
            {
                textSegments = TextRangeEditTables.BuildTableRange(
                    /*anchorPosition:*/(TextPointer)position1, 
                    /*movingPosition:*/(TextPointer)position2,
                    includeCellAtMovingPosition, 
                    out isTableCellRange); 
            }
            else 
            {
                // We have abstract TextContainer - never expect/build table range in this case
                Invariant.Assert(!thisRange._IsTableCellRange, "range is not expected to be in IsTableCellRange state - 1");
                textSegments = null; 
                isTableCellRange = false;
            } 
 
            if (textSegments != null)
            { 
                // Note also that table range is always in normalized condition - by construction
                //
                thisRange._TextSegments = textSegments;
                thisRange._IsTableCellRange = isTableCellRange; 
            }
            else 
            { 
                // Simple textsegment case
                ITextPointer newStart = position1; 
                ITextPointer newEnd = position2;

                // Swap pointers to order them properly.
                // We do not sewap them when they are equal, 
                // so that for empty range we are predictable about
                // collapsed position orientation - taken from the first parameter 
                // (position1) (as per CreateNormalizedTextSegment semantics). 
                if (position1.CompareTo(position2) > 0)
                { 
                    newStart = position2;
                    newEnd = position1;
                }
 
                // Create new segment. Note that we do this unconditionally,
                // not trying to bypass it if new positions look the same as currently set, 
                // because we need to ensure range normalization here. 
                TextRangeBase.CreateNormalizedTextSegment(thisRange, newStart, newEnd);
                Invariant.Assert(!thisRange._IsTableCellRange, "Expecting that the range is in text segment state now - must be set by CreateNOrmalizedTextSegment"); 

                // Before setting final range state we need to check if we are still in TextSegment condition
                if (position1 is TextPointer)
                { 
                    ITextPointer finalStart = thisRange._TextSegments[0].Start;
                    ITextPointer finalEnd = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 
                    if (finalStart.CompareTo(newStart) != 0 || finalEnd.CompareTo(newEnd) != 0) 
                    {
                        // This means that as a result of position normalization they have been moved 
                        // so we must check what is the range state now.
                        // NOTE: We are in TextSegment state now, so anchor/moving ordering is not important,
                        // so we use thisRange.Start/End (normalized) whose order may be different from
                        // position1/position2. 
                        // Note: we use includeCellAtMovingPosition=false here because the movingPosition is taken from a constructed table range, not from input
                        textSegments = TextRangeEditTables.BuildTableRange( 
                            /*anchorPosition:*/(TextPointer)finalStart, 
                            /*movingPosition:*/(TextPointer)finalEnd,
                            /*includeCellAtMovingPosition:*/false, 
                            out isTableCellRange);
                        if (textSegments != null)
                        {
                            thisRange._TextSegments = textSegments; 
                            thisRange._IsTableCellRange = isTableCellRange;
                        } 
                    } 
                }
            } 

            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work.
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation; 

            if (markRangeChanged) 
            { 
                //
                TextRangeBase.MarkRangeChanged(thisRange); 
            }
        }

        ///  
        /// Raises the Changed event for this range.
        /// 
        /// It must be called within a BeginChange/EndChange 
        /// block.
        ///  
        private static void MarkRangeChanged(ITextRange thisRange)
        {
            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "changeBlockLevel > 0 is expected");
            thisRange._IsChanged = true; 
        }
 
        #endregion Private Methods 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------- 
//
// File: TextRangeBase.cs
//
// Copyright (C) Microsoft Corporation.  All rights reserved. 
//
// Description: Provides an abstract level of TextRange implementation 
//      Implemented as a static class containing a set of methods 
//      implementing members of abstract ITextRange interface.
//      These members are supposed to be called from concrete 
//      classes TextRange and TextSelection - to ensure that the
//      both have the same base implementation.
//
//      TextSelection is allowed to add additional actions over 
//      base ones. TextRange must do pure call redirections,
//      otherwise TextSelection inheritance from TextRange 
//      will be broken. 
//
//      Only methods that require virtualization for TextSelection 
//      implementation go here. All other methods of ITextRange
//      are implemented directly in TextRange clas in appropriate
//      ITextRange.Member.
// 
//---------------------------------------------------------------------------
 
namespace System.Windows.Documents 
{
    using MS.Internal; 
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Globalization; 
    using System.Text;
    using System.Xml; 
    using System.IO; 
    using MS.Internal.Documents;
    using System.Windows.Controls; // TextBlock 

    /// 
    /// A class a portion of text content.
    /// Can be contigous or disjoint; supports rectangular table ranges. 
    /// Provides an API for text and table editing operations.
    ///  
    internal static class TextRangeBase 
    {
        //----------------------------------------------------- 
        //
        // ITextRange Methods
        //
        //----------------------------------------------------- 

        #region ITextRange Methods 
 
        //......................................................
        // 
        // Selection Building
        //
        //......................................................
 
        /// 
        ///  
        // 
        internal static bool Contains(ITextRange thisRange, ITextPointer textPointer)
        { 
            NormalizeRange(thisRange);

            if (textPointer == null)
            { 
                throw new ArgumentNullException("textPointer");
            } 
 
            if (textPointer.TextContainer != thisRange.Start.TextContainer)
            { 
                throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree), "textPointer");
            }

            // Correct position normalization on range boundary so that 
            // our test would not depend on what side of formatting tags
            // pointer is located. 
            if (textPointer.CompareTo(thisRange.Start) < 0) 
            {
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Forward); 
            }
            else if (textPointer.CompareTo(thisRange.End) > 0)
            {
                textPointer = textPointer.GetFormatNormalizedPosition(LogicalDirection.Backward); 
            }
 
            // Check if at least one segment contains this position. 
            for (int i = 0; i < thisRange._TextSegments.Count; i++)
            { 
                if (thisRange._TextSegments[i].Contains(textPointer))
                {
                    return true;
                } 
            }
 
            return false; 
        }
 
        /// 
        /// Base implementation of ITextRange.Select method.
        /// 
        ///  
        /// The range which is an object of this operation.
        ///  
        ///  
        /// One of two boundary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as an "anchor" position, so the selection will always include
        /// the cell at this position
        /// 
        ///  
        /// The other of two buondary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as a "moving" position, so the selection may not include the cell 
        /// at this position - when the cell has bigger index than the anchor
        /// one and when it is positioned at the very beginning of a cell, 
        /// (if any of these two conditions if false then the cell at this position is included).
        /// 
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2)
        { 
            Select(thisRange, position1, position2, /*includeCellAtMovingPosition:*/false);
        } 
 
        /// 
        /// Base implementation of ITextRange.Select method. 
        /// 
        /// 
        /// The range which is an object of this operation.
        ///  
        /// 
        /// One of two boundary positions for building selection. 
        /// In case of table cell-crossing selection it is considered 
        /// as an "anchor" position, so the selection will always include
        /// the cell at this position 
        /// 
        /// 
        /// The other of two buondary positions for building selection.
        /// In case of table cell-crossing selection it is considered 
        /// as a "moving" position, so the selection may not include the cell
        /// at this position - when the cell has bigger index than the anchor 
        /// one and when it is positioned at the very beginning of a cell, 
        /// and when includeCellAtMovingPosition==false (if any of these three
        /// conditions if false then the cell at this position is included). 
        /// 
        /// 
        /// True indicates that a cell at a movingPosition must be included
        /// into a selection even when it is at cell start. 
        /// False indicates that when a movingPosition is at cell start
        /// and the cell has bigger index than anchor cell, then selection 
        /// should not include it - it only indicates cell crossing. 
        /// When we build a table range from existing range's Start/End pair
        /// we must use false for this parameter - because the end position 
        /// of a table range is not included into it - by construction.
        /// When you use independent position - say, from hit-testing -
        /// then you typically use "true" for this parameter, unnless
        /// you intentially cross cell boundary - as for one cell celection. 
        /// 
        internal static void Select(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition) 
        { 
            if (thisRange._TextSegments == null)
            { 
                // This is initializing call from TextRange constructor.
                // No need in change notifications, no need in position verification.
                TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/false);
            } 
            else
            { 
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position1, "position1"); 
                ValidationHelper.VerifyPosition(thisRange.Start.TextContainer, position2, "position2");
 
                TextRangeBase.BeginChange(thisRange);
                try
                {
                    TextRangeBase.SelectPrivate(thisRange, position1, position2, includeCellAtMovingPosition, /*markRangeChanged*/true); 
                }
                finally 
                { 
                    TextRangeBase.EndChange(thisRange);
                } 
            }
        }

        ///  
        /// Selects a word containing this position
        ///  
        ///  
        /// 
        /// A TextPointer containing a word to select. 
        /// 
        internal static void SelectWord(ITextRange thisRange, ITextPointer position)
        {
            if (position == null) 
            {
                throw new ArgumentNullException("position"); 
            } 

            // Move position to character boundary (also respect atomics) 
            //

            ITextPointer normalizedPosition = position.CreatePointer();
            normalizedPosition.MoveToInsertionPosition(LogicalDirection.Backward); 

            TextSegment wordRange = TextPointerBase.GetWordRange(normalizedPosition); 
 
            TextRangeBase.Select(thisRange, wordRange.Start, wordRange.End);
        } 

        // Returns a word within which empty selection is located.
        // Returns TextSegment.Null if selection is not empty or
        // if the position is between or at word boundary. 
        internal static TextSegment GetAutoWord(ITextRange thisRange)
        { 
            TextSegment autoWordRange = TextSegment.Null; 

            if (thisRange.IsEmpty && // 
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Forward) && //
                !TextPointerBase.IsAtWordBoundary(thisRange.Start, LogicalDirection.Backward))
            {
                // 

                autoWordRange = TextPointerBase.GetWordRange(thisRange.Start); 
                string autoWord = TextRangeBase.GetTextInternal(autoWordRange.Start, autoWordRange.End).TrimEnd(' '); 

                string textFromWordStart = TextRangeBase.GetTextInternal(autoWordRange.Start, thisRange.Start); 

                if (textFromWordStart.Length >= autoWord.Length)
                {
                    // The caret is beyond the end of a word (in a whitespace area) 
                    autoWordRange = TextSegment.Null;
                } 
            } 

            return autoWordRange; 
        }

        /// 
        /// Selects a paragraph around the given position. 
        /// 
        ///  
        ///  
        /// A position identifying a paragraph to select.
        ///  
        internal static void SelectParagraph(ITextRange thisRange, ITextPointer position)
        {
            if (position == null)
            { 
                throw new ArgumentNullException("position");
            } 
 
            ITextPointer start;
            ITextPointer end; 
            FindParagraphOrListItemBoundaries(position, out start, out end);

            // Select the paragraph contents
            TextRangeBase.Select(thisRange, start, end); 
        }
 
        // Apply initial typing heuristics -- adjust range for typing 
        // when it spans one or more TableCells.
        // 
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method.
        internal static void ApplyInitialTypingHeuristics(ITextRange thisRange) 
        {
            // When table cells selected, clear the start cell and collapse selection into it 
            if (thisRange.IsTableCellRange) 
            {
                TableCell cell; 
                if (thisRange.Start is TextPointer &&
                    (cell = TextRangeEditTables.GetTableCellFromPosition((TextPointer)thisRange.Start)) != null)
                {
                    // Select the first cell content to make springload formatting happen below 
                    thisRange.Select(cell.ContentStart, cell.ContentEnd);
                } 
                else 
                {
                    thisRange.Select(thisRange.Start, thisRange.Start); 
                }
            }
        }
 
        // Apply typing heuristics
        //  - extend for overtype. 
        //  - prevent paragraph merges when only the leading edge of that 
        //    last paragraph is selected.
        // 
        // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are
        // called together, with a an extra step in between for TextSelection
        // overrides of the ApplyTypingHueristic method.
        internal static void ApplyFinalTypingHeuristics(ITextRange thisRange, bool overType) 
        {
            // Expand empty selection forward in overtype mode 
            if (overType && thisRange.IsEmpty && 
                !TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Forward))
            { 
                //


                ITextPointer nextPosition = thisRange.End.CreatePointer(); 
                nextPosition.MoveToNextInsertionPosition(LogicalDirection.Forward);
                if (!TextRangeEditTables.IsTableStructureCrossed(thisRange.Start, nextPosition)) 
                { 
                    TextRange range = new TextRange(thisRange.Start, nextPosition);
                    Invariant.Assert(!range.IsTableCellRange); 

                    range.Text = String.Empty;
                }
            } 

            // If the range is non-empty, and its end just passes a paragraph break, 
            // pull the end back to stop a paragraph merge on the next keystroke. 
            if (!thisRange.IsEmpty &&
                (TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Backward) || 
                 TextPointerBase.IsAfterLastParagraph(thisRange.End)))
            {
                ITextPointer newEnd = thisRange.End.GetNextInsertionPosition(LogicalDirection.Backward);
                thisRange.Select(thisRange.Start, newEnd); 
            }
        } 
 
        /// 
        ///  
        /// 
        internal static void ApplyTypingHeuristics(ITextRange thisRange, bool overType)
        {
            BeginChange(thisRange); 
            try
            { 
                ApplyInitialTypingHeuristics(thisRange); 
                ApplyFinalTypingHeuristics(thisRange, overType);
            } 
            finally
            {
                EndChange(thisRange);
            } 
        }
 
        internal static void FindParagraphOrListItemBoundaries(ITextPointer position, out ITextPointer start, out ITextPointer end) 
        {
            // Identify a maximum portion of text around navigator 
            // which may be wrapped by Paragraph
            start = position.CreatePointer();
            end = position.CreatePointer();
            SkipParagraphContent(start, LogicalDirection.Backward); 
            SkipParagraphContent(end, LogicalDirection.Forward);
        } 
 
        // Moves the navigator in the given direction over all characters,
        // embedded objects and formatting tags. 
        //

        private static void SkipParagraphContent(ITextPointer navigator, LogicalDirection direction)
        { 
            TextPointerContext nextContext = navigator.GetPointerContext(direction);
 
            while (true) 
            {
                if (nextContext == TextPointerContext.None // 
                    || //
                    // Entering non-inline content
                    (nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Forward || //
                    nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Backward) && // 
                    !typeof(Inline).IsAssignableFrom(navigator.GetElementType(direction)) //
                    || 
                    // Exiting non-inline content 
                    (nextContext == TextPointerContext.ElementEnd && direction == LogicalDirection.Forward || //
                    nextContext == TextPointerContext.ElementStart && direction == LogicalDirection.Backward) && // 
                    !typeof(Inline).IsAssignableFrom(navigator.ParentType))
                {
                    // End of paragraph content reached. Stop here.
                    break; 
                }
 
                //Need to bail out if MoveToNextContentPosition fails 
                if (!navigator.MoveToNextContextPosition(direction))
                { 
                    break;
                }
                nextContext = navigator.GetPointerContext(direction);
            } 
        }
 
        // Calculates a value of a given property on this range 
        internal static object GetPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty)
        { 
            if (TextSchema.IsCharacterProperty(formattingProperty))
            {
                return GetCharacterPropertyValue(thisRange, formattingProperty);
            } 
            else
            { 
                Invariant.Assert(TextSchema.IsParagraphProperty(formattingProperty), "The property is expected to be one of either character or paragraph formatting one"); 
                return GetParagraphPropertyValue(thisRange, formattingProperty);
            } 
        }

        // Calculates character formatting property
        private static object GetCharacterPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty) 
        {
            // 
 
            object startValue = GetCharacterValueFromPosition(thisRange.Start, formattingProperty);
 
            // Need to run over all text runs to check that the value is the same for all of them.
            // We'll stop on the first different value if any; and return MixedValue.Instance
            for (int i = 0; i < thisRange._TextSegments.Count; i++)
            { 
                TextSegment textSegment = thisRange._TextSegments[i];
 
                ITextPointer position = textSegment.Start.CreatePointer(); 
                bool moved = true;
                while (moved && position.CompareTo(textSegment.End) < 0) 
                {
                    // Check whether the value in this text run is the same as at the beginning
                    object value = GetCharacterValueFromPosition(position, formattingProperty);
                    if (!TextSchema.ValuesAreEqual(value, startValue)) 
                    {
                        return DependencyProperty.UnsetValue; // 
                    } 

                    // Skip text run 
                    if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                    {
                        moved = position.MoveToNextContextPosition(LogicalDirection.Forward);
                    } 

                    // try to skip formatting tags 
                    moved = position.MoveToInsertionPosition(LogicalDirection.Forward); 

                    if (!moved) 
                    {
                        // Go to the next run, of there was no formatting boundary
                        moved = position.MoveToNextInsertionPosition(LogicalDirection.Forward);
                    } 
                }
            } 
 
            return startValue;
        } 

        // Gets a non-inherited property from a given position
        private static object GetCharacterValueFromPosition(ITextPointer pointer, DependencyProperty formattingProperty)
        { 
            object value = null;
 
            if (formattingProperty != Inline.TextDecorationsProperty) 
            {
                value = pointer.GetValue(formattingProperty); 
            }
            else
            {
                if (pointer is TextPointer) // Implement only for concrete TextCotainer returning null otherwise - for optimization 
                {
                    DependencyObject element = ((TextPointer)pointer).Parent as TextElement; 
                    while (value == null && (element is Inline || element is Paragraph || element is TextBlock)) 
                    {
                        value = element.GetValue(formattingProperty); 

                        element = element is TextElement ? ((TextElement)element).Parent : null;
                    }
                } 
            }
 
            return value; 
        }
 
        // Calculates paragraph formatting property
        // Returns DependencyProperty.UnsetValue if different areas of the range have different value for this property.
        private static object GetParagraphPropertyValue(ITextRange thisRange, DependencyProperty formattingProperty)
        { 
            object startValue = null;
 
            // Need to run over all text runs to check that the value is the same for all of them. 
            // We'll stop on the first different value if any; and return MixedValue.Instance
            for (int i = 0; i < thisRange._TextSegments.Count; i++) 
            {
                TextSegment textSegment = thisRange._TextSegments[i];

                ITextPointer position = textSegment.Start.CreatePointer(); 

                // Find position scoped by paragraph - in backward direction - to get start value 
                while (!typeof(Paragraph).IsAssignableFrom(position.ParentType) && 
                    position.MoveToNextContextPosition(LogicalDirection.Backward)) ;
 
                // Traverse the segment to find all other paragraph positions
                bool moved = true;
                while (moved && position.CompareTo(textSegment.End) <= 0)
                { 
                    if (typeof(Paragraph).IsAssignableFrom(position.ParentType))
                    { 
                        object value = position.GetValue(formattingProperty); 
                        if (startValue == null)
                        { 
                            startValue = value;
                        }

                        if (!TextSchema.ValuesAreEqual(value, startValue)) 
                        {
                            return DependencyProperty.UnsetValue; 
                        } 

                        position.MoveToElementEdge(ElementEdge.AfterEnd); 
                    }
                    moved = position.MoveToNextContextPosition(LogicalDirection.Forward);
                }
            } 

            // Most properties does not allow null as a value, 
            // so if we still have null try to get a value from range start position. 
            // For some properties (like TextDecorations) it still may remain null.
            if (startValue == null) 
            {
                startValue = thisRange.Start.GetValue(formattingProperty);
            }
 
            return startValue;
        } 
 
        // Returns true if this range start and end pointers cross a paragraph boundary, false otherwise.
        internal static bool IsParagraphBoundaryCrossed(ITextRange thisRange) 
        {
            ITextPointer startNavigator = thisRange.Start.CreatePointer();
            ITextPointer endNavigator = thisRange.End.CreatePointer();
 
            if (TextPointerBase.IsAfterLastParagraph(endNavigator))
            { 
                endNavigator.MoveToInsertionPosition(LogicalDirection.Backward); 
            }
 
            // Walk upto the closest block ancestor
            while (typeof(Inline).IsAssignableFrom(startNavigator.ParentType))
            {
                startNavigator.MoveToElementEdge(ElementEdge.AfterEnd); 
            }
            while (typeof(Inline).IsAssignableFrom(endNavigator.ParentType)) 
            { 
                endNavigator.MoveToElementEdge(ElementEdge.AfterEnd);
            } 

            // start and end are within the scope of the same paragraph?
            return !startNavigator.HasEqualScope(endNavigator);
        } 

        //......................................................... 
        // 
        //  Change Notifications
        // 
        //.........................................................

        /// 
        ///  
        /// 
        internal static void BeginChange(ITextRange thisRange) 
        { 
            BeginChangeWorker(thisRange, String.Empty);
        } 

        /// 
        /// 
        ///  
        internal static void BeginChangeNoUndo(ITextRange thisRange)
        { 
            BeginChangeWorker(thisRange, null); 
        }
 
        /// 
        /// 
        /// 
        internal static void EndChange(ITextRange thisRange) 
        {
            EndChange(thisRange, false /* disableScroll */, false /* skipEvents */ ); 
        } 

        ///  
        /// 
        /// 
        internal static void EndChange(ITextRange thisRange, bool disableScroll, bool skipEvents)
        { 
            ChangeBlockUndoRecord changeBlockUndoRecord;
            bool isChanged; 
            ITextContainer textContainer; 

            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "Unmatched EndChange call!"); 

            textContainer = thisRange.Start.TextContainer;

            try 
            {
                // 
                // Complete the content changed block. 
                //
                try 
                {
                    // Raise first public event -- TextContainer.EndChange.
                    textContainer.EndChange(skipEvents);
                } 
                finally
                { 
                    // Always drop the ChangeBlockLevel, no matter what happens. 
                    // This ensures that we won't ignore future events if the
                    // application recovers from an exception. 
                    thisRange._ChangeBlockLevel--;

                    // Clear out thisRange.IsChanged now so that it isn't
                    // left dangling if TextContainer.EndChange throws 
                    // an exception.
                    isChanged = thisRange._IsChanged; 
                    if (thisRange._ChangeBlockLevel == 0) 
                    {
                        thisRange._IsChanged = false; 
                    }
                }

                // 
                // Complete the range repositioned block.
                // 
                if (thisRange._ChangeBlockLevel == 0 && isChanged) 
                {
                    // Raise the second public event -- TextRange.Changed. 
                    thisRange.NotifyChanged(disableScroll, skipEvents);
                }
            }
            finally 
            {
                // Make sure we close the undo record no matter what happened. 
                changeBlockUndoRecord = (ChangeBlockUndoRecord)thisRange._ChangeBlockUndoRecord; 
                if (changeBlockUndoRecord != null && thisRange._ChangeBlockLevel == 0)
                { 
                    try
                    {
                        changeBlockUndoRecord.OnEndChange();
                    } 
                    finally
                    { 
                        thisRange._ChangeBlockUndoRecord = null; 
                    }
                } 
            }
        }

        internal static void NotifyChanged(ITextRange thisRange, bool disableScroll) 
        {
            thisRange.FireChanged(); 
        } 

        #endregion ITextRange Methods 

        // ....................................................................
        //
        // Static Helpers for dealing with content without range instantiation 
        //
        // .................................................................... 
 
        #region TextRange Helpers
 
        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition)
        { 
            Char[] charArray = null; // used for extracting text runs
 
            return GetTextInternal(startPosition, endPosition, ref charArray); 
        }
 
        // Returns the text covered by two TextPositions as a string.
        // Includes rules for translating paragraph breaks and embedded objects.
        //
        // Use this overload when looping over large quantities of text to avoid 
        // re-allocating a temporary buffer.
        internal static string GetTextInternal(ITextPointer startPosition, ITextPointer endPosition, ref Char[] charArray) 
        { 
            // Buffer for building a resulting plain text
            StringBuilder textBuffer = new StringBuilder(); 

            // Stack of List context - needed for efficient bullet generation
            Stack listItemCounter = null;
 
            ITextPointer navigator = startPosition.CreatePointer();
 
            Invariant.Assert(startPosition.CompareTo(endPosition) <= 0, "expecting: startPosition <= endPosition"); 

            while (navigator.CompareTo(endPosition) < 0) 
            {
                Type elementType;

                TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward); 
                switch (symbolType)
                { 
                    case TextPointerContext.Text: 
                        PlainConvertTextRun(textBuffer, navigator, endPosition, ref charArray);
                        break; 
                    case TextPointerContext.ElementEnd:
                        elementType = navigator.ParentType;

                        if (typeof(Paragraph).IsAssignableFrom(elementType) || 
                            typeof(BlockUIContainer).IsAssignableFrom(elementType))
                        { 
                            PlainConvertParagraphEnd(textBuffer, navigator); 
                        }
                        else if (typeof(LineBreak).IsAssignableFrom(elementType)) 
                        {
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                            textBuffer.Append(Environment.NewLine);
                        } 
                        else if (typeof(List).IsAssignableFrom(elementType))
                        { 
                            PlainConvertListEnd(navigator, ref listItemCounter); 
                        }
                        else 
                        {
                            // All other closing tags - just skip them
                            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        } 
                        break;
                    case TextPointerContext.EmbeddedElement : 
                        textBuffer.Append('\u0020'); // Substitute SPACE for embedded objects. 
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break; 
                    case TextPointerContext.ElementStart :
                        elementType = navigator.GetElementType(LogicalDirection.Forward);
                        if (typeof(AnchoredBlock).IsAssignableFrom(elementType))
                        { 
                            // Floaters and figures must start from a new line
                            textBuffer.Append(Environment.NewLine); 
                        } 
                        else if (typeof(List).IsAssignableFrom(elementType) && navigator is TextPointer)
                        { 
                            // New list level opens
                            PlainConvertListStart(navigator, ref listItemCounter);
                        }
                        else if (typeof(ListItem).IsAssignableFrom(elementType)) 
                        {
                            // List items must be preceeded by a list marker 
                            PlainConvertListItemStart(textBuffer, navigator, ref listItemCounter); 
                        }
                        else 
                        {
                            PlainConvertAccessKey(textBuffer, navigator);
                        }
                        navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
                        break;
 
                    default: 
                        Invariant.Assert(false, "Unexpected vlue for TextPointerContext");
                        break; 
                }
            }

            return textBuffer.ToString(); 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing Text runs 
        private static void PlainConvertTextRun(StringBuilder textBuffer, ITextPointer navigator, ITextPointer endPosition, ref Char[] charArray)
        { 
            // Copy this text run into destination
            int runLength = navigator.GetTextRunLength(LogicalDirection.Forward);
            charArray = EnsureCharArraySize(charArray, runLength);
            runLength = TextPointerBase.GetTextWithLimit(navigator, LogicalDirection.Forward, charArray, 0, runLength, endPosition); 
            textBuffer.Append(charArray, 0, runLength);
            navigator.MoveToNextContextPosition(LogicalDirection.Forward); 
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Paragraph elements. 
        // Outputs \n - for regular paragraphs and TableRow ends or \t for TableCell ends.
        private static void PlainConvertParagraphEnd(StringBuilder textBuffer, ITextPointer navigator)
        {
            // Check for a special case for a single paragraph within a TableCell 
            // which must be serialized as "\t" character.
            navigator.MoveToElementEdge(ElementEdge.BeforeStart); 
            bool theParagraphIsTheFirstInCollection = navigator.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart; 
            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
            navigator.MoveToElementEdge(ElementEdge.AfterEnd); 
            TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward);

            if (theParagraphIsTheFirstInCollection && symbolType == TextPointerContext.ElementEnd &&
                typeof(TableCell).IsAssignableFrom(navigator.ParentType)) 
            {
                // This is an end of a table cell 
 
                navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                symbolType = navigator.GetPointerContext(LogicalDirection.Forward); 
                if (symbolType == TextPointerContext.ElementStart)
                {
                    // Next table cell starts after this one. Use '\t' as a cell separator
                    textBuffer.Append('\t'); 
                }
                else 
                { 
                    // This was the last cell in a row. Use '\r\n' as a line separator
                    textBuffer.Append(Environment.NewLine); 
                }
            }
            else
            { 
                // Ordinary paragraph end
                textBuffer.Append(Environment.NewLine); 
            } 
        }
 
        // Part of plain text converter: called from GetTextInternal when processing ElementStart for List elements.
        // Initializes a stack of list item counters and pushes a new zero for the opened list level.
        private static void PlainConvertListStart(ITextPointer navigator, ref Stack listItemCounter)
        { 
            List list = (List)navigator.GetAdjacentElement(LogicalDirection.Forward);
 
            // Initialize list context 
            if (listItemCounter == null)
            { 
                listItemCounter = new Stack(1);
            }
            listItemCounter.Push(0);
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementEnd for Listelements 
        // Pops a current value from a stack of list item indices. 
        private static void PlainConvertListEnd(ITextPointer navigator, ref Stack listItemCounter)
        { 
            // Note that we do not expect List tag balansing:
            // We can get more List closing tags than we had opening ones -
            // it happens when range starts in the middle of a list.
            if (listItemCounter != null && listItemCounter.Count > 0) 
            {
                listItemCounter.Pop(); 
            } 
            navigator.MoveToNextContextPosition(LogicalDirection.Forward);
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for ListItem elements
        // Uses s stack of list items indices and updates it for following list items.
        private static void PlainConvertListItemStart(StringBuilder textBuffer, ITextPointer navigator, ref Stack listItemCounter) 
        {
            if (navigator is TextPointer) // can do somethinng useful only in concrete TextContainer - not in an abstract one 
            { 
                List list = (List)((TextPointer)navigator).Parent;
                ListItem listItem = (ListItem)navigator.GetAdjacentElement(LogicalDirection.Forward); 

                // Initialize list context
                if (listItemCounter == null)
                { 
                    listItemCounter = new Stack(1);
                } 
                if (listItemCounter.Count == 0) 
                {
                    // List is taken from its middle position. Need to identify starting item number 
                    listItemCounter.Push(((IList)listItem.SiblingListItems).IndexOf(listItem));
                }

                // Get list item number 
                Invariant.Assert(listItemCounter.Count > 0, "expectinng listItemCounter.Count > 0");
                int listItemIndex = listItemCounter.Pop(); 
                int indexBase = list != null ? list.StartIndex : 0; 
                TextMarkerStyle markerStyle = list != null ? list.MarkerStyle : TextMarkerStyle.Disc;
 
                WriteListMarker(textBuffer, markerStyle, listItemIndex + indexBase);

                // Advance
                listItemIndex++; 
                listItemCounter.Push(listItemIndex);
            } 
        } 

        // Part of plain text converter: called from GetTextInternal when processing ElementStart for AccessKey elements 
        // Uses s stack of list items indices and updates it for following list items.
        private static void PlainConvertAccessKey(StringBuilder textBuffer, ITextPointer navigator)
        {
            // Creating an "_" prefix for AccessKey character (represented as a Run with special serialization attribution) 
            object element = navigator.GetAdjacentElement(LogicalDirection.Forward);
            if (AccessText.HasCustomSerialization(element)) 
            { 
                textBuffer.Append(AccessText.AccessKeyMarker);
            } 
        }

        // Helper for GetTextInternal, manages a char buffer.
        // NOTE: Does not preserve the content of a buffer 
        private static Char[] EnsureCharArraySize(Char[] charArray, int textLength)
        { 
            if (charArray == null) 
            {
                charArray = new char[textLength + 10]; 
            }
            else if (charArray.Length < textLength)
            {
                int newLength = charArray.Length * 2; 
                if (newLength < textLength)
                { 
                    newLength = textLength + 10; 
                }
 
                charArray = new Char[newLength];
            }
            return charArray;
        } 

        // Writes a text representation of a list marker 
        private static void WriteListMarker(StringBuilder textBuffer, TextMarkerStyle listMarkerStyle, int listItemNumber) 
        {
            string markerText = null; 
            Char[] charArray = null;

            switch (listMarkerStyle)
            { 
                case TextMarkerStyle.None :
                    markerText = ""; 
                    break; 
                case TextMarkerStyle.Disc :
                    markerText = "\x2022"; // Bullet // not a "\x9f"; 
                    break;
                case TextMarkerStyle.Circle :
                    markerText = "\x25CB"; // White Circle // not a "\xa1";
                    break; 
                case TextMarkerStyle.Square :
                    markerText = "\x25A1"; // White Box // not a "\x71"; 
                    break; 
                case TextMarkerStyle.Box :
                    markerText = "\x25A0"; // Black Box // not a "\xa7"; 
                    break;

                case TextMarkerStyle.Decimal:
                    charArray = ConvertNumberToString(listItemNumber, false, DecimalNumerics); 
                    break;
 
                case TextMarkerStyle.LowerLatin: 
                    charArray = ConvertNumberToString(listItemNumber, true, LowerLatinNumerics);
                    break; 

                case TextMarkerStyle.UpperLatin:
                    charArray = ConvertNumberToString(listItemNumber, true, UpperLatinNumerics);
                    break; 

                case TextMarkerStyle.LowerRoman: 
                    markerText = ConvertNumberToRomanString(listItemNumber, false); 
                    break;
 
                case TextMarkerStyle.UpperRoman:
                    markerText = ConvertNumberToRomanString(listItemNumber, true);
                    break;
            } 

            if (markerText != null) 
            { 
                textBuffer.Append(markerText);
            } 
            else if (charArray != null)
            {
                textBuffer.Append(charArray, 0, charArray.Length);
            } 
            textBuffer.Append('\t');
        } 
 
        private const char NumberSuffix = '.';
 
        private const string DecimalNumerics = "0123456789";
        private const string LowerLatinNumerics = "abcdefghijklmnopqrstuvwxyz";
        private const string UpperLatinNumerics = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
        private static string[][] RomanNumerics = new string[][]
        { 
            new string[] { "m??", "cdm", "xlc", "ivx" }, 
            new string[] { "M??", "CDM", "XLC", "IVX" }
        }; 

        /// 
        /// Convert a number to string, consisting of digits followed by the NumberSuffix character.
        ///  
        /// Number to convert.
        /// True if there is no zero digit (e.g., alpha numbering). 
        /// Set of digits (e.g., 0-9 or a-z). 
        /// Returns the number string as an array of characters.
        private static char[] ConvertNumberToString(int number, bool oneBased, string numericSymbols) 
        {
            //  Whether zero-based or one-based numbering is used affects how we
            //  count and how we determine the maximum number of values for a
            //  given number of digits. 
            //
            //  The following table illustrates how counting differs. In both 
            //  cases we're using base-2 numbering (i.e., two distinct digits), 
            //  but with 1-based counting each of those two digits can be a
            //  significant leading digit. 
            //
            //            0-based     1-based
            //    ----------------------------
            //      0           0          -- 
            //      1           1           a
            //      2          10           b 
            //      3          11          aa 
            //      4         100          ab
            //      5         101          ba 
            //      6         110          bb
            //      7         111         aaa
            //      8        1000         aab
            //      9        1001         aba 
            //     10        1010         abb
            //     11        1011         baa 
            //     12        1100         bab 
            //     13        1101         bba
            //     14        1110         bbb 
            //     15        1111        aaaa
            //     16       10000        aaab
            //
            //  For zero-based counting, adding a leading zero does not change 
            //  the value of a number. Thus, the set of all N-digit numbers is
            //  a proper subset of the set of (N+1)-digit numbers. Thus the set 
            //  of values that can be represented by N *or fewer* digits is the 
            //  same as the number of combinations of exactly N digits, i.e.,
            // 
            //      b ^ N
            //
            //  where b is the base of the numbering system.
            // 
            //  For one-based counting, there is no zero digit. Thus, the set
            //  of N-digit numbers and the set of (N+1)-digit numbers are 
            //  disjoint sets. Thus, while the number of combinations of 
            //  *exactly* N digits is still b ^ N, the maximum value that
            //  can be represented by N *or fewer* digits is: 
            //
            //  Max(N)
            //      where N = 1   :   b
            //      where N > 1   :   (b ^ N) + Max(N - 1) 
            //
            if (oneBased) 
            { 
                // Subtract 1 from 1-based numbers so we can use zero-based
                // indexing. The formula for Max(N) given above should now be 
                // thought of as a limit rather than a maximum.
                --number;
            }
 
            Invariant.Assert(number >= 0, "expecting: number >= 0");
 
            char[] result; 

            int b = numericSymbols.Length; 
            if (number < b)
            {
                // Optimize common case of single-digit numbers.
                result = new char[2]; // digit + suffix 
                result[0] = numericSymbols[number];
                result[1] = NumberSuffix; 
            } 
            else
            { 
                // Disjoint is 1 if and only if the set of numbers with N
                // digits and the set of numbers with (N+1) digits are
                // disjoint (see comment above). Otherwise it is zero.
                int disjoint = oneBased ? 1 : 0; 

                // Count digits. 
                int digits = 1; 
                for (int limit = b, pow = b; number >= limit; ++digits)
                { 
                    pow *= b;
                    limit = pow + (limit * disjoint);
                }
 
                // Build string in reverse order starting with suffix.
                result = new char[digits + 1]; // digits + suffix 
                result[digits] = NumberSuffix; 
                for (int i = digits - 1; i >= 0; --i)
                { 
                    result[i] = numericSymbols[number % b];
                    number = (number / b) - disjoint;
                }
            } 

            return result; 
        } 

        ///  
        /// Convert 1-based number to a Roman numeric string
        /// followed by NumberSuffix character.
        /// 
        ///  
        /// Roman number is 1-based. The Roman numeric string is a series of symbols. Following
        /// is the list of symbols and its value. 
        /// 
        ///     Symbol      Value
        ///         I           1 
        ///         V           5
        ///         X          10
        ///         L          50
        ///         C         100 
        ///         D         500
        ///         M        1000 
        /// 
        /// The rule of Roman number prohibits the use of more than 3 consecutive identical symbol
        /// but using subtraction of symbol standing for multiples of 10, so the value 4 is written 
        /// as IV (5-1) rather than IIII.
        ///
        /// Due to the writing rule and the fact that the symbol represents not the numeral digit
        /// but the value of the number. Roman number system cannot represent value larger than 3999. 
        ///
        /// See, http://www.ccsn.nevada.edu/math/ancient_systems.htm 
        /// 
        /// However, there exists a more relaxing use of Roman numbers to represent values 4000 and
        /// 4999 by using 4 consecutive M. The value 4999 is than written as 'MMMMCMXCIX'. Such use 
        /// however is not widely accepted.
        ///
        /// See, http://www.guernsey.net/~sgibbs/roman.html
        /// 
        /// For values larger than 3999, an overscore is used on the symbol to indicate 1000 multiplication.
        ///                                    ___ 
        /// So, value 7000 would be written as VII. This writing rule has a fair amount of disagreement 
        /// since it is widely understood that it is not invented by the Romans and they rarely had a
        /// need for large numbers during their time. Furthermore, accepting this writing rule just 
        /// for the sake of being able to write larger number would create a new limitation of the values
        /// greater than 3,999,999. Unicode 4.0 does not encode these overscore symbols.
        ///
        /// See, http://www.gwydir.demon.co.uk/jo/roman/number.htm 
        ///      http://www.novaroma.org/via_romana/numbers.html
        /// 
        /// Implementation-wise, IE adopts a general limitation of 3999 and simply convert the value 
        /// into a regular numeric form.
        /// 
        /// We'll follow the mainstream and adopt the 3999 limit. The fallback would also do would IE does.
        ///
        /// 
        private static string ConvertNumberToRomanString( 
            int number,
            bool uppercase 
            ) 
        {
            if (number > 3999) 
            {
                // Roman numeric string not supported
                return number.ToString(CultureInfo.InvariantCulture);
            } 

            StringBuilder builder = new StringBuilder(); 
 
            AddRomanNumeric(builder, number / 1000, RomanNumerics[uppercase ? 1 : 0][0]);
            number %= 1000; 
            AddRomanNumeric(builder, number / 100, RomanNumerics[uppercase ? 1 : 0][1]);
            number %= 100;
            AddRomanNumeric(builder, number / 10, RomanNumerics[uppercase ? 1 : 0][2]);
            number %= 10; 
            AddRomanNumeric(builder, number, RomanNumerics[uppercase ? 1 : 0][3]);
 
            builder.Append(NumberSuffix); 

            return builder.ToString(); 
        }


        ///  
        /// Convert number 0 - 9 into Roman numeric
        ///  
        /// string builder 
        /// number to convert
        /// Roman numeric char for one five and ten 
        private static void AddRomanNumeric(
            StringBuilder builder,
            int number,
            string oneFiveTen 
            )
        { 
            Invariant.Assert(number >= 0 && number <= 9, "expecting: number >= 0 && number <= 9"); 

            if (number >= 1 && number <= 9) 
            {
                if (number == 4 || number == 9)
                    builder.Append(oneFiveTen[0]);
 
                if (number == 9)
                { 
                    builder.Append(oneFiveTen[2]); 
                }
                else 
                {
                    if (number >= 4)
                        builder.Append(oneFiveTen[1]);
 
                    for (int i = number % 5; i > 0 && i < 4; i--)
                        builder.Append(oneFiveTen[0]); 
                } 
            }
        } 

        #endregion TextRange Helpers

        //------------------------------------------------------ 
        //
        // ITextRange Properties 
        // 
        //-----------------------------------------------------
 
        #region ITextRange Properties

        //......................................................
        // 
        //  Boundary Positions
        // 
        //...................................................... 

        internal static ITextPointer GetStart(ITextRange thisRange) 
        {
            NormalizeRange(thisRange);

            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for Start position"); 
            return thisRange._TextSegments[0].Start;
        } 
 
        internal static ITextPointer GetEnd(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);

            Invariant.Assert(thisRange._TextSegments != null && thisRange._TextSegments.Count > 0, "expecting nonempty _TextSegments array for End position");
            return thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 
        }
 
        internal static bool GetIsEmpty(ITextRange thisRange) 
        {
            NormalizeRange(thisRange); 

            // We assume that if a range is empty then it uses the same instance
            // of TextPointer for both Start and End positions.
            Invariant.Assert( 
                (thisRange._TextSegments.Count == 1 &&
                (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End) 
                == 
                (thisRange.Start.CompareTo(thisRange.End) == 0),
                "Range emptiness assumes using one instance of TextPointer for both start and end"); 

            return (thisRange._TextSegments.Count == 1 &&
                 (object)thisRange._TextSegments[0].Start == (object)thisRange._TextSegments[0].End);
        } 

        internal static List GetTextSegments(ITextRange thisRange) 
        { 
            // NOTE: We cannot normalize thisRange because it will rebuild a collection
            // of textSegments and will cause range move notification, leading to stack overflow. 
            //


            return thisRange._TextSegments; 
        }
 
        //...................................................... 
        //
        //  Content - rich and plain 
        //
        //......................................................

        // Implementation of a getter for a ITextRange.Text property 
        internal static string GetText(ITextRange thisRange)
        { 
            NormalizeRange(thisRange); 

            if (!thisRange.IsTableCellRange) 
            {
                //

 
                // Extend the range from its start position to include initial list marker (if any).
                // We do not do this auto-extension inside GetTextInternal to avoid undesirable 
                // "bulleting" effects on random plain text (say, in Run.TextProperty serialization). 
                // THis is TextRange.get_Text-specific feature.
                ITextPointer start = thisRange.Start; 
                while (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
                    !typeof(AnchoredBlock).IsAssignableFrom(start.ParentType))
                {
                    // Any start tag is harmless except for AnchoredBlocks that would produce extra NewLines. So don't cross them. 
                    start = start.GetNextContextPosition(LogicalDirection.Backward);
                } 
 
                return TextRangeBase.GetTextInternal(start, thisRange.End);
            } 
            else
            {
                string text;
 
                //
                text = String.Empty; 
                for (int i = 0; i < thisRange._TextSegments.Count; i++) 
                {
                    TextSegment textSegment; 

                    textSegment = thisRange._TextSegments[i];

                    text += TextRangeBase.GetTextInternal(textSegment.Start, textSegment.End); 
                    //
                } 
 
                return text;
            } 
        }

        // Implementation of a setter fot ITextRange.Text property
        internal static void SetText(ITextRange thisRange, string textData) 
        {
            NormalizeRange(thisRange); 
 
            if (textData == null)
            { 
                throw new ArgumentNullException("textData");
            }

            ITextPointer explicitInsertPosition = null; 

            TextRangeBase.BeginChange(thisRange); 
            try 
            {
                // Delete content covered by this range 
                if (!thisRange.IsEmpty)
                {
                    if (thisRange.Start is TextPointer &&
                        ((TextPointer)thisRange.Start).Parent == ((TextPointer)thisRange.End).Parent && 
                        ((TextPointer)thisRange.Start).Parent is Run &&
                        textData.Length > 0) 
                    { 
                        // When textrange start/end are parented by the same Run, we can optimize
                        // and delete content without any checks. 
                        //
                        // Note that NOT doing so has a serious side effect in this case.
                        // Low-level code in TextRangeEdit does not preserve an empty run
                        // with no formatting properties after deletion. 
                        // We dont want to loose the empty Run,
                        // when we are just about to set the range text to non-empty string. 
                        // Otherwise, newly inserted text might have undesirable formatting properties 
                        // applied due to an insertion position within an adjacent Run.
 
                        if (thisRange.Start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text &&
                            thisRange.End.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
                        {
                            // If we're deleting with surrounding text, make sure we insert later between the surrounding text. 
                            // Because we will invalidate layout with the delete, it's possible that thisRange.Start
                            // will normalize itself to a different character offset on the next reference. 
                            // This is because -- unfortunately -- when layout is valid we use ITextView.IsAtCaretUnitBoundary 
                            // to normalize unicode offsets, but when layout is dirty we use a different code path
                            // that ignores the current font and simply checks Unicode values for surrogates and 
                            // combining marks.  See bug 1683515 for an example.
                            explicitInsertPosition = thisRange.Start;
                        }
 
                        TextContainer textContainer = ((TextPointer)thisRange.Start).TextContainer;
                        textContainer.DeleteContentInternal((TextPointer)thisRange.Start, (TextPointer)thisRange.End); 
                    } 
                    else
                    { 
                        thisRange.Start.DeleteContentToPosition(thisRange.End);
                    }

                    if (thisRange.Start is TextPointer) 
                    {
                        TextRangeEdit.MergeFlowDirection((TextPointer)thisRange.Start); 
                    } 

                    thisRange.Select(thisRange.Start, thisRange.Start); 
                }

                // Insert text at end position
                // Note that the non-emptiness check below is not an optimization: 
                // In case of empty text the code block in it would change an empty range
                // orientation, which is undesirable side effect. 
                // Also if the inserted text is empty we need to avoid ensuring insertion position, 
                // which can create paragraphs etc.
                if (textData.Length > 0) 
                {
                    ITextPointer insertPosition = (explicitInsertPosition == null) ? thisRange.Start : explicitInsertPosition;

                    // Ensure last paragraph existence and prepare ends for the new selection 
                    bool pastedFragmentEndsWithNewLine = textData.EndsWith("\n", StringComparison.Ordinal);
 
                    // We are going to insert paragraph implicitly when the block content becomes totally empty. 
                    // Store the fact that implicit paragraph was inserted to exclude ane extra paragraph break
                    // from the end of pasted fragment 
                    bool implicitParagraphInserted = insertPosition is TextPointer &&
                        TextSchema.IsValidChild(/*position*/insertPosition, /*childType*/typeof(Block)) &&
                        (insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None ||
                        insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) && 
                        (insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None ||
                        insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd); 
 
                    // Make sure that the range is positioned at insertion position
                    if (insertPosition is TextPointer && explicitInsertPosition == null) 
                    {
                        TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition((TextPointer)insertPosition);
                        thisRange.Select(insertionPosition, insertionPosition);
                        insertPosition = thisRange.Start; 
                    }
                    Invariant.Assert(TextSchema.IsInTextContent(insertPosition), "range.Start is expected to be in text content"); 
 
                    ITextPointer newStart = insertPosition.GetFrozenPointer(LogicalDirection.Backward);
                    ITextPointer newEnd = insertPosition.CreatePointer(LogicalDirection.Forward); 

                    if ((newStart is TextPointer) && ((TextPointer)newStart).Paragraph != null)
                    {
                        // Rich text - '\n' must be replaced by Paragraphs 
                        TextPointer insertionPosition = (TextPointer)newStart.CreatePointer(LogicalDirection.Forward);
                        string[] textParagraphs = textData.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None); 
 
                        int length = textParagraphs.Length;
                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) 
                        {
                            length--;
                        }
 
                        for (int i = 0; i < length; i++)
                        { 
                            insertionPosition.InsertTextInRun(textParagraphs[i]); 
                            if (i < length - 1)
                            { 
                                if (insertionPosition.HasNonMergeableInlineAncestor)
                                {
                                    // We cannot split a Hyperlink or other non-mergeable Inline element,
                                    // so insert a space character instead (similar to embedded object). 
                                    // Note that this means, SetText would loose
                                    // paragraph break information in this case. 
                                    insertionPosition.InsertTextInRun(" "); 
                                }
                                else 
                                {
                                    // insertionPosition gets repositioned to just inside
                                    // the following Paragraph.
                                    insertionPosition = insertionPosition.InsertParagraphBreak(); 
                                }
                                // Keep newEnd in [....] with the paragraph break. 
                                // We can't rely on LogicalDirection alone for 
                                // anything other than simple text inserts.
                                newEnd = insertionPosition; 
                            }
                        }

                        if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) 
                        {
                            // We must include ending paragraph break into a resulting range 
                            newEnd = newEnd.GetNextInsertionPosition(LogicalDirection.Forward); 
                            if (newEnd == null)
                            { 
                                newEnd = newStart.TextContainer.End; // set end of range to IsAfterLastParagraph position
                            }

                            // Note: As a result of this logic with implicitParagraphInserted && pastedFragmentEndsWithNewLine 
                            // we have the following behavior:
                            // Given that: 
                            //    range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd); 
                            // the statement:
                            //    range.Text = "foo\r\n"; 
                            // has the effect of leaving flowDocument with this content (note: just one paragraph):
                            //    foo
                            // and range selecting the whole content:
                            //    range.Text == "foo\r\n" 
                            //
                            // the statement: 
                            //    range.Text = "foo"; 
                            // results with the same content in flowDocument (one paragraph)
                            // but the range is not extended beyond last paragraph end: 
                            //    range.Text == "foo".
                        }
                    }
                    else 
                    {
                        // Non-paragraph text - insert without '\n' conversion 
                        newStart.InsertTextInRun(textData); 
                    }
 
                    // Select the range
                    TextRangeBase.SelectPrivate(thisRange, newStart, newEnd, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/true);
                }
            } 
            finally
            { 
                TextRangeBase.EndChange(thisRange); 
            }
        } 

        internal static string GetXml(ITextRange thisRange)
        {
            NormalizeRange(thisRange); 

            // Create XmlWriter 
            StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture); 
            XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
 
            TextRangeSerialization.WriteXaml(xmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null);

            return stringWriter.ToString();
        } 

        internal static bool CanSave(ITextRange thisRange, string dataFormat) 
        { 
            NormalizeRange(thisRange);
 
            bool canSave = (
                dataFormat == DataFormats.Text ||
                dataFormat == DataFormats.Xaml ||
                (SecurityHelper.CheckUnmanagedCodePermission() && ( 
                    dataFormat == DataFormats.XamlPackage ||
                    dataFormat == DataFormats.Rtf))); 
 
            return canSave;
        } 

        internal static bool CanLoad(ITextRange thisRange, string dataFormat)
        {
            NormalizeRange(thisRange); 

            bool canLoad = ( 
                dataFormat == DataFormats.Text || 
                dataFormat == DataFormats.Xaml ||
                (SecurityHelper.CheckUnmanagedCodePermission() && ( 
                    dataFormat == DataFormats.XamlPackage ||
                    dataFormat == DataFormats.Rtf)));

            return canLoad; 
        }
 
        internal static void Save(ITextRange thisRange, Stream stream, string dataFormat, bool preserveTextElements) 
        {
            if (stream == null) 
            {
                throw new ArgumentNullException("stream");
            }
            if (dataFormat == null) 
            {
                throw new ArgumentNullException("dataFormat"); 
            } 

            NormalizeRange(thisRange); 

            if (dataFormat == DataFormats.Text)
            {
                string text = thisRange.Text; 
                StreamWriter textStreamWriter = new StreamWriter(stream);
                textStreamWriter.Write(text); 
                textStreamWriter.Flush(); 
            }
            else if (dataFormat == DataFormats.Xaml) 
            {
                StreamWriter xamlStreamWriter = new StreamWriter(stream);
                XmlTextWriter xamlXmlWriter = new XmlTextWriter(xamlStreamWriter);
                // Passing null as wpfPayload parameter we request to produce 
                // xaml without images - all of them will be repllaced by whitespaces.
                TextRangeSerialization.WriteXaml(xamlXmlWriter, thisRange, /*useFlowDocumentAsRoot:*/false, /*wpfPayload:*/null, preserveTextElements); 
                xamlXmlWriter.Flush(); 
            }
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission()) 
            {
                // Non-null stream here means unconditional request to create a WPF package for the range
                // independently whether there are images in it or not.
                WpfPayload.SaveRange(thisRange, ref stream, /*useFlowDocumentAsRoot:*/false, preserveTextElements); 
            }
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission()) 
            { 
                Stream wpfPayloadMemory = null;
                // Passing null as a wpfPayloadStream we allow to not create wpf package 
                // when it is not needed (there is no images in the range)
                string xamlText = WpfPayload.SaveRange(thisRange, ref wpfPayloadMemory, /*useFlowDocumentAsRoot:*/false);
                // Convert xaml to rtf text to set rtf data into data object.
                string rtfText = TextEditorCopyPaste.ConvertXamlToRtf(xamlText, wpfPayloadMemory); 
                StreamWriter rtfStreamWriter = new StreamWriter(stream);
                rtfStreamWriter.Write(rtfText); 
                rtfStreamWriter.Flush(); 
            }
            else 
            {
                // Unsupported format - thows exception
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat");
            } 
        }
 
        internal static void Load(TextRange thisRange, Stream stream, string dataFormat) 
        {
            if (stream == null) 
            {
                throw new ArgumentNullException("stream");
            }
            if (dataFormat == null) 
            {
                throw new ArgumentNullException("dataFormat"); 
            } 

            NormalizeRange(thisRange); 

            // Reset the stream position to the beginning
            if (stream.CanSeek)
            { 
                stream.Seek(0, SeekOrigin.Begin);
            } 
 
            if (dataFormat == DataFormats.Text)
            { 
                StreamReader textStreamReader = new StreamReader(stream);
                string text = textStreamReader.ReadToEnd();
                thisRange.Text = text;
            } 
            else if (dataFormat == DataFormats.Xaml)
            { 
                StreamReader xamlStreamReader = new StreamReader(stream); 
                string xamlText = xamlStreamReader.ReadToEnd();
                thisRange.Xml = xamlText; 
            }
            else if (dataFormat == DataFormats.XamlPackage && SecurityHelper.CheckUnmanagedCodePermission())
            {
                object element = WpfPayload.LoadElement(stream); 
                if (!(element is Section) && !(element is Span))
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream"); 
                }
                thisRange.SetXmlVirtual((TextElement)element); 
            }
            else if (dataFormat == DataFormats.Rtf && SecurityHelper.CheckUnmanagedCodePermission())
            {
                // 
                StreamReader rtfStreamReader = new StreamReader(stream);
                string rtfText = rtfStreamReader.ReadToEnd(); 
                MemoryStream memoryStream = TextEditorCopyPaste.ConvertRtfToXaml(rtfText); 
                if (memoryStream == null)
                { 
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream");
                }
                TextElement textElement = WpfPayload.LoadElement(memoryStream) as TextElement;
                if (!(textElement is Section) && !(textElement is Span)) 
                {
                    throw new ArgumentException(SR.Get(SRID.TextRange_UnrecognizedStructureInDataFormat, dataFormat), "stream"); 
                } 
                thisRange.SetXmlVirtual(textElement);
            } 
            else
            {
                // Unsupported format - thows exception
                throw new ArgumentException(SR.Get(SRID.TextRange_UnsupportedDataFormat, dataFormat), "dataFormat"); 
            }
        } 
 
        // Ref count of open change blocks -- incremented/decremented
        // around BeginChange/EndChange calls. 
        internal static int GetChangeBlockLevel(ITextRange thisRange)
        {
            return thisRange._ChangeBlockLevel;
        } 

        //...................................................... 
        // 
        //  Embedded Object Selection
        // 
        //......................................................

        internal static UIElement GetUIElementSelected(ITextRange range)
        { 
            ITextPointer start = range.Start.CreatePointer();
            TextPointerContext context = start.GetPointerContext(LogicalDirection.Forward); 
            while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) 
            {
                start.MoveToNextContextPosition(LogicalDirection.Forward); 
                context = start.GetPointerContext(LogicalDirection.Forward);
            }
            if (context == TextPointerContext.EmbeddedElement)
            { 
                ITextPointer end = range.End.CreatePointer();
                context = end.GetPointerContext(LogicalDirection.Backward); 
                while (context == TextPointerContext.ElementStart || context == TextPointerContext.ElementEnd) 
                {
                    end.MoveToNextContextPosition(LogicalDirection.Backward); 
                    context = end.GetPointerContext(LogicalDirection.Backward);
                }
                if (context == TextPointerContext.EmbeddedElement && start.GetOffsetToPosition(end) == 1)
                { 
                    return start.GetAdjacentElement(LogicalDirection.Forward) as UIElement;
                } 
            } 
            return null;
        } 

        //......................................................
        //
        //  Table Selection Properties 
        //
        //...................................................... 
 
        internal static bool GetIsTableCellRange(ITextRange thisRange)
        { 
            NormalizeRange(thisRange);

            return thisRange._IsTableCellRange;
        } 

        #endregion ITextRange Properties 
 
        //------------------------------------------------------
        // 
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // Worker for the BeginChange/BeginChangeNoUndo variants. 
        // If description is null, no default undo unit is opened.
        private static void BeginChangeWorker(ITextRange thisRange, string description) 
        {
            ITextContainer textContainer = thisRange.Start.TextContainer;

            if (description != null && thisRange._ChangeBlockUndoRecord == null && thisRange._ChangeBlockLevel == 0) 
            {
                thisRange._ChangeBlockUndoRecord = new ChangeBlockUndoRecord(textContainer, description); 
            } 

            Invariant.Assert(thisRange._ChangeBlockLevel > 0 || !thisRange._IsChanged, "_changed must be false on new move sequence"); 
            thisRange._ChangeBlockLevel++;

            if (description != null)
            { 
                textContainer.BeginChange();
            } 
            else 
            {
                textContainer.BeginChangeNoUndo(); 
            }
        }

        // Creates a one-segment collection from a pair of text positions 
        // The segment normalization is done by the following rules:
        // 1. If start and end pointers have equal positions, or became equal after normalization, 
        //    then the segment uses one instance of ITextPointer for both ends, 
        //    which guarantees the segment emptiness in the subsequente editing
        //    around it. This single pointer takes orientation from start parameter 
        //    and normalized in that direction.
        // 2. In case when a segment is non-empty, two positions will be created
        //    and normalized inward - towards a segment contents;
        //    Their gravities will be directed outward (start - Backward, end -  Forward), 
        //    so that any insertion happend at segment edge position goes inside a segment
        //    - the behavior we need to inserting stuff into TextRanges. 
        private static void CreateNormalizedTextSegment(ITextRange thisRange, ITextPointer start, ITextPointer end) 
        {
            ValidationHelper.VerifyPositionPair(start, end); 

            // Normalize the segment
            if (start.CompareTo(end) == 0)
            { 
                // When the range is empty we must keep it that way during normalization
                if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection)) 
                { 
                    start = GetNormalizedPosition(thisRange, start, start.LogicalDirection);
                    end = start; 
                }
            }
            else
            { 
                start = GetNormalizedPosition(thisRange, start, LogicalDirection.Forward);
                if (!TextPointerBase.IsAfterLastParagraph(end)) 
                { 
                    // NOTE: Position after the last paragraph is special.
                    // Even though this position is not valid insertion position, 
                    // we allow ranges to reach it. This is necessary to be able
                    // to "select all" content, and select the last paragraph.
                    end = GetNormalizedPosition(thisRange, end, LogicalDirection.Backward);
                } 

                // Collapse range in case of overlapped normalization result 
                if (start.CompareTo(end) >= 0) 
                {
                    // The range is effectuvely empty, so collapse it to single pointer instance 
                    if (start.LogicalDirection == LogicalDirection.Backward)
                    {
                        // Choose a position normalized backward,
                        start = end.GetFrozenPointer(LogicalDirection.Backward); 

                        // NOTE that otherwise we will use start position, 
                        // which is oriented and normalizd Forward 
                    }
                    end = start; 
                }
                else
                {
                    // Handle Floater/Figure boundaries: non-empty ranges never cross them 
                    if (start is TextPointer)
                    { 
                        TextPointer adjustedStart = (TextPointer)start; 
                        TextPointer adjustedEnd = (TextPointer)end;
                        NormalizeAnchoredBlockBoundaries(ref adjustedStart, ref adjustedEnd); 
                        start = adjustedStart;
                        end = adjustedEnd;
                    }
 
                    Invariant.Assert(start.CompareTo(end) <= 0, "expecting start <= end");
 
                    // Normalize the segment, start and end may have become equal now. 
                    if (start.CompareTo(end) == 0)
                    { 
                        // When the range is empty we must keep it that way during normalization
                        if (!IsAtNormalizedPosition(thisRange, start, start.LogicalDirection))
                        {
                            start = GetNormalizedPosition(thisRange, start, start.LogicalDirection); 
                            end = start;
                        } 
                    } 

                    // 



 

 
 

 
                }
            }

            // Set this text segment as a selected range 
            //
            thisRange._TextSegments = new List(1); 
            thisRange._TextSegments.Add(new TextSegment(start, end)); 
            thisRange._IsTableCellRange = false;
        } 

        private static bool IsAtNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction)
        {
            bool isAtNormalizedPosition; 

            if (thisRange.IgnoreTextUnitBoundaries) 
            { 
                isAtNormalizedPosition = TextPointerBase.IsAtFormatNormalizedPosition(position, direction);
            } 
            else
            {
                isAtNormalizedPosition = TextPointerBase.IsAtInsertionPosition(position, direction);
            } 

            return isAtNormalizedPosition; 
        } 

        private static ITextPointer GetNormalizedPosition(ITextRange thisRange, ITextPointer position, LogicalDirection direction) 
        {
            ITextPointer normalizedPosition;

            if (thisRange.IgnoreTextUnitBoundaries) 
            {
                normalizedPosition = position.GetFormatNormalizedPosition(direction); 
            } 
            else
            { 
                normalizedPosition = position.GetInsertionPosition(direction);
            }

            return normalizedPosition; 
        }
 
        // Helper for CreateNormalizedTextSegment 
        // Checks whether start and end cross any Floater/Figure boundaries.
        // If yes, normalizes the position(s) so that a non-empty range never crosses Floater/Figure boundaries. 
        // Returns true if the range crosses AnchoredBlock boundary and was adjusted to not do so.
        // Returns false if it does not cross AnchoredBlock boundary and start/end stay where they were.
        internal static void NormalizeAnchoredBlockBoundaries(ref TextPointer start, ref TextPointer end)
        { 
            // Check AnchoredBlocks ancestors at start
            TextElement outerAnchoredBlock = start.Parent as TextElement; 
            while (outerAnchoredBlock != null) 
            {
                // Find the next ancestor AncoredBlock 
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType()))
                {
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement;
                } 

                if (outerAnchoredBlock != null) 
                { 
                    // Anchored block found. Check whether the other position belongs to it.
                    AnchoredBlock innerAnchoredBlock = null; 
                    TextElement innerElement = end.Parent as TextElement;
                    while (innerElement != null && innerElement != outerAnchoredBlock)
                    {
                        if (innerElement is AnchoredBlock) 
                        {
                            innerAnchoredBlock = (AnchoredBlock)innerElement; 
                        } 
                        innerElement = innerElement.Parent as TextElement;
                    } 
                    if (innerElement == outerAnchoredBlock)
                    {
                        // Common ancestor AnchoredBlock is found.
                        if (innerAnchoredBlock != null) 
                        {
                            end = innerAnchoredBlock.ElementEnd; 
                        } 
                        return;
                    } 

                    // The AnchoredElement found at start position does not include end.
                    // Expand start to include the whole outerAnchoredBlock
                    start = outerAnchoredBlock.ElementStart; 

                    // and go to the next possible AnchoredBlock level 
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }
            } 

            // Check AnchoredBlocks ancestors at end
            outerAnchoredBlock = end.Parent as TextElement;
            while (outerAnchoredBlock != null) 
            {
                // Find the next ancestor AncoredBlock 
                while (outerAnchoredBlock != null && !typeof(AnchoredBlock).IsAssignableFrom(outerAnchoredBlock.GetType())) 
                {
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }

                if (outerAnchoredBlock != null)
                { 
                    // Anchored block found. Check whether the other position belongs to it.
                    AnchoredBlock innerAnchoredBlock = null; 
                    TextElement innerElement = start.Parent as TextElement; 
                    while (innerElement != null && innerElement != outerAnchoredBlock)
                    { 
                        if (innerElement is AnchoredBlock)
                        {
                            innerAnchoredBlock = (AnchoredBlock)innerElement;
                        } 
                        innerElement = innerElement.Parent as TextElement;
                    } 
                    if (innerElement == outerAnchoredBlock) 
                    {
                        // Common ancestor AnchoredBlock is found. 
                        if (innerAnchoredBlock != null)
                        {
                            start = innerAnchoredBlock.ElementStart;
                        } 
                        return;
                    } 
 
                    // The AnchoredElement found at end position does not include start.
                    // Expand end to include the whole outerAnchoredBlock 
                    end = outerAnchoredBlock.ElementEnd;

                    // and go to the next possible AnchoredBlock level
                    outerAnchoredBlock = outerAnchoredBlock.Parent as TextElement; 
                }
            } 
        } 

        // Method used in all public entry points to 
        // ensure that thisRange is really normalized appropriately.
        private static void NormalizeRange(ITextRange thisRange)
        {
            if (thisRange._ContentGeneration == thisRange._TextSegments[0].Start.TextContainer.Generation) 
            {
                // There were no content changes since range has been built, 
                // so no normalization needed. 
                return;
            } 

            ITextPointer start = thisRange._TextSegments[0].Start;
            ITextPointer end = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End;
 
            if (thisRange._IsTableCellRange)
            { 
                Invariant.Assert(thisRange._TextSegments[0].Start is TextPointer); 

                // Table range - normalization may lead to full range rebuild 
                TextRangeEditTables.IdentifyValidBoundaries(thisRange, out start, out end);

                //
 
                SelectPrivate(thisRange, start, end, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/false);
            } 
            else 
            {
                // Text range - normalization on both ends must be ensured 
                bool needNormalization = false;

                if ((object)start == (object)end)
                { 
                    if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection))
                    { 
                        // Empty range can be normalized in any direction, 
                        // so we use direction-neutral predicate
                        needNormalization = true; 
                    }
                }
                else if (start.CompareTo(end) == 0)
                { 
                    // The range which initially was not empty is not collapsed,
                    // so we need to re-create it to use one TextPointer instance 
                    // instead of two. 
                    // Note that gravity for the start pointer is Backward
                    // in this case - so that resulting caret will be normalized/ 
                    // oriented backward.
                    needNormalization = true;
                }
                else if ( 
                    !TextPointerBase.IsAtInsertionPosition(start, LogicalDirection.Forward) ||
                    !TextPointerBase.IsAtInsertionPosition(end, LogicalDirection.Backward)) 
                { 
                    // If for a non-empty range, start/end are not at insertion position,
                    // we need to normalize it. 
                    needNormalization = true;
                }

                if (needNormalization) 
                {
                    CreateNormalizedTextSegment(thisRange, start, end); 
                } 
            }
 
            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work.
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation;
        } 

        // Implementation body of range Select method 
        // When the range is being built for the very first time OR a table range is being rebuilt during normalization, 
        // we do not fire any range notifications.
        // NOTE: Because this method is called from NormalizeRange method we should totally avoid calling 
        // any ITextRange methods involving normalization (such as Start/End)
        private static void SelectPrivate(ITextRange thisRange, ITextPointer position1, ITextPointer position2, bool includeCellAtMovingPosition, bool markRangeChanged)
        {
            List textSegments; 
            bool isTableCellRange;
 
            Invariant.Assert(position1 != null, "null check: position1"); 
            Invariant.Assert(position2 != null, "null check: position2");
 
            if (position1 is TextPointer)
            {
                textSegments = TextRangeEditTables.BuildTableRange(
                    /*anchorPosition:*/(TextPointer)position1, 
                    /*movingPosition:*/(TextPointer)position2,
                    includeCellAtMovingPosition, 
                    out isTableCellRange); 
            }
            else 
            {
                // We have abstract TextContainer - never expect/build table range in this case
                Invariant.Assert(!thisRange._IsTableCellRange, "range is not expected to be in IsTableCellRange state - 1");
                textSegments = null; 
                isTableCellRange = false;
            } 
 
            if (textSegments != null)
            { 
                // Note also that table range is always in normalized condition - by construction
                //
                thisRange._TextSegments = textSegments;
                thisRange._IsTableCellRange = isTableCellRange; 
            }
            else 
            { 
                // Simple textsegment case
                ITextPointer newStart = position1; 
                ITextPointer newEnd = position2;

                // Swap pointers to order them properly.
                // We do not sewap them when they are equal, 
                // so that for empty range we are predictable about
                // collapsed position orientation - taken from the first parameter 
                // (position1) (as per CreateNormalizedTextSegment semantics). 
                if (position1.CompareTo(position2) > 0)
                { 
                    newStart = position2;
                    newEnd = position1;
                }
 
                // Create new segment. Note that we do this unconditionally,
                // not trying to bypass it if new positions look the same as currently set, 
                // because we need to ensure range normalization here. 
                TextRangeBase.CreateNormalizedTextSegment(thisRange, newStart, newEnd);
                Invariant.Assert(!thisRange._IsTableCellRange, "Expecting that the range is in text segment state now - must be set by CreateNOrmalizedTextSegment"); 

                // Before setting final range state we need to check if we are still in TextSegment condition
                if (position1 is TextPointer)
                { 
                    ITextPointer finalStart = thisRange._TextSegments[0].Start;
                    ITextPointer finalEnd = thisRange._TextSegments[thisRange._TextSegments.Count - 1].End; 
                    if (finalStart.CompareTo(newStart) != 0 || finalEnd.CompareTo(newEnd) != 0) 
                    {
                        // This means that as a result of position normalization they have been moved 
                        // so we must check what is the range state now.
                        // NOTE: We are in TextSegment state now, so anchor/moving ordering is not important,
                        // so we use thisRange.Start/End (normalized) whose order may be different from
                        // position1/position2. 
                        // Note: we use includeCellAtMovingPosition=false here because the movingPosition is taken from a constructed table range, not from input
                        textSegments = TextRangeEditTables.BuildTableRange( 
                            /*anchorPosition:*/(TextPointer)finalStart, 
                            /*movingPosition:*/(TextPointer)finalEnd,
                            /*includeCellAtMovingPosition:*/false, 
                            out isTableCellRange);
                        if (textSegments != null)
                        {
                            thisRange._TextSegments = textSegments; 
                            thisRange._IsTableCellRange = isTableCellRange;
                        } 
                    } 
                }
            } 

            // Store content generation which will be needed in range normalization for
            // avoiding unnecessary work.
            thisRange._ContentGeneration = thisRange._TextSegments[0].Start.TextContainer.Generation; 

            if (markRangeChanged) 
            { 
                //
                TextRangeBase.MarkRangeChanged(thisRange); 
            }
        }

        ///  
        /// Raises the Changed event for this range.
        /// 
        /// It must be called within a BeginChange/EndChange 
        /// block.
        ///  
        private static void MarkRangeChanged(ITextRange thisRange)
        {
            Invariant.Assert(thisRange._ChangeBlockLevel > 0, "changeBlockLevel > 0 is expected");
            thisRange._IsChanged = true; 
        }
 
        #endregion Private Methods 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK