File: Microsoft.Tools.Common\Microsoft\Activities\Presentation\Xaml\SourceTextScanner.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
// <copyright>
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
 
namespace Microsoft.Activities.Presentation.Xaml
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
 
    // NOTE: (x, y) denotes line x column y, where x and y are 0 based, 
    // while the line/column in SourceLocation and LineNumberPair is 1 based.
    internal class SourceTextScanner
    {
        private const char NewLine = '\n';
 
        private const char Return = '\r';
 
        private string source;
 
        // <start index, length>
        // say a line is "abc\n"
        // the length is 4, with '\n' included.
        private List<Tuple<int, int>> indexCache;
 
        internal SourceTextScanner(string source)
        {
            SharedFx.Assert(source != null, "source != null");
            this.source = source;
            this.indexCache = new List<Tuple<int, int>>();
        }
 
        internal Tuple<LineColumnPair, char> SearchCharAfter(LineColumnPair startPoint, params char[] charsToSearch)
        {
            SharedFx.Assert(startPoint != null, "startPoint != null");
 
            int line = startPoint.LineNumber - 1;
            int column = startPoint.ColumnNumber - 1;
 
            HashSet<char> charsToSearchSet = new HashSet<char>(charsToSearch);
            int index = this.GetIndex(line, column);
            if (index < 0)
            {
                return null;
            }
 
            bool firstLoop = true;
            foreach (Tuple<char, int> currentPair in this.Scan(index))
            {
                if (firstLoop)
                {
                    firstLoop = false;
                }
                else
                {
                    if (charsToSearchSet.Contains(currentPair.Item1))
                    {
                        LineColumnPair location = this.GetLocation(currentPair.Item2);
                        SharedFx.Assert(location != null, "invalid location");
                        return Tuple.Create(location, currentPair.Item1);
                    }
                }
            }
 
            return null;
        }
 
        private LineColumnPair GetLocation(int index)
        {
            SharedFx.Assert(index >= 0 && index < this.source.Length, "index out of range");
 
            while (!this.IsIndexInScannedLine(index))
            {
                this.TryScanNextLine();
            }
            
            int line = this.indexCache.Count - 1;
            for (; line >= 0; --line)
            {
                if (index >= this.indexCache[line].Item1)
                {
                    break;
                }
            }
            
            SharedFx.Assert(line >= 0, "line < this.indexCache.Count");
            int column = index - this.indexCache[line].Item1;
            SharedFx.Assert(column < this.indexCache[line].Item2, "Should Not Happen");
 
            return new LineColumnPair(line + 1, column + 1);
        }
        
        private int GetIndex(int line, int column)
        {
            while (this.indexCache.Count <= line)
            {
                if (!this.TryScanNextLine())
                {
                    break;
                }
            }
 
            if (this.indexCache.Count <= line)
            {
                SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "line out of range:({0},{1})", line + 1, column + 1));
                return -1;
            }
 
            if (column >= this.indexCache[line].Item2)
            {
                SharedFx.Assert(string.Format(CultureInfo.CurrentCulture, "column out of range:({0},{1})", line + 1, column + 1));
                return -1;
            }
 
            return this.indexCache[line].Item1 + column;
        }
 
        private bool IsIndexInScannedLine(int index)
        {
            SharedFx.Assert(index >= 0 && index < this.source.Length, "invalid index");
 
            int last = this.indexCache.Count - 1;
            return last >= 0 && index < this.indexCache[last].Item1 + this.indexCache[last].Item2;
        }
 
        // return created
        private bool TryScanNextLine()
        {
            int startIndex = 0;
            if (this.indexCache.Count > 0)
            {
                int tail = this.indexCache.Count - 1;
                startIndex = this.indexCache[tail].Item1 + this.indexCache[tail].Item2;
            }
 
            if (startIndex >= this.source.Length)
            {
                return false;
            }
 
            int lastIndex = -1;
            foreach (Tuple<char, int> currentPair in this.Scan(startIndex))
            {
                lastIndex = currentPair.Item2;
                if (currentPair.Item1 == NewLine)
                {
                    break;
                }
            }
 
            if (lastIndex < 0)
            {
                SharedFx.Assert("lastIndex < 0");
                return false;
            }
 
            int lineLength = lastIndex - startIndex + 1;
            this.indexCache.Add(Tuple.Create(startIndex, lineLength));
            return true;
        }
 
        // Tuple<current charactor, charactor index>
        // this Scan will replace \r\n=>\n \r=>\n
        // \r\n return: <\n, \n's index>
        // \r   return: <\n, \r's index>
        private IEnumerable<Tuple<char, int>> Scan(int index)
        {
            if (index < 0 || index >= this.source.Length)
            {
                SharedFx.Assert("index < 0 || index >= this.source.Length");
                yield break;
            }
 
            while (index < this.source.Length)
            {
                char currentChar = this.source[index];
 
                if (currentChar == Return)
                {
                    if (index + 1 < this.source.Length && this.source[index + 1] == NewLine)
                    {
                        ++index;
                    }
 
                    currentChar = NewLine;
                }
 
                yield return Tuple.Create(currentChar, index);
                ++index;
            }
        }
    }
}