File: src\Microsoft.Data.Analysis\TextFieldParser.cs
Web Access
Project: src\test\Microsoft.Data.Analysis.Tests\Microsoft.Data.Analysis.Tests.csproj (Microsoft.Data.Analysis.Tests)
// 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.
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
 
namespace Microsoft.Data.Analysis
{
    internal enum FieldType
    {
        Delimited,
        FixedWidth
    }
 
    internal class QuoteDelimitedFieldBuilder
    {
        private readonly StringBuilder _field;
        private bool _fieldFinished;
        private int _index;
        private int _delimiterLength;
        private readonly Regex _delimiterRegex;
        private readonly string _spaceChars;
        private bool _malformedLine;
 
        public QuoteDelimitedFieldBuilder(Regex delimiterRegex, string spaceChars)
        {
            _delimiterRegex = delimiterRegex;
            _spaceChars = spaceChars;
            _field = new StringBuilder();
        }
 
        public bool FieldFinished => _fieldFinished;
 
        public string Field => _field.ToString();
 
        public int Index => _index;
 
        public int DelimiterLength => _delimiterLength;
 
        public bool MalformedLine => _malformedLine;
 
        public void BuildField(string line, int startAt)
        {
            _index = startAt;
            int length = line.Length;
 
            while (_index < length)
            {
                if (line[_index] == '"')
                {
                    // Are we at the end of a file?
                    if (_index + 1 == length)
                    {
                        // We've found the end of the field
                        _fieldFinished = true;
                        _delimiterLength = 1;
 
                        // Move index past end of line
                        _index++;
                        return;
                    }
                    // Check to see if this is an escaped quote
                    if (_index + 1 < line.Length && line[_index + 1] == '"')
                    {
                        _field.Append('"');
                        _index += 2;
                        continue;
                    }
 
                    // Find the next delimiter and make sure everything between the quote and delimiter is ignorable
                    int Limit;
                    Match delimiterMatch = _delimiterRegex.Match(line, _index + 1);
                    if (!delimiterMatch.Success)
                    {
                        Limit = length - 1;
                    }
                    else
                    {
                        Limit = delimiterMatch.Index - 1;
                    }
 
                    for (int i = _index + 1; i < Limit; i++)
                    {
                        if (_spaceChars.IndexOf(line[i]) < 0)
                        {
                            _malformedLine = true;
                            return;
                        }
                    }
 
                    // The length of the delimiter is the length of the closing quote (1) + any spaces + the length of the delimiter we matched if any
                    _delimiterLength = 1 + Limit - _index;
                    if (delimiterMatch.Success)
                    {
                        _delimiterLength += delimiterMatch.Length;
                    }
 
                    _fieldFinished = true;
                    return;
                }
                else
                {
                    _field.Append(line[_index]);
                    _index += 1;
                }
            }
        }
    }
 
 
    internal class TextFieldParser : IDisposable
    {
        private delegate int ChangeBufferFunction();
 
        private bool _disposed;
 
        private TextReader _reader;
 
        private string[] _commentTokens = null;
 
        private long _lineNumber = 1;
 
        private bool _endOfData;
 
        private string _errorLine = "";
 
        private long _errorLineNumber = -1;
 
        private FieldType _textFieldType = FieldType.Delimited;
 
        private int[] _fieldWidths;
 
        private int[] _fieldWidthsCopy;
 
        private string[] _delimiters;
 
        private string[] _delimitersCopy;
 
        private Regex _delimiterRegex;
 
        private Regex _delimiterWithEndCharsRegex;
 
        private readonly int[] _whitespaceCodes = new int[] { '\u0009', '\u000B', '\u000C', '\u0020', '\u0085', '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u200B', '\u2028', '\u2029', '\u3000', '\uFEFF' };
 
        private Regex _beginQuotesRegex;
 
        private bool _trimWhiteSpace = true;
 
        private int _position;
 
        private int _peekPosition;
 
        private int _charsRead;
 
        private bool _needPropertyCheck = true;
 
