File: System\Windows\Documents\RangeContentEnumerator.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#pragma warning disable 1634, 1691 // To enable presharp warning disables (#pragma suppress) below.
//
// Description: IEnumerator for TextRange and TextElement content.
//
 
using System;
using System.Collections;
using MS.Internal;
using System.Text;
using MS.Utility;
using System.Windows.Controls;
 
#pragma warning disable 1634, 1691  // suppressing PreSharp warnings
 
namespace System.Windows.Documents
{
    internal class RangeContentEnumerator : IEnumerator
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Creates an enumerator instance.
        // Null start/end creates an empty enumerator.
        internal RangeContentEnumerator(TextPointer start, TextPointer end)
        {
            Invariant.Assert(start != null && end != null || start == null && end == null, "If start is null end should be null!");
 
            _start = start;
            _end = end;
 
            // Remember what generation the backing store was in when we started,
            // so we can throw if this enumerator is accessed after content has
            // changed.
            if (_start != null)
            {
                _generation = _start.TextContainer.Generation;
            }
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        /// <summary>
        /// Return the current object this enumerator is pointing to.  This is 
        /// generally a FrameworkElement, TextElement, an embedded
        /// object, or Text content.
        /// </summary>
        public object Current
        {
            get 
            { 
                int runLength;
                int offset;
 
                if (_navigator == null)
                {
                    // Disable presharp 6503: Property get methods should not throw exceptions.
                    // We must throw here -- it's part of the IEnumerator contract.
                    #pragma warning suppress 6503
                    throw new InvalidOperationException(SR.EnumeratorNotStarted);
                }
 
                // Check _currentCache before looking at _navigator.  For the
                // last item in the enumeration, _navigator == _end, but
                // that's ok, the previous get_Current call (which sets _currentCache
                // non-null) put it there.
                if (_currentCache != null)
                {
                    return _currentCache;
                }
 
                if (_navigator.CompareTo(_end) >= 0)
                {
                    #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
                    throw new InvalidOperationException(SR.EnumeratorReachedEnd);
                }
 
                // Throw if the tree has been modified since this enumerator was created unless a tree walk
                // by the property system is in progress. For example, changing DataContext on FlowDocument
                // can invalidate a binding on Run.Text during the inherited property change tree walk,
                // which in turn can modify the TextContainer.
                if (_generation != _start.TextContainer.Generation && !IsLogicalChildrenIterationInProgress)
                {
                    #pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
                    throw new InvalidOperationException(SR.EnumeratorVersionChanged);
                }
 
                switch (_navigator.GetPointerContext(LogicalDirection.Forward))
                {
                    case TextPointerContext.Text:
                        offset = 0;
 
                        // Merge all successive text runs into a single value.
                        do
                        {
                            runLength = _navigator.GetTextRunLength(LogicalDirection.Forward);
                            EnsureBufferCapacity(offset + runLength);
                            _navigator.GetTextInRun(LogicalDirection.Forward, _buffer, offset, runLength);
                            offset += runLength;
                            _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        }
                        while (_navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text);
 
                        _currentCache = new string(_buffer, 0, offset);
 
                        // _navigator is at next content.
                        break;
 
                    case TextPointerContext.EmbeddedElement:
                        _currentCache = _navigator.GetAdjacentElement(LogicalDirection.Forward);
 
                        // Position _navigator at next content.
                        _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break;
 
                    case TextPointerContext.ElementStart:
                        // Return the element, and advance past its entire content.
                        _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        _currentCache = _navigator.Parent;
 
                        // Position _navigator at next content.
                        _navigator.MoveToElementEdge(ElementEdge.AfterEnd);
                        break;
 
                    default:
                        Invariant.Assert(false, "Unexpected run type!");
                        _currentCache = null; // We should never get here.
                        break;
                }
 
                return _currentCache;
            }
        }
 
        /// <summary>
        /// Advance the enumerator to the next object in the range.  Return true if content
        /// was found.
        /// </summary>
        public bool MoveNext()
        {
            // Throw if the tree has been modified since this enumerator was created unless a tree walk
            // by the property system is in progress. For example, changing DataContext on FlowDocument
            // can invalidate a binding on Run.Text during the inherited property change tree walk,
            // which in turn can modify the TextContainer.
            if (_start != null && _generation != _start.TextContainer.Generation && !IsLogicalChildrenIterationInProgress)
            {
                throw new InvalidOperationException(SR.EnumeratorVersionChanged);
            }
 
            if (_start == null || _start.CompareTo(_end) == 0)
                return false;
 
            if (_navigator != null && _navigator.CompareTo(_end) >= 0)
            {
                return false;
            }
 
            if (_navigator == null)
            {
                _navigator = new TextPointer(_start);
            }
            else if (_currentCache == null) // If we have a cache, _navigator is already positioned at the next item.
            {
                switch (_navigator.GetPointerContext(LogicalDirection.Forward))
                {
                    case TextPointerContext.Text:
 
                        // Skip all successive text runs as a single value.
                        do
                        {
                            _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        }
                        while (_navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text && _navigator.CompareTo(_end) < 0);
 
                        break;
 
                    case TextPointerContext.EmbeddedElement:
                        _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        break;
 
                    case TextPointerContext.ElementStart:
                        // Return the element, and advance past its entire content.
                        _navigator.MoveToNextContextPosition(LogicalDirection.Forward);
                        _navigator.MoveToPosition(((TextElement)_navigator.Parent).ElementEnd);
                        break;
 
                    default:
                        Invariant.Assert(false, "Unexpected run type!");
                        break;
                }
            }
 
            _currentCache = null;
 
            return (_navigator.CompareTo(_end) < 0);
        }
 
        /// <summary>
        /// Reset the enumerator to the start of the range.
        /// </summary>
        public void Reset()
        {
            // Throw if the tree has been modified since this enumerator was created.
            if (_start != null && _generation != _start.TextContainer.Generation)
            {
                throw new InvalidOperationException(SR.EnumeratorVersionChanged);
            }
 
            _navigator = null;
        }
 
        #endregion Public Methods        
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Events
        //
        //------------------------------------------------------
 
        #region Public Events
 
        #endregion Public Events
 
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
 
        #region Protected Methods
 
        #endregion Protected Events
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        #endregion Internal methods
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
 
        #region Internal Events
 
        #endregion Internal Events
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        private void EnsureBufferCapacity(int size)
        {
            char [] newBuffer;
 
            if (_buffer == null)
            {
                _buffer = new char[size];
            }
            else if (_buffer.Length < size)
            {
                newBuffer = new char[Math.Max(2*_buffer.Length, size)];
                _buffer.CopyTo(newBuffer, 0);
                _buffer = newBuffer;
            }
        }
 
        #endregion Private methods
 
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
 
        #region Private Properties
 
        private bool IsLogicalChildrenIterationInProgress
        {
            get 
            {
                DependencyObject node = _start.Parent;
 
                while (node != null)
                {
                    FrameworkElement fe = node as FrameworkElement;
                    if (fe != null)
                    {
                        if (fe.IsLogicalChildrenIterationInProgress)
                        {
                            return true;
                        }
                    }
                    else
                    {
                        FrameworkContentElement fce = node as FrameworkContentElement;
                        if (fce != null && fce.IsLogicalChildrenIterationInProgress)
                        {
                            return true;
                        }
                    }
 
                    node = LogicalTreeHelper.GetParent(node);
                }
 
                return false;
            }
        }
 
        #endregion Private Properties
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // Edges of the span to enumerator over.
        private readonly TextPointer _start;
        private readonly TextPointer _end;
 
        // Backing store generation when this enumerator was created.
        private readonly uint _generation;
 
        // Current position in the enumeration.
        private TextPointer _navigator;
 
        // Calculated content at the current position.
        private object _currentCache;
 
        // Buffer to text content retrieval.
        private char [] _buffer;
 
        #endregion Private Fields
    }
}