diff options
Diffstat (limited to 'src/Http/Routing/src/Patterns/RoutePatternParser.cs')
-rw-r--r-- | src/Http/Routing/src/Patterns/RoutePatternParser.cs | 809 |
1 files changed, 404 insertions, 405 deletions
diff --git a/src/Http/Routing/src/Patterns/RoutePatternParser.cs b/src/Http/Routing/src/Patterns/RoutePatternParser.cs index 26e9f69b4a..8b3e4760ec 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternParser.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternParser.cs @@ -8,568 +8,567 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -namespace Microsoft.AspNetCore.Routing.Patterns +namespace Microsoft.AspNetCore.Routing.Patterns; + +internal static class RoutePatternParser { - internal static class RoutePatternParser + private const char Separator = '/'; + private const char OpenBrace = '{'; + private const char CloseBrace = '}'; + private const char QuestionMark = '?'; + private const char Asterisk = '*'; + private const string PeriodString = "."; + + internal static readonly char[] InvalidParameterNameChars = new char[] { - private const char Separator = '/'; - private const char OpenBrace = '{'; - private const char CloseBrace = '}'; - private const char QuestionMark = '?'; - private const char Asterisk = '*'; - private const string PeriodString = "."; - - internal static readonly char[] InvalidParameterNameChars = new char[] - { Separator, OpenBrace, CloseBrace, QuestionMark, Asterisk - }; + }; - public static RoutePattern Parse(string pattern) + public static RoutePattern Parse(string pattern) + { + if (pattern == null) { - if (pattern == null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - var trimmedPattern = TrimPrefix(pattern); - - var context = new Context(trimmedPattern); - var segments = new List<RoutePatternPathSegment>(); - - while (context.MoveNext()) - { - var i = context.Index; + throw new ArgumentNullException(nameof(pattern)); + } - if (context.Current == Separator) - { - // If we get here is means that there's a consecutive '/' character. - // Templates don't start with a '/' and parsing a segment consumes the separator. - throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators); - } + var trimmedPattern = TrimPrefix(pattern); - if (!ParseSegment(context, segments)) - { - throw new RoutePatternException(pattern, context.Error); - } + var context = new Context(trimmedPattern); + var segments = new List<RoutePatternPathSegment>(); - // A successful parse should always result in us being at the end or at a separator. - Debug.Assert(context.AtEnd() || context.Current == Separator); + while (context.MoveNext()) + { + var i = context.Index; - if (context.Index <= i) - { - // This shouldn't happen, but we want to crash if it does. - var message = "Infinite loop detected in the parser. Please open an issue."; - throw new InvalidProgramException(message); - } + if (context.Current == Separator) + { + // If we get here is means that there's a consecutive '/' character. + // Templates don't start with a '/' and parsing a segment consumes the separator. + throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators); } - if (IsAllValid(context, segments)) + if (!ParseSegment(context, segments)) { - return RoutePatternFactory.Pattern(pattern, segments); + throw new RoutePatternException(pattern, context.Error); } - else + + // A successful parse should always result in us being at the end or at a separator. + Debug.Assert(context.AtEnd() || context.Current == Separator); + + if (context.Index <= i) { - throw new RoutePatternException(pattern, context.Error); + // This shouldn't happen, but we want to crash if it does. + var message = "Infinite loop detected in the parser. Please open an issue."; + throw new InvalidProgramException(message); } } - private static bool ParseSegment(Context context, List<RoutePatternPathSegment> segments) + if (IsAllValid(context, segments)) + { + return RoutePatternFactory.Pattern(pattern, segments); + } + else { - Debug.Assert(context != null); - Debug.Assert(segments != null); + throw new RoutePatternException(pattern, context.Error); + } + } + + private static bool ParseSegment(Context context, List<RoutePatternPathSegment> segments) + { + Debug.Assert(context != null); + Debug.Assert(segments != null); - var parts = new List<RoutePatternPart>(); + var parts = new List<RoutePatternPart>(); + + while (true) + { + var i = context.Index; - while (true) + if (context.Current == OpenBrace) { - var i = context.Index; + if (!context.MoveNext()) + { + // This is a dangling open-brace, which is not allowed + context.Error = Resources.TemplateRoute_MismatchedParameter; + return false; + } if (context.Current == OpenBrace) { - if (!context.MoveNext()) + // This is an 'escaped' brace in a literal, like "{{foo" + context.Back(); + if (!ParseLiteral(context, parts)) { - // This is a dangling open-brace, which is not allowed - context.Error = Resources.TemplateRoute_MismatchedParameter; return false; } - - if (context.Current == OpenBrace) - { - // This is an 'escaped' brace in a literal, like "{{foo" - context.Back(); - if (!ParseLiteral(context, parts)) - { - return false; - } - } - else - { - // This is a parameter - context.Back(); - if (!ParseParameter(context, parts)) - { - return false; - } - } } else { - if (!ParseLiteral(context, parts)) + // This is a parameter + context.Back(); + if (!ParseParameter(context, parts)) { return false; } } - - if (context.Current == Separator || context.AtEnd()) - { - // We've reached the end of the segment - break; - } - - if (context.Index <= i) + } + else + { + if (!ParseLiteral(context, parts)) { - // This shouldn't happen, but we want to crash if it does. - var message = "Infinite loop detected in the parser. Please open an issue."; - throw new InvalidProgramException(message); + return false; } } - if (IsSegmentValid(context, parts)) + if (context.Current == Separator || context.AtEnd()) { - segments.Add(new RoutePatternPathSegment(parts)); - return true; + // We've reached the end of the segment + break; } - else + + if (context.Index <= i) { - return false; + // This shouldn't happen, but we want to crash if it does. + var message = "Infinite loop detected in the parser. Please open an issue."; + throw new InvalidProgramException(message); } } - private static bool ParseParameter(Context context, List<RoutePatternPart> parts) + if (IsSegmentValid(context, parts)) { - Debug.Assert(context.Current == OpenBrace); - context.Mark(); + segments.Add(new RoutePatternPathSegment(parts)); + return true; + } + else + { + return false; + } + } + + private static bool ParseParameter(Context context, List<RoutePatternPart> parts) + { + Debug.Assert(context.Current == OpenBrace); + context.Mark(); - context.MoveNext(); + context.MoveNext(); - while (true) + while (true) + { + if (context.Current == OpenBrace) { - if (context.Current == OpenBrace) + // This is an open brace inside of a parameter, it has to be escaped + if (context.MoveNext()) { - // This is an open brace inside of a parameter, it has to be escaped - if (context.MoveNext()) - { - if (context.Current != OpenBrace) - { - // If we see something like "{p1:regex(^\d{3", we will come here. - context.Error = Resources.TemplateRoute_UnescapedBrace; - return false; - } - } - else + if (context.Current != OpenBrace) { - // This is a dangling open-brace, which is not allowed - // Example: "{p1:regex(^\d{" - context.Error = Resources.TemplateRoute_MismatchedParameter; + // If we see something like "{p1:regex(^\d{3", we will come here. + context.Error = Resources.TemplateRoute_UnescapedBrace; return false; } } - else if (context.Current == CloseBrace) - { - // When we encounter Closed brace here, it either means end of the parameter or it is a closed - // brace in the parameter, in that case it needs to be escaped. - // Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter - if (!context.MoveNext()) - { - // This is the end of the string -and we have a valid parameter - break; - } - - if (context.Current == CloseBrace) - { - // This is an 'escaped' brace in a parameter name - } - else - { - // This is the end of the parameter - break; - } - } - - if (!context.MoveNext()) + else { // This is a dangling open-brace, which is not allowed + // Example: "{p1:regex(^\d{" context.Error = Resources.TemplateRoute_MismatchedParameter; return false; } } + else if (context.Current == CloseBrace) + { + // When we encounter Closed brace here, it either means end of the parameter or it is a closed + // brace in the parameter, in that case it needs to be escaped. + // Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter + if (!context.MoveNext()) + { + // This is the end of the string -and we have a valid parameter + break; + } + + if (context.Current == CloseBrace) + { + // This is an 'escaped' brace in a parameter name + } + else + { + // This is the end of the parameter + break; + } + } - var text = context.Capture(); - if (text == "{}") + if (!context.MoveNext()) { - context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty); + // This is a dangling open-brace, which is not allowed + context.Error = Resources.TemplateRoute_MismatchedParameter; return false; } + } - var inside = text.Substring(1, text.Length - 2); - var decoded = inside.Replace("}}", "}").Replace("{{", "{"); + var text = context.Capture(); + if (text == "{}") + { + context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty); + return false; + } - // At this point, we need to parse the raw name for inline constraint, - // default values and optional parameters. - var templatePart = RouteParameterParser.ParseRouteParameter(decoded); + var inside = text.Substring(1, text.Length - 2); + var decoded = inside.Replace("}}", "}").Replace("{{", "{"); - // See #475 - this is here because InlineRouteParameterParser can't return errors - if (decoded.StartsWith("*", StringComparison.Ordinal) && decoded.EndsWith("?", StringComparison.Ordinal)) - { - context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional; - return false; - } + // At this point, we need to parse the raw name for inline constraint, + // default values and optional parameters. + var templatePart = RouteParameterParser.ParseRouteParameter(decoded); - if (templatePart.IsOptional && templatePart.Default != null) - { - // Cannot be optional and have a default value. - // The only way to declare an optional parameter is to have a ? at the end, - // hence we cannot have both default value and optional parameter within the template. - // A workaround is to add it as a separate entry in the defaults argument. - context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue; - return false; - } + // See #475 - this is here because InlineRouteParameterParser can't return errors + if (decoded.StartsWith("*", StringComparison.Ordinal) && decoded.EndsWith("?", StringComparison.Ordinal)) + { + context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional; + return false; + } - var parameterName = templatePart.Name; - if (IsValidParameterName(context, parameterName)) - { - parts.Add(templatePart); - return true; - } - else - { - return false; - } + if (templatePart.IsOptional && templatePart.Default != null) + { + // Cannot be optional and have a default value. + // The only way to declare an optional parameter is to have a ? at the end, + // hence we cannot have both default value and optional parameter within the template. + // A workaround is to add it as a separate entry in the defaults argument. + context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue; + return false; } - private static bool ParseLiteral(Context context, List<RoutePatternPart> parts) + var parameterName = templatePart.Name; + if (IsValidParameterName(context, parameterName)) { - context.Mark(); + parts.Add(templatePart); + return true; + } + else + { + return false; + } + } + + private static bool ParseLiteral(Context context, List<RoutePatternPart> parts) + { + context.Mark(); - while (true) + while (true) + { + if (context.Current == Separator) { - if (context.Current == Separator) + // End of the segment + break; + } + else if (context.Current == OpenBrace) + { + if (!context.MoveNext()) { - // End of the segment - break; + // This is a dangling open-brace, which is not allowed + context.Error = Resources.TemplateRoute_MismatchedParameter; + return false; } - else if (context.Current == OpenBrace) - { - if (!context.MoveNext()) - { - // This is a dangling open-brace, which is not allowed - context.Error = Resources.TemplateRoute_MismatchedParameter; - return false; - } - if (context.Current == OpenBrace) - { - // This is an 'escaped' brace in a literal, like "{{foo" - keep going. - } - else - { - // We've just seen the start of a parameter, so back up. - context.Back(); - break; - } - } - else if (context.Current == CloseBrace) + if (context.Current == OpenBrace) { - if (!context.MoveNext()) - { - // This is a dangling close-brace, which is not allowed - context.Error = Resources.TemplateRoute_MismatchedParameter; - return false; - } - - if (context.Current == CloseBrace) - { - // This is an 'escaped' brace in a literal, like "{{foo" - keep going. - } - else - { - // This is an unbalanced close-brace, which is not allowed - context.Error = Resources.TemplateRoute_MismatchedParameter; - return false; - } + // This is an 'escaped' brace in a literal, like "{{foo" - keep going. } - - if (!context.MoveNext()) + else { + // We've just seen the start of a parameter, so back up. + context.Back(); break; } } - - var encoded = context.Capture(); - var decoded = encoded.Replace("}}", "}").Replace("{{", "{"); - if (IsValidLiteral(context, decoded)) + else if (context.Current == CloseBrace) { - parts.Add(RoutePatternFactory.LiteralPart(decoded)); - return true; + if (!context.MoveNext()) + { + // This is a dangling close-brace, which is not allowed + context.Error = Resources.TemplateRoute_MismatchedParameter; + return false; + } + + if (context.Current == CloseBrace) + { + // This is an 'escaped' brace in a literal, like "{{foo" - keep going. + } + else + { + // This is an unbalanced close-brace, which is not allowed + context.Error = Resources.TemplateRoute_MismatchedParameter; + return false; + } } - else + + if (!context.MoveNext()) { - return false; + break; } } - private static bool IsAllValid(Context context, List<RoutePatternPathSegment> segments) + var encoded = context.Capture(); + var decoded = encoded.Replace("}}", "}").Replace("{{", "{"); + if (IsValidLiteral(context, decoded)) { - // A catch-all parameter must be the last part of the last segment - for (var i = 0; i < segments.Count; i++) - { - var segment = segments[i]; - for (var j = 0; j < segment.Parts.Count; j++) - { - var part = segment.Parts[j]; - if (part is RoutePatternParameterPart parameter - && parameter.IsCatchAll && - (i != segments.Count - 1 || j != segment.Parts.Count - 1)) - { - context.Error = Resources.TemplateRoute_CatchAllMustBeLast; - return false; - } - } - } - + parts.Add(RoutePatternFactory.LiteralPart(decoded)); return true; } + else + { + return false; + } + } - private static bool IsSegmentValid(Context context, List<RoutePatternPart> parts) + private static bool IsAllValid(Context context, List<RoutePatternPathSegment> segments) + { + // A catch-all parameter must be the last part of the last segment + for (var i = 0; i < segments.Count; i++) { - // If a segment has multiple parts, then it can't contain a catch all. - for (var i = 0; i < parts.Count; i++) + var segment = segments[i]; + for (var j = 0; j < segment.Parts.Count; j++) { - var part = parts[i]; - if (part is RoutePatternParameterPart parameter && parameter.IsCatchAll && parts.Count > 1) + var part = segment.Parts[j]; + if (part is RoutePatternParameterPart parameter + && parameter.IsCatchAll && + (i != segments.Count - 1 || j != segment.Parts.Count - 1)) { - context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment; + context.Error = Resources.TemplateRoute_CatchAllMustBeLast; return false; } } + } - // if a segment has multiple parts, then only the last one parameter can be optional - // if it is following a optional separator. - for (var i = 0; i < parts.Count; i++) + return true; + } + + private static bool IsSegmentValid(Context context, List<RoutePatternPart> parts) + { + // If a segment has multiple parts, then it can't contain a catch all. + for (var i = 0; i < parts.Count; i++) + { + var part = parts[i]; + if (part is RoutePatternParameterPart parameter && parameter.IsCatchAll && parts.Count > 1) { - var part = parts[i]; + context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment; + return false; + } + } + + // if a segment has multiple parts, then only the last one parameter can be optional + // if it is following a optional separator. + for (var i = 0; i < parts.Count; i++) + { + var part = parts[i]; - if (part is RoutePatternParameterPart parameter && parameter.IsOptional && parts.Count > 1) + if (part is RoutePatternParameterPart parameter && parameter.IsOptional && parts.Count > 1) + { + // This optional parameter is the last part in the segment + if (i == parts.Count - 1) { - // This optional parameter is the last part in the segment - if (i == parts.Count - 1) + var previousPart = parts[i - 1]; + + if (!previousPart.IsLiteral && !previousPart.IsSeparator) { - var previousPart = parts[i - 1]; - - if (!previousPart.IsLiteral && !previousPart.IsSeparator) - { - // The optional parameter is preceded by something that is not a literal or separator - // Example of error message: - // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded - // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter. - context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( - RoutePatternPathSegment.DebuggerToString(parts), - parameter.Name, - parts[i - 1].DebuggerToString()); - - return false; - } - else if (previousPart is RoutePatternLiteralPart literal && literal.Content != PeriodString) - { - // The optional parameter is preceded by a literal other than period. - // Example of error message: - // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded - // by an invalid segment '-'. Only a period (.) can precede an optional parameter. - context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( - RoutePatternPathSegment.DebuggerToString(parts), - parameter.Name, - parts[i - 1].DebuggerToString()); - - return false; - } - - parts[i - 1] = RoutePatternFactory.SeparatorPart(((RoutePatternLiteralPart)previousPart).Content); + // The optional parameter is preceded by something that is not a literal or separator + // Example of error message: + // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded + // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter. + context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( + RoutePatternPathSegment.DebuggerToString(parts), + parameter.Name, + parts[i - 1].DebuggerToString()); + + return false; } - else + else if (previousPart is RoutePatternLiteralPart literal && literal.Content != PeriodString) { - // This optional parameter is not the last one in the segment - // Example: - // An optional parameter must be at the end of the segment. In the segment '{RouteValue?})', - // optional parameter 'RouteValue' is followed by ')' - context.Error = Resources.FormatTemplateRoute_OptionalParameterHasTobeTheLast( + // The optional parameter is preceded by a literal other than period. + // Example of error message: + // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded + // by an invalid segment '-'. Only a period (.) can precede an optional parameter. + context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod( RoutePatternPathSegment.DebuggerToString(parts), parameter.Name, - parts[i + 1].DebuggerToString()); + parts[i - 1].DebuggerToString()); return false; } - } - } - // A segment cannot contain two consecutive parameters - var isLastSegmentParameter = false; - for (var i = 0; i < parts.Count; i++) - { - var part = parts[i]; - if (part.IsParameter && isLastSegmentParameter) + parts[i - 1] = RoutePatternFactory.SeparatorPart(((RoutePatternLiteralPart)previousPart).Content); + } + else { - context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; + // This optional parameter is not the last one in the segment + // Example: + // An optional parameter must be at the end of the segment. In the segment '{RouteValue?})', + // optional parameter 'RouteValue' is followed by ')' + context.Error = Resources.FormatTemplateRoute_OptionalParameterHasTobeTheLast( + RoutePatternPathSegment.DebuggerToString(parts), + parameter.Name, + parts[i + 1].DebuggerToString()); + return false; } - - isLastSegmentParameter = part.IsParameter; } - - return true; } - private static bool IsValidParameterName(Context context, string parameterName) + // A segment cannot contain two consecutive parameters + var isLastSegmentParameter = false; + for (var i = 0; i < parts.Count; i++) { - if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0) + var part = parts[i]; + if (part.IsParameter && isLastSegmentParameter) { - context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName); + context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return false; } - if (!context.ParameterNames.Add(parameterName)) - { - context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName); - return false; - } + isLastSegmentParameter = part.IsParameter; + } - return true; + return true; + } + + private static bool IsValidParameterName(Context context, string parameterName) + { + if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0) + { + context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName); + return false; } - private static bool IsValidLiteral(Context context, string literal) + if (!context.ParameterNames.Add(parameterName)) { - Debug.Assert(context != null); - Debug.Assert(literal != null); + context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName); + return false; + } - if (literal.IndexOf(QuestionMark) != -1) - { - context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal); - return false; - } + return true; + } - return true; + private static bool IsValidLiteral(Context context, string literal) + { + Debug.Assert(context != null); + Debug.Assert(literal != null); + + if (literal.IndexOf(QuestionMark) != -1) + { + context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal); + return false; } - private static string TrimPrefix(string routePattern) + return true; + } + + private static string TrimPrefix(string routePattern) + { + if (routePattern.StartsWith("~/", StringComparison.Ordinal)) { - if (routePattern.StartsWith("~/", StringComparison.Ordinal)) - { - return routePattern.Substring(2); - } - else if (routePattern.StartsWith("/", StringComparison.Ordinal)) - { - return routePattern.Substring(1); - } - else if (routePattern.StartsWith("~", StringComparison.Ordinal)) - { - throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate); - } - return routePattern; + return routePattern.Substring(2); + } + else if (routePattern.StartsWith("/", StringComparison.Ordinal)) + { + return routePattern.Substring(1); + } + else if (routePattern.StartsWith("~", StringComparison.Ordinal)) + { + throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate); } + return routePattern; + } + + [DebuggerDisplay("{DebuggerToString()}")] + private class Context + { + private readonly string _template; + private int _index; + private int? _mark; + + private readonly HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); - [DebuggerDisplay("{DebuggerToString()}")] - private class Context + public Context(string template) { - private readonly string _template; - private int _index; - private int? _mark; + Debug.Assert(template != null); + _template = template; - private readonly HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + _index = -1; + } - public Context(string template) - { - Debug.Assert(template != null); - _template = template; + public char Current + { + get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; } + } - _index = -1; - } + public int Index => _index; - public char Current - { - get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; } - } + public string Error + { + get; + set; + } - public int Index => _index; + public HashSet<string> ParameterNames + { + get { return _parameterNames; } + } - public string Error - { - get; - set; - } + public bool Back() + { + return --_index >= 0; + } - public HashSet<string> ParameterNames - { - get { return _parameterNames; } - } + public bool AtEnd() + { + return _index >= _template.Length; + } - public bool Back() - { - return --_index >= 0; - } + public bool MoveNext() + { + return ++_index < _template.Length; + } + + public void Mark() + { + Debug.Assert(_index >= 0); + + // Index is always the index of the character *past* Current - we want to 'mark' Current. + _mark = _index; + } - public bool AtEnd() + public string Capture() + { + if (_mark.HasValue) { - return _index >= _template.Length; + var value = _template.Substring(_mark.Value, _index - _mark.Value); + _mark = null; + return value; } - - public bool MoveNext() + else { - return ++_index < _template.Length; + return null; } + } - public void Mark() + private string DebuggerToString() + { + if (_index == -1) { - Debug.Assert(_index >= 0); - - // Index is always the index of the character *past* Current - we want to 'mark' Current. - _mark = _index; + return _template; } - - public string Capture() + else if (_mark.HasValue) { - if (_mark.HasValue) - { - var value = _template.Substring(_mark.Value, _index - _mark.Value); - _mark = null; - return value; - } - else - { - return null; - } + return _template.Substring(0, _mark.Value) + + "|" + + _template.Substring(_mark.Value, _index - _mark.Value) + + "|" + + _template.Substring(_index); } - - private string DebuggerToString() + else { - if (_index == -1) - { - return _template; - } - else if (_mark.HasValue) - { - return _template.Substring(0, _mark.Value) + - "|" + - _template.Substring(_mark.Value, _index - _mark.Value) + - "|" + - _template.Substring(_index); - } - else - { - return string.Concat(_template.Substring(0, _index), "|", _template.Substring(_index)); - } + return string.Concat(_template.Substring(0, _index), "|", _template.Substring(_index)); } } } |