        private const int DEFAULT_BUFFER_LENGTH = 4096;
 
        private char[] _buffer = new char[DEFAULT_BUFFER_LENGTH];
 
        private bool _hasFieldsEnclosedInQuotes = true;
 
        private int _lineLength;
 
        private string _spaceChars;
 
        private readonly int _maxLineSize = 10000000;
 
        private readonly int _maxBufferSize = 10000000;
 
        private readonly bool _leaveOpen;
 
        private readonly char[] _newLineChars = Environment.NewLine.ToCharArray();
 
        public string[] CommentTokens
        {
            get => _commentTokens;
            set
            {
                CheckCommentTokensForWhitespace(value);
                _commentTokens = value;
                _needPropertyCheck = true;
            }
        }
 
        public bool EndOfData
        {
            get
            {
                if (_endOfData)
                {
                    return _endOfData;
                }
                if ((_reader == null) || (_buffer == null))
                {
                    _endOfData = true;
                    return true;
                }
                if (PeekNextDataLine() != null)
                {
                    return false;
                }
                _endOfData = true;
                return true;
            }
        }
 
        public long LineNumber
        {
            get
            {
                if (_lineNumber != -1 && ((_reader.Peek() == -1) && (_position == _charsRead)))
                {
                    // Side effect of a property. Not great. Just leaving it in for now.
                    CloseReader();
                }
                return _lineNumber;
            }
        }
 
        public string ErrorLine => _errorLine;
 
        public long ErrorLineNumber => _errorLineNumber;
 
        public FieldType TextFieldType
        {
            get => _textFieldType;
            set
            {
                ValidateFieldTypeEnumValue(value, "value");
                _textFieldType = value;
                _needPropertyCheck = true;
            }
        }
 
        public int[] FieldWidths
        {
            get => _fieldWidths;
            private set
            {
                if (value != null)
                {
                    ValidateFieldWidthsOnInput(value);
                    _fieldWidthsCopy = (int[])value.Clone();
                }
                else
                {
                    _fieldWidthsCopy = null;
                }
                _fieldWidths = value;
                _needPropertyCheck = true;
            }
        }
 
        public string[] Delimiters
        {
            get => _delimiters;
            private set
            {
                if (value != null)
                {
                    ValidateDelimiters(value);
                    _delimitersCopy = (string[])value.Clone();
                }
                else
                {
                    _delimitersCopy = null;
                }
                _delimiters = value;
                _needPropertyCheck = true;
                _beginQuotesRegex = null;
            }
        }
 
        public bool TrimWhiteSpace
        {
            get => _trimWhiteSpace;
            set
            {
                _trimWhiteSpace = value;
            }
        }
 
        public bool HasFieldsEnclosedInQuotes
        {
            get => _hasFieldsEnclosedInQuotes;
            set
            {
                _hasFieldsEnclosedInQuotes = value;
            }
        }
 
        private Regex BeginQuotesRegex
        {
            get
            {
                if (_beginQuotesRegex == null)
                {
                    string pattern = string.Format(CultureInfo.InvariantCulture, "\\G[{0}]*\"", WhitespacePattern);
                    _beginQuotesRegex = new Regex(pattern, RegexOptions.CultureInvariant);
                }
                return _beginQuotesRegex;
            }
        }
 
        private string EndQuotePattern => string.Format(CultureInfo.InvariantCulture, "\"[{0}]*", WhitespacePattern);
 
        private string WhitespaceCharacters
        {
            get
            {
                StringBuilder builder = new StringBuilder();
                int[] whitespaceCodes = _whitespaceCodes;
                foreach (int code in whitespaceCodes)
                {
                    char spaceChar = (char)code;
                    if (!CharacterIsInDelimiter(spaceChar))
                    {
                        builder.Append(spaceChar);
                    }
                }
                return builder.ToString();
            }
        }
 
