// Segments have the following order: // 5 - Literal segments // 4 - Multi-part segments && Constrained parameter segments // 3 - Unconstrained parameter segements // 2 - Constrained wildcard parameter segments // 1 - Unconstrained wildcard parameter segments private static int ComputeGenerationDigit(TemplateSegment segment) { if(segment.Parts.Count > 1) { return 4; } var part = segment.Parts[0]; if(part.IsLiteral) { return 5; } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 1 : 3; if (part.InlineConstraints != null && part.InlineConstraints.Any()) { digit++; } return digit; } }
// Segments have the following order: // 1 - Literal segments // 2 - Constrained parameter segments / Multi-part segments // 3 - Unconstrained parameter segments // 4 - Constrained wildcard parameter segments // 5 - Unconstrained wildcard parameter segments private static int ComputeDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments but before parameter segments return 2; } var part = segment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return 1; } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 5 : 3; // If there is a route constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (part.InlineConstraints != null && part.InlineConstraints.Any()) { digit--; } return digit; } }
private bool MatchComplexSegment( TemplateSegment routeSegment, string requestSegment, IReadOnlyDictionary <string, object> defaults, RouteValueDictionary values) { var indexOfLastSegment = routeSegment.Parts.Count - 1; // We match the request to the template starting at the rightmost parameter // If the last segment of template is optional, then request can match the // template with or without the last parameter. So we start with regular matching, // but if it doesn't match, we start with next to last parameter. Example: // Template: {p1}/{p2}.{p3?}. If the request is foo/bar.moo it will match right away // giving p3 value of moo. But if the request is foo/bar, we start matching from the // rightmost giving p3 the value of bar, then we end up not matching the segment. // In this case we start again from p2 to match the request and we succeed giving // the value bar to p2 if (routeSegment.Parts[indexOfLastSegment].IsOptional && routeSegment.Parts[indexOfLastSegment - 1].IsOptionalSeperator) { if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment)) { return(true); } else { if (requestSegment.EndsWith( routeSegment.Parts[indexOfLastSegment - 1].Text, StringComparison.OrdinalIgnoreCase)) { return(false); } return(MatchComplexSegmentCore( routeSegment, requestSegment, Defaults, values, indexOfLastSegment - 2)); } } else { return(MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment)); } }
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment) { // If a segment has multiple parts, then it can't contain a catch all. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1) { context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment; return(false); } } // if a segment has multiple parts, then the parameters can't be optional for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1) { context.Error = Resources.TemplateRoute_CannotHaveOptionalParameterInMultiSegment; return(false); } } // A segment cannot containt two consecutive parameters var isLastSegmentParameter = false; for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && isLastSegmentParameter) { context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return(false); } isLastSegmentParameter = part.IsParameter; } return(true); }
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment) { context.Mark(); string encoded; while (true) { if (context.Current == Separator) { encoded = context.Capture(); context.Back(); break; } else if (context.Current == OpenBrace) { if (!context.Next()) { // 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 and return context.Back(); encoded = context.Capture(); context.Back(); break; } } else if (context.Current == CloseBrace) { if (!context.Next()) { // 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.Next()) { encoded = context.Capture(); break; } } var decoded = encoded.Replace("}}", "}").Replace("{{", "{"); if (IsValidLiteral(context, decoded)) { segment.Parts.Add(TemplatePart.CreateLiteral(decoded)); return true; } else { return false; } }
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment) { context.Mark(); while (true) { if (context.Current == OpenBrace) { // This is an open brace inside of a parameter, it has to be escaped if (context.Next()) { 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.Next()) { // This is the end of the string -and we have a valid parameter context.Back(); break; } if (context.Current == CloseBrace) { // This is an 'escaped' brace in a parameter name } else { // This is the end of the parameter context.Back(); break; } } if (!context.Next()) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return false; } } var rawParameter = context.Capture(); var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{"); // At this point, we need to parse the raw name for inline constraint, // default values and optional parameters. var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded); if (templatePart.IsCatchAll && templatePart.IsOptional) { 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)) { segment.Parts.Add(templatePart); return true; } else { return false; } }
private bool MatchComplexSegment(TemplateSegment routeSegment, string requestSegment, IDictionary <string, object> defaults, RouteValueDictionary values) { Contract.Assert(routeSegment != null); Contract.Assert(routeSegment.Parts.Count > 1); // Find last literal segment and get its last index in the string var lastIndex = requestSegment.Length; var indexOfLastSegmentUsed = routeSegment.Parts.Count - 1; TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered while (indexOfLastSegmentUsed >= 0) { var newLastIndex = lastIndex; var part = routeSegment.Parts[indexOfLastSegmentUsed]; if (part.IsParameter) { // Hold on to the parameter so that we can fill it in when we locate the next literal parameterNeedsValue = part; } else { Contract.Assert(part.IsLiteral); lastLiteral = part; var startIndex = lastIndex - 1; // If we have a pending parameter subsegment, we must leave at least one character for that if (parameterNeedsValue != null) { startIndex--; } if (startIndex < 0) { return(false); } var indexOfLiteral = requestSegment.LastIndexOf(part.Text, startIndex, StringComparison.OrdinalIgnoreCase); if (indexOfLiteral == -1) { // If we couldn't find this literal index, this segment cannot match return(false); } // If the first subsegment is a literal, it must match at the right-most extent of the request URI. // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/". // This check is related to the check we do at the very end of this function. if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1)) { if ((indexOfLiteral + part.Text.Length) != requestSegment.Length) { return(false); } } newLastIndex = indexOfLiteral; } if ((parameterNeedsValue != null) && (((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0))) { // If we have a pending parameter that needs a value, grab that value int parameterStartIndex; int parameterTextLength; if (lastLiteral == null) { if (indexOfLastSegmentUsed == 0) { parameterStartIndex = 0; } else { parameterStartIndex = newLastIndex; Contract.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above"); } parameterTextLength = lastIndex; } else { // If we're getting a value for a parameter that is somewhere in the middle of the segment if ((indexOfLastSegmentUsed == 0) && (part.IsParameter)) { parameterStartIndex = 0; parameterTextLength = lastIndex; } else { parameterStartIndex = newLastIndex + lastLiteral.Text.Length; parameterTextLength = lastIndex - parameterStartIndex; } } var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength); if (string.IsNullOrEmpty(parameterValueString)) { // If we're here that means we have a segment that contains multiple sub-segments. // For these segments all parameters must have non-empty values. If the parameter // has an empty value it's not a match. return(false); } else { // If there's a value in the segment for this parameter, use the subsegment value values.Add(parameterNeedsValue.Name, parameterValueString); } parameterNeedsValue = null; lastLiteral = null; } lastIndex = newLastIndex; indexOfLastSegmentUsed--; } // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire* // request URI in order for it to be a match. // This check is related to the check we do earlier in this function for LiteralSubsegments. return((lastIndex == 0) || routeSegment.Parts[0].IsParameter); }
private static bool ParseSegment(TemplateParserContext context, List <TemplateSegment> segments) { Debug.Assert(context != null); Debug.Assert(segments != null); var segment = new TemplateSegment(); while (true) { if (context.Current == OpenBrace) { if (!context.Next()) { // 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, segment)) { return(false); } } else { // This is the inside of a parameter if (!ParseParameter(context, segment)) { return(false); } } } else if (context.Current == Separator) { // We've reached the end of the segment break; } else { if (!ParseLiteral(context, segment)) { return(false); } } if (!context.Next()) { // We've reached the end of the string break; } } if (IsSegmentValid(context, segment)) { segments.Add(segment); return(true); } else { return(false); } }
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment) { context.Mark(); string encoded; while (true) { if (context.Current == Separator) { encoded = context.Capture(); context.Back(); break; } else if (context.Current == OpenBrace) { if (!context.Next()) { // 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 and return context.Back(); encoded = context.Capture(); context.Back(); break; } } else if (context.Current == CloseBrace) { if (!context.Next()) { // 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.Next()) { encoded = context.Capture(); break; } } var decoded = encoded.Replace("}}", "}").Replace("{{", "}"); if (IsValidLiteral(context, decoded)) { segment.Parts.Add(TemplatePart.CreateLiteral(decoded)); return(true); } else { return(false); } }
private bool MatchComplexSegmentCore( TemplateSegment routeSegment, string requestSegment, IReadOnlyDictionary<string, object> defaults, RouteValueDictionary values, int indexOfLastSegmentUsed) { Debug.Assert(routeSegment != null); Debug.Assert(routeSegment.Parts.Count > 1); // Find last literal segment and get its last index in the string var lastIndex = requestSegment.Length; TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered var outValues = new RouteValueDictionary(); while (indexOfLastSegmentUsed >= 0) { var newLastIndex = lastIndex; var part = routeSegment.Parts[indexOfLastSegmentUsed]; if (part.IsParameter) { // Hold on to the parameter so that we can fill it in when we locate the next literal parameterNeedsValue = part; } else { Debug.Assert(part.IsLiteral); lastLiteral = part; var startIndex = lastIndex - 1; // If we have a pending parameter subsegment, we must leave at least one character for that if (parameterNeedsValue != null) { startIndex--; } if (startIndex < 0) { return false; } var indexOfLiteral = requestSegment.LastIndexOf( part.Text, startIndex, StringComparison.OrdinalIgnoreCase); if (indexOfLiteral == -1) { // If we couldn't find this literal index, this segment cannot match return false; } // If the first subsegment is a literal, it must match at the right-most extent of the request URI. // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/". // This check is related to the check we do at the very end of this function. if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1)) { if ((indexOfLiteral + part.Text.Length) != requestSegment.Length) { return false; } } newLastIndex = indexOfLiteral; } if ((parameterNeedsValue != null) && (((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0))) { // If we have a pending parameter that needs a value, grab that value int parameterStartIndex; int parameterTextLength; if (lastLiteral == null) { if (indexOfLastSegmentUsed == 0) { parameterStartIndex = 0; } else { parameterStartIndex = newLastIndex; Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above"); } parameterTextLength = lastIndex; } else { // If we're getting a value for a parameter that is somewhere in the middle of the segment if ((indexOfLastSegmentUsed == 0) && (part.IsParameter)) { parameterStartIndex = 0; parameterTextLength = lastIndex; } else { parameterStartIndex = newLastIndex + lastLiteral.Text.Length; parameterTextLength = lastIndex - parameterStartIndex; } } var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength); if (string.IsNullOrEmpty(parameterValueString)) { // If we're here that means we have a segment that contains multiple sub-segments. // For these segments all parameters must have non-empty values. If the parameter // has an empty value it's not a match. return false; } else { // If there's a value in the segment for this parameter, use the subsegment value outValues.Add(parameterNeedsValue.Name, parameterValueString); } parameterNeedsValue = null; lastLiteral = null; } lastIndex = newLastIndex; indexOfLastSegmentUsed--; } // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire* // request URI in order for it to be a match. // This check is related to the check we do earlier in this function for LiteralSubsegments. if (lastIndex == 0 || routeSegment.Parts[0].IsParameter) { foreach (var item in outValues) { values.Add(item.Key, item.Value); } return true; } return false; }
private bool MatchComplexSegment( TemplateSegment routeSegment, string requestSegment, IReadOnlyDictionary<string, object> defaults, RouteValueDictionary values) { var indexOfLastSegment = routeSegment.Parts.Count - 1; // We match the request to the template starting at the rightmost parameter // If the last segment of template is optional, then request can match the // template with or without the last parameter. So we start with regular matching, // but if it doesn't match, we start with next to last parameter. Example: // Template: {p1}/{p2}.{p3?}. If the request is foo/bar.moo it will match right away // giving p3 value of moo. But if the request is foo/bar, we start matching from the // rightmost giving p3 the value of bar, then we end up not matching the segment. // In this case we start again from p2 to match the request and we succeed giving // the value bar to p2 if (routeSegment.Parts[indexOfLastSegment].IsOptional && routeSegment.Parts[indexOfLastSegment - 1].IsOptionalSeperator) { if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment)) { return true; } else { if (requestSegment.EndsWith( routeSegment.Parts[indexOfLastSegment - 1].Text, StringComparison.OrdinalIgnoreCase)) { return false; } return MatchComplexSegmentCore( routeSegment, requestSegment, Defaults, values, indexOfLastSegment - 2); } } else { return MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment); } }
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment) { // If a segment has multiple parts, then it can't contain a catch all. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1) { 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 seperator. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1) { // This is the last part if (i == segment.Parts.Count - 1) { Debug.Assert(segment.Parts[i - 1].IsLiteral); if (segment.Parts[i - 1].Text == PeriodString) { segment.Parts[i - 1].IsOptionalSeperator = true; } else { context.Error = Resources.TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator; return(false); } } else { context.Error = Resources.TemplateRoute_CanHaveOnlyLastParameterOptional_IfFollowingOptionalSeperator; return(false); } } } // A segment cannot contain two consecutive parameters var isLastSegmentParameter = false; for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && isLastSegmentParameter) { context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return(false); } isLastSegmentParameter = part.IsParameter; } return(true); }
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment) { // If a segment has multiple parts, then it can't contain a catch all. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1) { 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 seperator. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1) { // This optional parameter is the last part in the segment if (i == segment.Parts.Count - 1) { Debug.Assert(segment.Parts[i - 1].IsLiteral); // the optional parameter is preceded by a period if (segment.Parts[i - 1].Text == PeriodString) { segment.Parts[i - 1].IsOptionalSeperator = true; } else { // The optional parameter is preceded by a literal other than period // Example of error message: // "In the complex segment {RouteValue}-{param?}, the optional parameter 'param'is preceded // by an invalid segment "-". Only valid literal to precede an optional parameter is a // period (.). context.Error = string.Format( Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod, segment.DebuggerToString(), part.Name, segment.Parts[i - 1].Text); return(false); } } else { // 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 ')' var nextPart = segment.Parts[i + 1]; var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text; context.Error = string.Format( Resources.TemplateRoute_OptionalParameterHasTobeTheLast, segment.DebuggerToString(), segment.Parts[i].Name, invalidPartText ); return(false); } } } // A segment cannot contain two consecutive parameters var isLastSegmentParameter = false; for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && isLastSegmentParameter) { context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return(false); } isLastSegmentParameter = part.IsParameter; } return(true); }
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment) { context.Mark(); while (true) { if (context.Current == OpenBrace) { // This is an open brace inside of a parameter, it has to be escaped if (context.Next()) { 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.Next()) { // This is the end of the string -and we have a valid parameter context.Back(); break; } if (context.Current == CloseBrace) { // This is an 'escaped' brace in a parameter name } else { // This is the end of the parameter context.Back(); break; } } if (!context.Next()) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } } var rawParameter = context.Capture(); var decoded = rawParameter.Replace("}}", "}").Replace("{{", "{"); // At this point, we need to parse the raw name for inline constraint, // default values and optional parameters. var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded); if (templatePart.IsCatchAll && templatePart.IsOptional) { 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)) { segment.Parts.Add(templatePart); return(true); } else { return(false); } }
private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment) { // If a segment has multiple parts, then it can't contain a catch all. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1) { 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 seperator. for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1) { // This optional parameter is the last part in the segment if (i == segment.Parts.Count - 1) { Debug.Assert(segment.Parts[i - 1].IsLiteral); // the optional parameter is preceded by a period if (segment.Parts[i - 1].Text == PeriodString) { segment.Parts[i - 1].IsOptionalSeperator = true; } else { // The optional parameter is preceded by a literal other than period // Example of error message: // "In the complex segment {RouteValue}-{param?}, the optional parameter 'param'is preceded // by an invalid segment "-". Only valid literal to precede an optional parameter is a // period (.). context.Error = string.Format( Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod, segment.DebuggerToString(), part.Name, segment.Parts[i - 1].Text); return false; } } else { // 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 ')' var nextPart = segment.Parts[i + 1]; var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text; context.Error = string.Format( Resources.TemplateRoute_OptionalParameterHasTobeTheLast, segment.DebuggerToString(), segment.Parts[i].Name, invalidPartText ); return false; } } } // A segment cannot contain two consecutive parameters var isLastSegmentParameter = false; for (var i = 0; i < segment.Parts.Count; i++) { var part = segment.Parts[i]; if (part.IsParameter && isLastSegmentParameter) { context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters; return false; } isLastSegmentParameter = part.IsParameter; } return true; }
private static bool ParseSegment(TemplateParserContext context, List<TemplateSegment> segments) { Debug.Assert(context != null); Debug.Assert(segments != null); var segment = new TemplateSegment(); while (true) { if (context.Current == OpenBrace) { if (!context.Next()) { // 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, segment)) { return false; } } else { // This is the inside of a parameter if (!ParseParameter(context, segment)) { return false; } } } else if (context.Current == Separator) { // We've reached the end of the segment break; } else { if (!ParseLiteral(context, segment)) { return false; } } if (!context.Next()) { // We've reached the end of the string break; } } if (IsSegmentValid(context, segment)) { segments.Add(segment); return true; } else { return false; } }
private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment) { context.Mark(); while (true) { if (context.Current == Separator) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } else if (context.Current == OpenBrace) { // If we see a '{' while parsing a parameter name it's invalid. We'll just accept it for now // and let the validation code for the name find it. } else if (context.Current == CloseBrace) { if (!context.Next()) { // This is the end of the string - and we have a valid parameter context.Back(); break; } if (context.Current == CloseBrace) { // This is an 'escaped' brace in a parameter name, which is not allowed. // We'll just accept it for now and let the validation code for the name find it. } else { // This is the end of the parameter context.Back(); break; } } if (!context.Next()) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } } var rawParameter = context.Capture(); // At this point, we need to parse the raw name for inline constraint, // default values and optional parameters. var templatePart = InlineRouteParameterParser.ParseRouteParameter(rawParameter); if (templatePart.IsCatchAll && templatePart.IsOptional) { 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)) { segment.Parts.Add(templatePart); return(true); } else { return(false); } }