// 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); } }
public void ToRoutePatternPathSegment() { // Arrange var literalPartA = RoutePatternFactory.LiteralPart("A"); var paramPartB = RoutePatternFactory.ParameterPart("B"); var paramPartC = RoutePatternFactory.ParameterPart("C"); var paramPartD = RoutePatternFactory.ParameterPart("D"); var separatorPartE = RoutePatternFactory.SeparatorPart("E"); var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB)); // Act var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment(); templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D")); templateSegment.Parts.RemoveAt(0); // Assert Assert.Equal(4, routePatternPathSegment.Parts.Count); Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[0]); Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[0]).Name); Assert.IsType <RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]); Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart)routePatternPathSegment.Parts[1]).Content); Assert.IsType <RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]); Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart)routePatternPathSegment.Parts[2]).Content); Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[3]); Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[3]).Name); }
// 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 ComputeMatchDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with 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); } }
// 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; } }
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)); } }
public static string ToTemplateSegmentString(this TemplateSegment templateSegment) => string.Join(string.Empty, templateSegment.Parts.Select(ToTemplatePartString));
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 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) { if (!segment.Parts[i - 1].IsLiteral) { // The optional parameter is preceded by something that is not a literal. // 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 = string.Format( Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod, segment.DebuggerToString(), part.Name, segment.Parts[i - 1].DebuggerToString()); return(false); } else if (segment.Parts[i - 1].Text != 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 = string.Format( Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod, segment.DebuggerToString(), part.Name, segment.Parts[i - 1].Text); return(false); } segment.Parts[i - 1].IsOptionalSeperator = true; } 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 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 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); }
// 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 ComputeMatchDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with 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; } }