        private string WhitespacePattern
        {
            get
            {
                StringBuilder builder = new StringBuilder();
                int[] whitespaceCodes = _whitespaceCodes;
                for (int i = 0; i < whitespaceCodes.Length; i++)
                {
                    int code = whitespaceCodes[i];
                    char spaceChar = (char)code;
                    if (!CharacterIsInDelimiter(spaceChar))
                    {
                        builder.Append("\\u" + code.ToString("X4", CultureInfo.InvariantCulture));
                    }
                }
                return builder.ToString();
            }
        }
 
        public TextFieldParser(string path)
        {
            InitializeFromPath(path, Encoding.ASCII, detectEncoding: true);
        }
 
        public TextFieldParser(string path, Encoding defaultEncoding)
        {
            InitializeFromPath(path, defaultEncoding, detectEncoding: true);
        }
 
        public TextFieldParser(string path, Encoding defaultEncoding, bool detectEncoding)
        {
            InitializeFromPath(path, defaultEncoding, detectEncoding);
        }
 
        public TextFieldParser(Stream stream)
        {
            InitializeFromStream(stream, Encoding.ASCII, detectEncoding: true);
        }
 
        public TextFieldParser(Stream stream, Encoding defaultEncoding)
        {
            InitializeFromStream(stream, defaultEncoding, detectEncoding: true);
        }
 
        public TextFieldParser(Stream stream, Encoding defaultEncoding, bool detectEncoding)
        {
            InitializeFromStream(stream, defaultEncoding, detectEncoding);
        }
 
        public TextFieldParser(Stream stream, Encoding defaultEncoding, bool detectEncoding, bool leaveOpen)
        {
            _leaveOpen = leaveOpen;
            InitializeFromStream(stream, defaultEncoding, detectEncoding);
        }
 
        public TextFieldParser(TextReader reader)
        {
            _reader = reader ?? throw new ArgumentNullException(nameof(reader));
            ReadToBuffer();
        }
 
        public void SetDelimiters(params string[] delimiters)
        {
            Delimiters = delimiters;
        }
 
        public void SetFieldWidths(params int[] fieldWidths)
        {
            FieldWidths = fieldWidths;
        }
 
 
        public string ReadLine()
        {
            if ((_reader == null) || (_buffer == null))
            {
                return null;
            }
 
            ChangeBufferFunction BufferFunction = ReadToBuffer;
            string line = ReadNextLine(ref _position, BufferFunction);
            if (line == null)
            {
                FinishReading();
                return null;
            }
 
            _lineNumber++;
            return line.TrimEnd(_newLineChars);
        }
 
        public string[] ReadFields()
        {
            if ((_reader == null) || (_buffer == null))
            {
                return null;
            }
            ValidateReadyToRead();
            switch (_textFieldType)
            {
                case FieldType.FixedWidth:
                    return ParseFixedWidthLine();
                case FieldType.Delimited:
                    return ParseDelimitedLine();
                default:
                    Debug.Fail("The TextFieldType is not supported");
                    return null;
            }
        }
 
        ///<summary>
        /// Peek at <paramref name="numberOfChars"/> characters of the next data line without reading the line
        ///</summary>
        ///<param name="numberOfChars">The number of characters to look at in the next data line.</param>
        ///<returns>A string consisting of the first <paramref name="numberOfChars"/> characters of the next line. >If numberOfChars is greater than the next line, only the next line is returned</returns>
        public string PeekChars(int numberOfChars)
        {
            if (numberOfChars <= 0)
            {
                throw new ArgumentException(string.Format(Strings.PositiveNumberOfCharacters, nameof(numberOfChars)));
            }
 
            if ((_reader == null) || (_buffer == null))
            {
                return null;
            }
 
            if (_endOfData)
            {
                return null;
            }
 
            string line = PeekNextDataLine();
            if (line == null)
            {
                _endOfData = true;
                return null;
            }
 
            line = line.TrimEnd(_newLineChars);
            if (line.Length < numberOfChars)
            {
                return line;
            }
 
            return line.Substring(0, numberOfChars);
        }
 
 
        public string ReadToEnd()
        {
            if ((_reader == null) || (_buffer == null))
            {
                return null;
            }
            StringBuilder builder = new StringBuilder(_buffer.Length);
            builder.Append(_buffer, _position, _charsRead - _position);
            builder.Append(_reader.ReadToEnd());
            FinishReading();
            return builder.ToString();
        }
 
