public static RoutePattern Parse(string pattern) { if (pattern == null) { throw new ArgumentNullException(nameof(pattern)); } var trimmedPattern = TrimPrefix(pattern); var context = new TemplateParserContext(trimmedPattern); var segments = new List <RoutePatternPathSegment>(); while (context.MoveNext()) { var i = context.Index; 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 (!ParseSegment(context, segments)) { throw new RoutePatternException(pattern, context.Error); } // 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 InvalidProgramException("Infinite loop in the parser. This is a bug."); } } if (IsAllValid(context, segments)) { var builder = RoutePatternBuilder.Create(pattern); for (var i = 0; i < segments.Count; i++) { builder.PathSegments.Add(segments[i]); } return(builder.Build()); } else { throw new RoutePatternException(pattern, context.Error); } }
private static bool ParseSegment(TemplateParserContext context, List <RoutePatternPathSegment> segments) { Debug.Assert(context != null); Debug.Assert(segments != null); var parts = new List <RoutePatternPart>(); while (true) { var i = context.Index; 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" 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)) { return(false); } } if (context.Current == Separator || context.AtEnd()) { // We've reached the end of the segment break; } if (context.Index <= i) { throw new InvalidProgramException("Infinite loop in the parser. This is a bug."); } } if (IsSegmentValid(context, parts)) { segments.Add(new RoutePatternPathSegment(null, parts.ToArray())); return(true); } else { return(false); } }
private static bool ParseLiteral(TemplateParserContext context, List <RoutePatternPart> parts) { context.Mark(); while (true) { if (context.Current == Separator) { // End of the segment break; } 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.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); } } if (!context.MoveNext()) { break; } } var encoded = context.Capture(); var decoded = encoded.Replace("}}", "}").Replace("{{", "{"); if (IsValidLiteral(context, decoded)) { parts.Add(RoutePatternPart.CreateLiteralFromText(encoded, decoded)); return(true); } else { return(false); } }
private static bool ParseParameter(TemplateParserContext context, List <RoutePatternPart> parts) { Debug.Assert(context.Current == OpenBrace); context.Mark(); context.MoveNext(); while (true) { if (context.Current == OpenBrace) { // 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 { // 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; } } if (!context.MoveNext()) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } } var text = context.Capture(); if (text == "{}") { context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty); return(false); } var inside = text.Substring(1, text.Length - 2); var decoded = inside.Replace("}}", "}").Replace("{{", "{"); // At this point, we need to parse the raw name for inline constraint, // default values and optional parameters. var templatePart = InlineRouteParameterParser.ParseRouteParameter(text, decoded); // See #475 - this is here because InlineRouteParameterParser can't return errors if (decoded.StartsWith("*") && decoded.EndsWith("?")) { context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional; return(false); } if (templatePart.IsOptional && templatePart.DefaultValue != 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); } var parameterName = templatePart.Name; if (IsValidParameterName(context, parameterName)) { parts.Add(templatePart); return(true); } else { return(false); } }