// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.Activities.Debugger { using System.Collections.Generic; using System.IO; // internal partial class CharacterSpottingTextReader : TextReader { // These 'special characters' couple with the fact that we are working on XML. private const char StartAngleBracket = '<'; private const char EndAngleBracket = '>'; private const char SingleQuote = '\''; private const char DoubleQuote = '"'; private const char EndLine = '\n'; private const char CarriageReturn = '\r'; private TextReader underlyingReader; private int currentLine; private int currentPosition; private List startAngleBrackets; private List endAngleBrackets; private List singleQuotes; private List doubleQuotes; private List endLines; public CharacterSpottingTextReader(TextReader underlyingReader) { UnitTestUtility.Assert(underlyingReader != null, "underlyingReader should not be null and should be ensured by caller."); this.underlyingReader = underlyingReader; this.currentLine = 1; this.currentPosition = 1; this.startAngleBrackets = new List(); this.endAngleBrackets = new List(); this.singleQuotes = new List(); this.doubleQuotes = new List(); this.endLines = new List(); } // CurrentLocation consists of the current line number and the current position on the line. // // The current position is like a cursor moving along the line. For example, a string "abc" ending with "\r\n": // // abc\r\n // // the current position, depicted as | below, moves from char to char: // // |a|b|c|\r|\n // // When we are at the beginning of the line, the current position is 1. After we read the first char, // we advance the current position to 2, and so on: // // 1 2 3 4 // |a|b|c|\r|\n // // As we reach the end-of-line character on the line, which can be \r, \r\n or \n, we move to the next line and reset the current position to 1. private DocumentLocation CurrentLocation { get { return new DocumentLocation(this.currentLine, this.currentPosition); } } public override void Close() { this.underlyingReader.Close(); } public override int Peek() { // This character is not consider read, therefore we don't need to analyze this. return this.underlyingReader.Peek(); } public override int Read() { int result = this.underlyingReader.Read(); if (result != -1) { result = this.AnalyzeReadData((char)result); } return result; } internal DocumentLocation FindCharacterStrictlyAfter(char c, DocumentLocation afterLocation) { List locationList = this.GetLocationList(c); UnitTestUtility.Assert(locationList != null, "We should always find character for special characters only"); // Note that this 'nextLocation' may not represent a real document location (we could hit an end line character here so that there is no next line // position. This is merely used for the search algorithm below: DocumentLocation nextLocation = new DocumentLocation(afterLocation.LineNumber, new OneBasedCounter(afterLocation.LinePosition.Value + 1)); BinarySearchResult result = locationList.MyBinarySearch(nextLocation); if (result.IsFound) { // It is possible that the next location is a quote itself, or return nextLocation; } else if (result.IsNextIndexAvailable) { // Some other later position is the quote, or return locationList[result.NextIndex]; } else { // in the worst case no quote can be found. return null; } } internal DocumentLocation FindCharacterStrictlyBefore(char c, DocumentLocation documentLocation) { List locationList = this.GetLocationList(c); UnitTestUtility.Assert(locationList != null, "We should always find character for special characters only"); BinarySearchResult result = locationList.MyBinarySearch(documentLocation); if (result.IsFound) { if (result.FoundIndex > 0) { return locationList[result.FoundIndex - 1]; } else { return null; } } else if (result.IsNextIndexAvailable) { if (result.NextIndex > 0) { return locationList[result.NextIndex - 1]; } else { return null; } } else if (locationList.Count > 0) { return locationList[locationList.Count - 1]; } else { return null; } } private List GetLocationList(char c) { switch (c) { case StartAngleBracket: return this.startAngleBrackets; case EndAngleBracket: return this.endAngleBrackets; case SingleQuote: return this.singleQuotes; case DoubleQuote: return this.doubleQuotes; case EndLine: return this.endLines; default: return null; } } /// /// Process last character read, and canonicalize end line. /// /// The last character read by the underlying reader /// The last character processed private char AnalyzeReadData(char lastCharacterRead) { // XML specification requires end-of-line == '\n' or '\r' or "\r\n" // See http://www.w3.org/TR/2008/REC-xml-20081126/#sec-line-ends for details. if (lastCharacterRead == CarriageReturn) { // if reading \r and peek next char is \n, then process \n as well int nextChar = this.underlyingReader.Peek(); if (nextChar == EndLine) { lastCharacterRead = (char)this.underlyingReader.Read(); } } if (lastCharacterRead == EndLine || lastCharacterRead == CarriageReturn) { this.endLines.Add(this.CurrentLocation); this.currentLine++; this.currentPosition = 1; // according to XML spec, both \r\n and \r should be translated to \n return EndLine; } else { List locations = this.GetLocationList(lastCharacterRead); if (locations != null) { locations.Add(this.CurrentLocation); } this.currentPosition++; return lastCharacterRead; } } } }