        public void Close()
        {
            CloseReader();
        }
 
        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!_disposed)
                {
                    Close();
                }
                _disposed = true;
            }
        }
 
        private void ValidateFieldTypeEnumValue(FieldType value, string paramName)
        {
            if (value < FieldType.Delimited || value > FieldType.FixedWidth)
            {
                throw new InvalidEnumArgumentException(paramName, (int)value, typeof(FieldType));
            }
        }
 
        private void CloseReader()
        {
            FinishReading();
            if (_reader != null)
            {
                if (!_leaveOpen)
                {
                    _reader.Close();
                }
                _reader = null;
            }
        }
 
        private void FinishReading()
        {
            _lineNumber = -1L;
            _endOfData = true;
            _buffer = null;
            _delimiterRegex = null;
            _beginQuotesRegex = null;
        }
 
        private void InitializeFromPath(string path, Encoding defaultEncoding, bool detectEncoding)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }
            if (defaultEncoding == null)
            {
                throw new ArgumentNullException(nameof(defaultEncoding));
            }
            string fullPath = ValidatePath(path);
            FileStream fileStreamTemp = new FileStream(fullPath, (FileMode.Open), (FileAccess.Read), (FileShare.ReadWrite));
            _reader = new StreamReader(fileStreamTemp, defaultEncoding, detectEncoding);
            ReadToBuffer();
        }
 
        private void InitializeFromStream(Stream stream, Encoding defaultEncoding, bool detectEncoding)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }
            if (!stream.CanRead)
            {
                throw new ArgumentException(Strings.StreamDoesntSupportReading);
            }
            if (defaultEncoding == null)
            {
                throw new ArgumentNullException(nameof(defaultEncoding));
            }
            _reader = new StreamReader(stream, defaultEncoding, detectEncoding);
            ReadToBuffer();
        }
 
        private string ValidatePath(string path)
        {
            if (!File.Exists(path))
            {
                throw new FileNotFoundException(Strings.FileNotFound);
            }
            return path;
        }
 
        private bool IgnoreLine(string line)
        {
            if (line == null)
            {
                return false;
            }
            string trimmedLine = line.Trim();
            if (trimmedLine.Length == 0)
            {
                return true;
            }
            if (_commentTokens != null)
            {
                string[] commentTokens = _commentTokens;
                foreach (string Token in commentTokens)
                {
                    if (Token == string.Empty)
                    {
                        continue;
                    }
                    if (trimmedLine.StartsWith(Token, StringComparison.Ordinal))
                    {
                        return true;
                    }
                    if (line.StartsWith(Token, StringComparison.Ordinal))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
 
        private int ReadToBuffer()
        {
            Debug.Assert(_buffer != null, "There's no buffer");
            Debug.Assert(_reader != null, "There's no StreamReader");
            _position = 0;
            int BufferLength = _buffer.Length;
            Debug.Assert(BufferLength >= DEFAULT_BUFFER_LENGTH, "Buffer shrunk to below default");
            if (BufferLength > DEFAULT_BUFFER_LENGTH)
            {
                BufferLength = DEFAULT_BUFFER_LENGTH;
                _buffer = new char[BufferLength];
            }
            _charsRead = _reader.Read(_buffer, 0, BufferLength);
            return _charsRead;
        }
 
        private int SlideCursorToStartOfBuffer()
        {
            Debug.Assert(_buffer != null, "There's no buffer");
            Debug.Assert(_reader != null, "There's no StreamReader");
            Debug.Assert((_position >= 0) && (_position <= _buffer.Length), "The cursor is out of range");
            if (_position > 0)
            {
                int bufferLength = _buffer.Length;
                char[] tempArray = new char[bufferLength];
                Array.Copy(_buffer, _position, tempArray, 0, bufferLength - _position);
                int charsRead = _reader.Read(tempArray, bufferLength - _position, _position);
                _charsRead = _charsRead - _position + charsRead;
                _position = 0;
                _buffer = tempArray;
                return charsRead;
            }
            return 0;
        }
 
        private int IncreaseBufferSize()
        {
            Debug.Assert(_buffer != null, "There's no buffer");
            Debug.Assert(_reader != null, "There's no StreamReader");
            _peekPosition = _charsRead;
            int bufferSize = _buffer.Length + DEFAULT_BUFFER_LENGTH;
            if (bufferSize > _maxBufferSize)
            {
                throw new Exception(Strings.ExceededMaxBufferSize);
            }
            char[] tempArray = new char[bufferSize];
            Array.Copy(_buffer, tempArray, _buffer.Length);
            int charsRead = _reader.Read(tempArray, _buffer.Length, DEFAULT_BUFFER_LENGTH);
            _buffer = tempArray;
            _charsRead += charsRead;
            Debug.Assert(_charsRead <= bufferSize, "We've read more chars than we have space for");
            return charsRead;
        }
 
        private string ReadNextDataLine()
        {
            ChangeBufferFunction BufferFunction = ReadToBuffer;
            string line;
            do
            {
                line = ReadNextLine(ref _position, BufferFunction);
                _lineNumber++;
            }
            while (IgnoreLine(line));
            if (line == null)
            {
                CloseReader();
            }
            return line;
        }
 
        private string PeekNextDataLine()
        {
            ChangeBufferFunction BufferFunction = IncreaseBufferSize;
            SlideCursorToStartOfBuffer();
            _peekPosition = 0;
            string line;
            do
            {
                line = ReadNextLine(ref _peekPosition, BufferFunction);
            }
            while (IgnoreLine(line));
            return line;
        }
 
        private string ReadNextLine(ref int cursor, ChangeBufferFunction changeBuffer)
        {
            Debug.Assert(_buffer != null, "There's no buffer");
            Debug.Assert((cursor >= 0) && (cursor <= _charsRead), "The cursor is out of range");
            if (cursor == _charsRead && changeBuffer() == 0)
            {
                return null;
            }
            StringBuilder Builder = null;
            // Consider replacing this do-while with a string search to take advantage of vectorization
            do
            {
                for (int i = cursor; i <= _charsRead - 1; i++)
                {
                    char Character = _buffer[i];
                    if (!(Character.Equals('\r') || Character.Equals('\n')))
                    {
                        continue;
                    }
                    if (Builder != null)
                    {
                        Builder.Append(_buffer, cursor, i - cursor + 1);
                    }
                    else
                    {
                        Builder = new StringBuilder(i + 1);
                        Builder.Append(_buffer, cursor, i - cursor + 1);
                    }
                    cursor = i + 1;
                    if (Character.Equals('\r'))
                    {
                        if (cursor < _charsRead)
                        {
                            if (_buffer[cursor].Equals('\n'))
                            {
                                cursor++;
                                Builder.Append("\n");
                            }
                        }
                        else if (changeBuffer() > 0 && _buffer[cursor].Equals('\n'))
                        {
                            cursor++;
                            Builder.Append("\n");
                        }
                    }
                    return Builder.ToString();
                }
 
                // Searched the whole buffer and haven't found an end of line. Save what we have and read more to the buffer
                int Size = _charsRead - cursor;
                if (Builder == null)
                {
                    Builder = new StringBuilder(Size + 10);
                }
                Builder.Append(_buffer, cursor, Size);
            }
            while (changeBuffer() > 0);
 
            return Builder.ToString();
        }
 
        private string[] ParseDelimitedLine()
        {
            string line = ReadNextDataLine();
            if (line == null)
            {
                return null;
            }
            long currentLineNumber = _lineNumber - 1;
            int index = 0;
            List<string> Fields = new List<string>();
            int lineEndIndex = GetEndOfLineIndex(line);
            while (index <= lineEndIndex)
            {
                Match matchResult = null;
                bool quoteDelimited = false;
                if (HasFieldsEnclosedInQuotes)
                {
                    matchResult = BeginQuotesRegex.Match(line, index);
                    quoteDelimited = matchResult.Success;
                }
                string field;
                if (quoteDelimited)
                {
                    // Move the Index beyond quote
                    index = matchResult.Index + matchResult.Length;
 
                    // Looking for the closing quote
                    QuoteDelimitedFieldBuilder endHelper = new QuoteDelimitedFieldBuilder(_delimiterWithEndCharsRegex, _spaceChars);
                    endHelper.BuildField(line, index);
                    if (endHelper.MalformedLine)
                    {
                        _errorLine = line.TrimEnd(_newLineChars);
                        _errorLineNumber = currentLineNumber;
                        throw new Exception(string.Format(Strings.CannotParseWithDelimiters, currentLineNumber));
                    }
                    if (endHelper.FieldFinished)
                    {
                        field = endHelper.Field;
                        index = endHelper.Index + endHelper.DelimiterLength;
                    }
                    else
                    {
                        // We may have an embedded line end character, so grab next line
                        do
                        {
                            int endOfLine = line.Length;
                            string newLine = ReadNextDataLine();
                            if (newLine == null)
                            {
                                _errorLine = line.TrimEnd(_newLineChars);
                                _errorLineNumber = currentLineNumber;
                                throw new Exception(string.Format(Strings.CannotParseWithDelimiters, currentLineNumber));
                            }
                            if (line.Length + newLine.Length > _maxLineSize)
                            {
                                _errorLine = line.TrimEnd(_newLineChars);
                                _errorLineNumber = currentLineNumber;
                                throw new Exception(string.Format(Strings.LineExceedsMaxLineSize, currentLineNumber));
                            }
                            line += newLine;
                            lineEndIndex = GetEndOfLineIndex(line);
                            endHelper.BuildField(line, endOfLine);
                            if (endHelper.MalformedLine)
                            {
                                _errorLine = line.TrimEnd(_newLineChars);
                                _errorLineNumber = currentLineNumber;
                                throw new Exception(string.Format(Strings.CannotParseWithDelimiters, currentLineNumber));
                            }
                        }
                        while (!endHelper.FieldFinished);
                        field = endHelper.Field;
                        index = endHelper.Index + endHelper.DelimiterLength;
                    }
                    if (_trimWhiteSpace)
                    {
                        field = field.Trim();
                    }
                    Fields.Add(field);
                    continue;
                }
 
                // Find the next delimiter
                Match delimiterMatch = _delimiterRegex.Match(line, index);
                if (delimiterMatch.Success)
                {
                    field = line.Substring(index, delimiterMatch.Index - index);
                    if (_trimWhiteSpace)
                    {
                        field = field.Trim();
                    }
                    Fields.Add(field);
                    index = delimiterMatch.Index + delimiterMatch.Length;
                    continue;
                }
                field = line.Substring(index).TrimEnd(_newLineChars);
                if (_trimWhiteSpace)
                {
                    field = field.Trim();
                }
                Fields.Add(field);
                break;
            }
            return Fields.ToArray();
        }
 
        private string[] ParseFixedWidthLine()
        {
            Debug.Assert(_fieldWidths != null, "No field widths");
            string line = ReadNextDataLine();
            if (line == null)
            {
                return null;
            }
            line = line.TrimEnd(_newLineChars);
            StringInfo lineInfo = new StringInfo(line);
            ValidateFixedWidthLine(lineInfo, _lineNumber - 1);
            int index = 0;
            int length = _fieldWidths.Length;
            string[] Fields = new string[length];
            for (int i = 0; i < length; i++)
            {
                Fields[i] = GetFixedWidthField(lineInfo, index, _fieldWidths[i]);
                index += _fieldWidths[i];
            }
            return Fields;
        }
 
        private string GetFixedWidthField(StringInfo line, int index, int fieldLength)
        {
            string field = (fieldLength > 0) ? line.SubstringByTextElements(index, fieldLength) : ((index < line.LengthInTextElements) ? line.SubstringByTextElements(index).TrimEnd(_newLineChars) : string.Empty);
            if (_trimWhiteSpace)
            {
                return field.Trim();
            }
            return field;
        }
 
        private int GetEndOfLineIndex(string line)
        {
            Debug.Assert(line != null, "We are parsing null");
            int length = line.Length;
            Debug.Assert(length > 0, "A blank line shouldn't be parsed");
            if (length == 1)
            {
                Debug.Assert(!line[0].Equals('\r') && !line[0].Equals('\n'), "A blank line shouldn't be parsed");
                return length;
            }
            checked
            {
                if (line[length - 2].Equals('\r') || line[length - 2].Equals('\n'))
                {
                    return length - 2;
                }
                if (line[length - 1].Equals('\r') || line[length - 1].Equals('\n'))
                {
                    return length - 1;
                }
                return length;
            }
        }
 
        private void ValidateFixedWidthLine(StringInfo line, long lineNumber)
        {
            Debug.Assert(line != null, "No Line sent");
            if (line.LengthInTextElements < _lineLength)
            {
                _errorLine = line.String;
                _errorLineNumber = checked(_lineNumber - 1);
                throw new Exception(string.Format(Strings.CannotParseWithFieldWidths, lineNumber));
            }
        }
 
        private void ValidateFieldWidths()
        {
            if (_fieldWidths == null)
            {
                throw new InvalidOperationException(Strings.NullFieldWidths);
            }
            if (_fieldWidths.Length == 0)
            {
                throw new InvalidOperationException(Strings.EmptyFieldWidths);
            }
            checked
            {
                int widthBound = _fieldWidths.Length - 1;
                _lineLength = 0;
                int num = widthBound - 1;
                for (int i = 0; i <= num; i++)
                {
                    Debug.Assert(_fieldWidths[i] > 0, "Bad field width, this should have been caught on input");
                    _lineLength += _fieldWidths[i];
                }
                if (_fieldWidths[widthBound] > 0)
                {
                    _lineLength += _fieldWidths[widthBound];
                }
            }
        }
 
        private void ValidateFieldWidthsOnInput(int[] widths)
        {
            Debug.Assert(widths != null, "There are no field widths");
            int bound = widths.Length - 1;
            for (int i = 0; i <= bound - 1; i++)
            {
                if (widths[i] < 1)
                {
                    throw new ArgumentException(Strings.InvalidFieldWidths);
                }
            }
        }
 
        private void ValidateAndEscapeDelimiters()
        {
            if (_delimiters == null)
            {
                throw new Exception(Strings.NullDelimiters);
            }
            if (_delimiters.Length == 0)
            {
                throw new Exception(Strings.EmptyDelimiters);
            }
            int length = _delimiters.Length;
            StringBuilder builder = new StringBuilder();
            StringBuilder quoteBuilder = new StringBuilder();
            quoteBuilder.Append(EndQuotePattern + "(");
            for (int i = 0; i <= length - 1; i++)
            {
                if (_delimiters[i] != null)
                {
                    if (_hasFieldsEnclosedInQuotes && _delimiters[i].IndexOf('"') > -1)
                    {
                        throw new Exception(Strings.IllegalQuoteDelimiter);
                    }
                    string escapedDelimiter = Regex.Escape(_delimiters[i]);
                    builder.Append(escapedDelimiter + "|");
                    quoteBuilder.Append(escapedDelimiter + "|");
                }
                else
                {
                    Debug.Fail("Delimiter element is empty. This should have been caught on input");
                }
            }
            _spaceChars = WhitespaceCharacters;
            _delimiterRegex = new Regex(builder.ToString(0, builder.Length - 1), (RegexOptions)512);
            builder.Append("\r|\n");
            _delimiterWithEndCharsRegex = new Regex(builder.ToString(), (RegexOptions)512);
            quoteBuilder.Append("\r|\n)|\"$");
        }
 
        private void ValidateReadyToRead()
        {
            if (!(_needPropertyCheck || ArrayHasChanged()))
            {
                return;
            }
            switch (_textFieldType)
            {
                case FieldType.Delimited:
                    ValidateAndEscapeDelimiters();
                    break;
                case FieldType.FixedWidth:
                    ValidateFieldWidths();
                    break;
                default:
                    Debug.Fail("Unknown TextFieldType");
                    break;
            }
            if (_commentTokens != null)
            {
                string[] commentTokens = _commentTokens;
                foreach (string token in commentTokens)
                {
                    if (token != string.Empty && (_hasFieldsEnclosedInQuotes && (_textFieldType == FieldType.Delimited)) && string.Compare(token.Trim(), "\"", StringComparison.Ordinal) == 0)
                    {
                        throw new Exception(Strings.IllegalQuoteDelimiter);
                    }
                }
            }
            _needPropertyCheck = false;
        }
 
        private void ValidateDelimiters(string[] delimiterArray)
        {
            if (delimiterArray == null)
            {
                return;
            }
            foreach (string delimiter in delimiterArray)
            {
                if (delimiter == string.Empty)
                {
                    throw new Exception(Strings.EmptyDelimiters);
                }
                if (delimiter.IndexOfAny(_newLineChars) > -1)
                {
                    throw new Exception(Strings.DelimiterCannotBeNewlineChar);
                }
            }
        }
 
        private bool ArrayHasChanged()
        {
            int lowerBound = 0;
            int upperBound = 0;
            switch (_textFieldType)
            {
                case FieldType.Delimited:
                    {
                        Debug.Assert(((_delimitersCopy == null) && (_delimiters == null)) || ((_delimitersCopy != null) && (_delimiters != null)), "Delimiters and copy are not both Nothing or both not Nothing");
                        if (_delimiters == null)
                        {
                            return false;
                        }
                        lowerBound = _delimitersCopy.GetLowerBound(0);
                        upperBound = _delimitersCopy.GetUpperBound(0);
                        int num3 = lowerBound;
                        int num4 = upperBound;
                        for (int i = num3; i <= num4; i++)
                        {
                            if (_delimiters[i] != _delimitersCopy[i])
                            {
                                return true;
                            }
                        }
                        break;
                    }
                case FieldType.FixedWidth:
                    {
                        Debug.Assert(((_fieldWidthsCopy == null) && (_fieldWidths == null)) || ((_fieldWidthsCopy != null) && (_fieldWidths != null)), "FieldWidths and copy are not both Nothing or both not Nothing");
                        if (_fieldWidths == null)
                        {
                            return false;
                        }
                        lowerBound = _fieldWidthsCopy.GetLowerBound(0);
                        upperBound = _fieldWidthsCopy.GetUpperBound(0);
                        int num = lowerBound;
                        int num2 = upperBound;
                        for (int j = num; j <= num2; j++)
                        {
                            if (_fieldWidths[j] != _fieldWidthsCopy[j])
                            {
                                return true;
                            }
                        }
                        break;
                    }
                default:
                    Debug.Fail("Unknown TextFieldType");
                    break;
            }
            return false;
        }
 
        private void CheckCommentTokensForWhitespace(string[] tokens)
        {
            if (tokens == null)
            {
                return;
            }
            foreach (string token in tokens)
            {
                if (token.Length == 1 && char.IsWhiteSpace(token[0]))
                {
                    throw new Exception(Strings.CommentTokenCannotContainWhitespace);
                }
            }
        }
 
        private bool CharacterIsInDelimiter(char testCharacter)
        {
            Debug.Assert(_delimiters != null, "No delimiters set!");
            string[] delimiters = _delimiters;
            foreach (string delimiter in delimiters)
            {
                if (delimiter.IndexOf(testCharacter) > -1)
                {
                    return true;
                }
            }
            return false;
        }
    }
}