/// <summary> /// Parses a literal. /// Precondition: lexer is at a literal token type: Boolean, DateTime, Decimal, Null, String, Int64, Integer, Double, Single, Guid, Binary. /// </summary> /// <param name="expressionLexer">The expression lexer.</param> /// <returns>The literal query token or null if something else was found.</returns> private static object TryParseLiteral(this ExpressionLexer expressionLexer) { Debug.Assert(expressionLexer.CurrentToken.Kind.IsLiteralType(), "TryParseLiteral called when not at a literal type token"); switch (expressionLexer.CurrentToken.Kind) { case ExpressionTokenKind.NullLiteral: return(ParseNullLiteral(expressionLexer)); case ExpressionTokenKind.BooleanLiteral: case ExpressionTokenKind.DecimalLiteral: case ExpressionTokenKind.StringLiteral: case ExpressionTokenKind.Int64Literal: case ExpressionTokenKind.IntegerLiteral: case ExpressionTokenKind.DoubleLiteral: case ExpressionTokenKind.SingleLiteral: case ExpressionTokenKind.GuidLiteral: case ExpressionTokenKind.BinaryLiteral: case ExpressionTokenKind.DateLiteral: case ExpressionTokenKind.DateTimeOffsetLiteral: case ExpressionTokenKind.DurationLiteral: case ExpressionTokenKind.GeographyLiteral: case ExpressionTokenKind.GeometryLiteral: case ExpressionTokenKind.QuotedLiteral: case ExpressionTokenKind.TimeOfDayLiteral: case ExpressionTokenKind.CustomTypeLiteral: return(ParseTypedLiteral(expressionLexer, expressionLexer.CurrentToken.GetLiteralEdmTypeReference())); default: return(null); } }
/// <summary> /// Constructor. /// </summary> /// <param name="maxDepth">The maximum depth of each part of the query - a recursion limit.</param> /// <param name="lexer">The ExpressionLexer containing text to be parsed.</param> internal UriQueryExpressionParser(int maxDepth, ExpressionLexer lexer) { Debug.Assert(maxDepth >= 0, "maxDepth >= 0"); Debug.Assert(lexer != null, "lexer != null"); this.maxDepth = maxDepth; this.lexer = lexer; }
// parses $compute query option. internal ComputeToken ParseCompute(string compute) { Debug.Assert(compute != null, "compute != null"); List <ComputeExpressionToken> transformationTokens = new List <ComputeExpressionToken>(); if (string.IsNullOrEmpty(compute)) { return(new ComputeToken(transformationTokens)); } this.recursionDepth = 0; this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(compute); while (true) { ComputeExpressionToken computed = this.ParseComputeExpression(); transformationTokens.Add(computed); if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) { break; } this.lexer.NextToken(); } this.lexer.ValidateToken(ExpressionTokenKind.End); return(new ComputeToken(transformationTokens)); }
/// <summary> /// Parses typed literals. /// </summary> /// <param name="expressionLexer">The expression lexer.</param> /// <param name="targetTypeReference">Expected type to be parsed.</param> /// <returns>The literal token produced by building the given literal.</returns> private static object ParseTypedLiteral(this ExpressionLexer expressionLexer, IEdmTypeReference targetTypeReference) { UriLiteralParsingException typeParsingException; object targetValue = DefaultUriLiteralParser.Instance.ParseUriStringToType(expressionLexer.CurrentToken.Text, targetTypeReference, out typeParsingException); if (targetValue == null) { string message; if (typeParsingException == null) { message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteral( targetTypeReference.FullName(), expressionLexer.CurrentToken.Text, expressionLexer.CurrentToken.Position, expressionLexer.ExpressionText); throw new ODataException(message); } else { message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteralWithReason( targetTypeReference.FullName(), expressionLexer.CurrentToken.Text, expressionLexer.CurrentToken.Position, expressionLexer.ExpressionText, typeParsingException.Message); throw new ODataException(message, typeParsingException); } } expressionLexer.NextToken(); return(targetValue); }
/// <summary> /// Create a new CountSegmentParser. /// </summary> /// <param name="lexer">Lexer positioned at a $count identifier.</param> /// <param name="parser">The UriQueryExpressionParser.</param> public CountSegmentParser(ExpressionLexer lexer, UriQueryExpressionParser parser) { ExceptionUtils.CheckArgumentNotNull(lexer, "lexer"); ExceptionUtils.CheckArgumentNotNull(parser, "parser"); this.lexer = lexer; this.parser = parser; }
/// <summary> /// Parses parameter alias into token. /// </summary> /// <param name="lexer">The lexer to use.</param> /// <returns>The parameter alias token.</returns> private static FunctionParameterAliasToken ParseParameterAlias(ExpressionLexer lexer) { Debug.Assert(lexer != null, "lexer != null"); FunctionParameterAliasToken ret = new FunctionParameterAliasToken(lexer.CurrentToken.Text); lexer.NextToken(); return(ret); }
/// <summary> /// Create a new FunctionCallParser. /// </summary> /// <param name="lexer">Lexer positioned at a function identifier.</param> /// <param name="parser">The UriQueryExpressionParser.</param> /// <param name="restoreStateIfFail">If set to true, catches any ODataException thrown while trying to parse function arguments.</param> public FunctionCallParser(ExpressionLexer lexer, UriQueryExpressionParser parser, bool restoreStateIfFail) { ExceptionUtils.CheckArgumentNotNull(lexer, "lexer"); ExceptionUtils.CheckArgumentNotNull(parser, "parser"); this.lexer = lexer; this.parser = parser; this.restoreStateIfFail = restoreStateIfFail; }
/// <summary> /// Parse an Identifier into the right QueryToken /// </summary> /// <param name="parameters">parameters passed in to the UriQueryExpressionParser</param> /// <param name="functionCallParser">Object to use to handle parsing function calls.</param> public IdentifierTokenizer(HashSet <string> parameters, IFunctionCallParser functionCallParser) { ExceptionUtils.CheckArgumentNotNull(parameters, "parameters"); ExceptionUtils.CheckArgumentNotNull(functionCallParser, "functionCallParser"); this.lexer = functionCallParser.Lexer; this.parameters = parameters; this.functionCallParser = functionCallParser; }
/// <summary> /// Parses null literals. /// </summary> /// <param name="expressionLexer">The expression lexer.</param> /// <returns>The literal token produced by building the given literal.</returns> private static object ParseNullLiteral(this ExpressionLexer expressionLexer) { Debug.Assert(expressionLexer.CurrentToken.Kind == ExpressionTokenKind.NullLiteral, "this.lexer.CurrentToken.InternalKind == ExpressionTokenKind.NullLiteral"); expressionLexer.NextToken(); ODataNullValue nullValue = new ODataNullValue(); return(nullValue); }
/// <summary> /// Parses null literals. /// </summary> /// <param name="lexer">The lexer to use.</param> /// <returns>The literal token produced by building the given literal.</returns> private static LiteralToken ParseNullLiteral(ExpressionLexer lexer) { Debug.Assert(lexer != null, "lexer != null"); Debug.Assert(lexer.CurrentToken.Kind == ExpressionTokenKind.NullLiteral, "this.lexer.CurrentToken.InternalKind == ExpressionTokenKind.NullLiteral"); LiteralToken result = new LiteralToken(null, lexer.CurrentToken.Text); lexer.NextToken(); return(result); }
/// <summary>Reads the next token, checks that it is a literal token type, converts to to a Common Language Runtime value as appropriate, and returns the value.</summary> /// <param name="expressionLexer">The expression lexer.</param> /// <returns>The value represented by the next token.</returns> internal static object ReadLiteralToken(this ExpressionLexer expressionLexer) { expressionLexer.NextToken(); if (expressionLexer.CurrentToken.Kind.IsLiteralType()) { return(TryParseLiteral(expressionLexer)); } throw new ODataException(ODataErrorStrings.ExpressionLexer_ExpectedLiteralToken(expressionLexer.CurrentToken.Text)); }
/// <summary> /// Parse expression text into Token. /// </summary> /// <param name="expressionText">The expression string to Parse.</param> /// <returns>The lexical token representing the expression text.</returns> internal QueryToken ParseSearch(string expressionText) { Debug.Assert(expressionText != null, "expressionText != null"); this.recursionDepth = 0; this.lexer = new SearchLexer(expressionText); QueryToken result = this.ParseExpression(); this.lexer.ValidateToken(ExpressionTokenKind.End); return(result); }
/// <summary> /// Parse expression text into Token. /// </summary> /// <param name="expressionText">The expression string to Parse.</param> /// <returns>The lexical token representing the expression text.</returns> internal QueryToken ParseExpressionText(string expressionText) { Debug.Assert(expressionText != null, "expressionText != null"); this.recursionDepth = 0; this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(expressionText); QueryToken result = this.ParseExpression(); this.lexer.ValidateToken(ExpressionTokenKind.End); return(result); }
internal IEnumerable <QueryToken> ParseApply(string apply) { Debug.Assert(apply != null, "apply != null"); List <QueryToken> transformationTokens = new List <QueryToken>(); if (string.IsNullOrEmpty(apply)) { return(transformationTokens); } this.recursionDepth = 0; this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(apply); while (true) { switch (this.lexer.CurrentToken.GetIdentifier()) { case ExpressionConstants.KeywordAggregate: transformationTokens.Add(ParseAggregate()); break; case ExpressionConstants.KeywordFilter: transformationTokens.Add(ParseApplyFilter()); break; case ExpressionConstants.KeywordGroupBy: transformationTokens.Add(ParseGroupBy()); break; case ExpressionConstants.KeywordCompute: transformationTokens.Add(ParseCompute()); break; default: throw ParseError(ODataErrorStrings.UriQueryExpressionParser_KeywordOrIdentifierExpected(supportedKeywords, this.lexer.CurrentToken.Position, this.lexer.ExpressionText)); } // '/' indicates there are more transformations if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Slash) { break; } this.lexer.NextToken(); } this.lexer.ValidateToken(ExpressionTokenKind.End); return(new ReadOnlyCollection <QueryToken>(transformationTokens)); }
/// <summary> /// Tries to parse a collection of function parameters for path. /// </summary> /// <param name="parenthesisExpression">The contents of the parentheses portion of the current path segment.</param> /// <param name="configuration">The ODataUriParserConfiguration to create a UriQueryExpressionParser.</param> /// <param name="splitParameters">The parameters if they were successfully split.</param> /// <returns>Whether the parameters could be split.</returns> internal static bool TrySplitOperationParameters(string parenthesisExpression, ODataUriParserConfiguration configuration, out ICollection <FunctionParameterToken> splitParameters) { ExpressionLexer lexer = new ExpressionLexer(parenthesisExpression, true /*moveToFirstToken*/, false /*useSemicolonDelimeter*/, true /*parsingFunctionParameters*/); UriQueryExpressionParser parser = new UriQueryExpressionParser(configuration.Settings.FilterLimit, lexer); var ret = parser.TrySplitOperationParameters(ExpressionTokenKind.End, out splitParameters); // check duplicate names if (splitParameters.Select(t => t.ParameterName).Distinct().Count() != splitParameters.Count) { throw new ODataException(ODataErrorStrings.FunctionCallParser_DuplicateParameterOrEntityKeyName); } return(ret); }
/// <summary> /// Parses a literal. /// </summary> /// <param name="lexer">The lexer to use.</param> /// <returns>The literal query token or null if something else was found.</returns> internal static LiteralToken TryParseLiteral(ExpressionLexer lexer) { Debug.Assert(lexer != null, "lexer != null"); switch (lexer.CurrentToken.Kind) { case ExpressionTokenKind.BooleanLiteral: case ExpressionTokenKind.DateLiteral: case ExpressionTokenKind.DecimalLiteral: case ExpressionTokenKind.StringLiteral: case ExpressionTokenKind.Int64Literal: case ExpressionTokenKind.IntegerLiteral: case ExpressionTokenKind.DoubleLiteral: case ExpressionTokenKind.SingleLiteral: case ExpressionTokenKind.GuidLiteral: case ExpressionTokenKind.BinaryLiteral: case ExpressionTokenKind.GeographyLiteral: case ExpressionTokenKind.GeometryLiteral: case ExpressionTokenKind.QuotedLiteral: case ExpressionTokenKind.DurationLiteral: case ExpressionTokenKind.TimeOfDayLiteral: case ExpressionTokenKind.DateTimeOffsetLiteral: case ExpressionTokenKind.CustomTypeLiteral: IEdmTypeReference literalEdmTypeReference = lexer.CurrentToken.GetLiteralEdmTypeReference(); // Why not using EdmTypeReference.FullName? (literalEdmTypeReference.FullName) string edmConstantName = GetEdmConstantNames(literalEdmTypeReference); return(ParseTypedLiteral(lexer, literalEdmTypeReference, edmConstantName)); case ExpressionTokenKind.BracedExpression: case ExpressionTokenKind.BracketedExpression: case ExpressionTokenKind.ParenthesesExpression: { LiteralToken result = new LiteralToken(lexer.CurrentToken.Text, lexer.CurrentToken.Text); lexer.NextToken(); return(result); } case ExpressionTokenKind.NullLiteral: return(ParseNullLiteral(lexer)); default: return(null); } }
/// <summary> /// Build the SelectOption strategy. /// TODO: Really should not take the clauseToParse here. Instead it should be provided with a call to ParseSelect() or ParseExpand(). /// </summary> /// <param name="clauseToParse">the clause to parse</param> /// <param name="maxRecursiveDepth">max recursive depth</param> /// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param> /// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param> public SelectExpandParser( string clauseToParse, int maxRecursiveDepth, bool enableCaseInsensitiveBuiltinIdentifier = false, bool enableNoDollarQueryOptions = false) { this.maxRecursiveDepth = maxRecursiveDepth; // Set max recursive depth for path, $filter, $orderby and $search to maxRecursiveDepth in case they were not be be specified. this.MaxPathDepth = maxRecursiveDepth; this.MaxFilterDepth = maxRecursiveDepth; this.MaxOrderByDepth = maxRecursiveDepth; this.MaxSearchDepth = maxRecursiveDepth; // Sets up our lexer. We don't turn useSemicolonDelimiter on since the parsing code for expand options, // which is the only thing that needs it, is in a different class that uses it's own lexer. this.lexer = clauseToParse != null ? new ExpressionLexer(clauseToParse, false /*moveToFirstToken*/, false /*useSemicolonDelimiter*/) : null; this.enableCaseInsensitiveBuiltinIdentifier = enableCaseInsensitiveBuiltinIdentifier; this.enableNoDollarQueryOptions = enableNoDollarQueryOptions; }
/// <summary> /// Parses typed literals. /// </summary> /// <param name="lexer">The lexer to use.</param> /// <param name="targetTypeReference">Expected type to be parsed.</param> /// <param name="targetTypeName">The EDM type name of the expected type to be parsed.</param> /// <returns>The literal token produced by building the given literal.</returns> private static LiteralToken ParseTypedLiteral(ExpressionLexer lexer, IEdmTypeReference targetTypeReference, string targetTypeName) { Debug.Assert(lexer != null, "lexer != null"); UriLiteralParsingException typeParsingException; object targetValue = DefaultUriLiteralParser.Instance.ParseUriStringToType(lexer.CurrentToken.Text, targetTypeReference, out typeParsingException); if (targetValue == null) { string message; if (typeParsingException == null) { message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteral( targetTypeName, lexer.CurrentToken.Text, lexer.CurrentToken.Position, lexer.ExpressionText); throw ParseError(message); } else { message = ODataErrorStrings.UriQueryExpressionParser_UnrecognizedLiteralWithReason( targetTypeName, lexer.CurrentToken.Text, lexer.CurrentToken.Position, lexer.ExpressionText, typeParsingException.Message); throw ParseError(message, typeParsingException); } } LiteralToken result = new LiteralToken(targetValue, lexer.CurrentToken.Text); lexer.NextToken(); return(result); }
/// <summary> /// Parse the complex/collection value in parameter alias. /// </summary> /// <param name="queryToken">The parsed token.</param> /// <param name="parameterType">The expected parameter type.</param> /// <param name="model">The model</param> /// <returns>Token with complex/collection value passed.</returns> private static QueryToken ParseComplexOrCollectionAlias(QueryToken queryToken, IEdmTypeReference parameterType, IEdmModel model) { LiteralToken valueToken = queryToken as LiteralToken; string valueStr; if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText)) { var lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression) { object result = valueStr; if (!parameterType.IsStructured() && !parameterType.IsStructuredCollectionType()) { result = ODataUriUtils.ConvertFromUriLiteral(valueStr, ODataVersion.V4, model, parameterType); } // For non-primitive type, we have to pass parameterType to LiteralToken, then to ConstantNode so the service can know what the type it is. return(new LiteralToken(result, valueToken.OriginalText, parameterType)); } } return(queryToken); }
/// <summary> /// Read a query option from the lexer. /// </summary> /// <returns>The query option as a string.</returns> internal static string ReadQueryOption(ExpressionLexer lexer) { if (lexer.CurrentToken.Kind != ExpressionTokenKind.Equal) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(lexer.ExpressionText)); } // get the full text from the current location onward // there could be literals like 'A string literal; tricky!' in there, so we need to be careful. // Also there could be more nested (...) expressions that we ignore until we recurse enough times to get there. string expressionText = lexer.AdvanceThroughExpandOption(); if (lexer.CurrentToken.Kind == ExpressionTokenKind.SemiColon) { // Move over the ';' separator lexer.NextToken(); return(expressionText); } // If there wasn't a semicolon, it MUST be the last option. We must be at ')' in this case lexer.ValidateToken(ExpressionTokenKind.CloseParen); return(expressionText); }
/// <summary> /// Parses the $orderby expression. /// </summary> /// <param name="orderBy">The $orderby expression string to parse.</param> /// <returns>The enumeraion of lexical tokens representing order by tokens.</returns> internal IEnumerable <OrderByToken> ParseOrderBy(string orderBy) { Debug.Assert(orderBy != null, "orderBy != null"); this.recursionDepth = 0; this.lexer = CreateLexerForFilterOrOrderByOrApplyExpression(orderBy); List <OrderByToken> orderByTokens = new List <OrderByToken>(); while (true) { QueryToken expression = this.ParseExpression(); bool ascending = true; if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending)) { this.lexer.NextToken(); } else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending)) { this.lexer.NextToken(); ascending = false; } OrderByToken orderByToken = new OrderByToken(expression, ascending ? OrderByDirection.Ascending : OrderByDirection.Descending); orderByTokens.Add(orderByToken); if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.Comma) { break; } this.lexer.NextToken(); } this.lexer.ValidateToken(ExpressionTokenKind.End); return(new ReadOnlyCollection <OrderByToken>(orderByTokens)); }
/// <summary> /// This is temp work around for $filter $orderby parameter expression which contains complex or collection /// like "Fully.Qualified.Namespace.CanMoveToAddresses(addresses=[{\"Street\":\"NE 24th St.\",\"City\":\"Redmond\"},{\"Street\":\"Pine St.\",\"City\":\"Seattle\"}])"; /// TODO: $filter $orderby parameter expression which contains nested complex or collection should NOT be supported in this way /// but should be parsed into token tree, and binded to node tree: parsedParameters.Select(p => this.bindMethod(p)); /// </summary> /// <param name="model">The model.</param> /// <param name="operation">IEdmFunction or IEdmOperation</param> /// <param name="parameterTokens">The tokens to bind.</param> /// <param name="enableCaseInsensitive">Whether to enable case-insensitive when resolving parameter name.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns>The FunctionParameterTokens with complex or collection values converted from string like "{...}", or "[..,..,..]".</returns> private static ICollection <FunctionParameterToken> HandleComplexOrCollectionParameterValueIfExists(IEdmModel model, IEdmOperation operation, ICollection <FunctionParameterToken> parameterTokens, bool enableCaseInsensitive, bool enableUriTemplateParsing = false) { ICollection <FunctionParameterToken> partiallyParsedParametersWithComplexOrCollection = new Collection <FunctionParameterToken>(); foreach (FunctionParameterToken paraToken in parameterTokens) { FunctionParameterToken funcParaToken; IEdmOperationParameter functionParameter = operation.FindParameter(paraToken.ParameterName); if (enableCaseInsensitive && functionParameter == null) { functionParameter = ODataUriResolver.ResolveOperationParameterNameCaseInsensitive(operation, paraToken.ParameterName); // The functionParameter can not be null here, else this method won't be called. funcParaToken = new FunctionParameterToken(functionParameter.Name, paraToken.ValueToken); } else { funcParaToken = paraToken; } FunctionParameterAliasToken aliasToken = funcParaToken.ValueToken as FunctionParameterAliasToken; if (aliasToken != null) { aliasToken.ExpectedParameterType = functionParameter.Type; } LiteralToken valueToken = funcParaToken.ValueToken as LiteralToken; string valueStr = null; if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText)) { ExpressionLexer lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/); if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression) { object result; UriTemplateExpression expression; if (enableUriTemplateParsing && UriTemplateParser.TryParseLiteral(lexer.CurrentToken.Text, functionParameter.Type, out expression)) { result = expression; } else if (!functionParameter.Type.IsStructured() && !functionParameter.Type.IsStructuredCollectionType()) { // ExpressionTokenKind.BracketedExpression means text like [1,2] // so now try convert it to collection type value: result = ODataUriUtils.ConvertFromUriLiteral(valueStr, ODataVersion.V4, model, functionParameter.Type); } else { // For complex & colleciton of complex directly return the raw string. partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken); continue; } LiteralToken newValueToken = new LiteralToken(result, valueToken.OriginalText); FunctionParameterToken newFuncParaToken = new FunctionParameterToken(funcParaToken.ParameterName, newValueToken); partiallyParsedParametersWithComplexOrCollection.Add(newFuncParaToken); continue; } } partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken); } return(partiallyParsedParametersWithComplexOrCollection); }
/// <summary> /// Building of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; ComputeToken computeOption = null; IEnumerable <QueryToken> applyOptions = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: // inner $filter filterOption = ParseInnerFilter(); break; case ExpressionConstants.QueryOptionOrderby: // inner $orderby orderByOptions = ParseInnerOrderBy(); break; case ExpressionConstants.QueryOptionTop: // inner $top topOption = ParseInnerTop(); break; case ExpressionConstants.QueryOptionSkip: // innner $skip skipOption = ParseInnerSkip(); break; case ExpressionConstants.QueryOptionCount: // inner $count countOption = ParseInnerCount(); break; case ExpressionConstants.QueryOptionSearch: // inner $search searchOption = ParseInnerSearch(); break; case ExpressionConstants.QueryOptionLevels: // inner $level levelsOption = ParseInnerLevel(); break; case ExpressionConstants.QueryOptionSelect: // inner $select selectOption = ParseInnerSelect(pathToken); break; case ExpressionConstants.QueryOptionExpand: // inner $expand expandOption = ParseInnerExpand(pathToken); break; case ExpressionConstants.QueryOptionCompute: // inner $compute computeOption = ParseInnerCompute(); break; case ExpressionConstants.QueryOptionApply: // inner $apply applyOptions = ParseInnerApply(); break; default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption, computeOption, applyOptions); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Building off a PathSegmentToken, continue parsing any select options (nested $filter, $expand, etc) /// to build up an SelectTermToken which fully represents the tree that makes up this select term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed select path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after a select option.</param> /// <returns>The select term token based on the path token, and all available select options.</returns> internal SelectTermToken BuildSelectTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ComputeToken computeOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingSelectOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: // inner $filter filterOption = ParseInnerFilter(); break; case ExpressionConstants.QueryOptionOrderby: // inner $orderby orderByOptions = ParseInnerOrderBy(); break; case ExpressionConstants.QueryOptionTop: // inner $top topOption = ParseInnerTop(); break; case ExpressionConstants.QueryOptionSkip: // innner $skip skipOption = ParseInnerSkip(); break; case ExpressionConstants.QueryOptionCount: // inner $count countOption = ParseInnerCount(); break; case ExpressionConstants.QueryOptionSearch: // inner $search searchOption = ParseInnerSearch(); break; case ExpressionConstants.QueryOptionSelect: // inner $select selectOption = ParseInnerSelect(pathToken); break; case ExpressionConstants.QueryOptionCompute: // inner $compute computeOption = ParseInnerCompute(); break; default: throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } return(new SelectTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, searchOption, selectOption, computeOption)); }
/// <summary> /// Constructs a term parser. /// </summary> /// <param name="lexer">Lexer to use for parsing the term. Should be position at the term to parse.</param> /// <param name="maxPathLength">Max length of a select or expand path.</param> /// <param name="isSelect">True if we are parsing select, false if we are parsing expand.</param> internal SelectExpandTermParser(ExpressionLexer lexer, int maxPathLength, bool isSelect) { this.lexer = lexer; this.maxPathLength = maxPathLength; this.isSelect = isSelect; }
/// <summary> /// Create a new FunctionCallParser. /// </summary> /// <param name="lexer">Lexer positioned at a function identifier.</param> /// <param name="parser">The UriQueryExpressionParser.</param> public FunctionCallParser(ExpressionLexer lexer, UriQueryExpressionParser parser) : this(lexer, parser, false /* restoreStateIfFail */) { }
/// <summary>Attempts to parse key values from the specified text.</summary> /// <param name='text'>Text to parse (not null).</param> /// <param name='instance'>After invocation, the parsed key instance.</param> /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param> /// <returns> /// true if the key instance was parsed; false if there was a /// syntactic error. /// </returns> /// <remarks> /// The returned instance contains only string values. To get typed values, a call to /// TryConvertValues is necessary. /// </remarks> private static bool TryParseFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing) { Debug.Assert(text != null, "text != null"); Dictionary <string, string> namedValues = null; List <string> positionalValues = null; // parse keys just like function parameters ExpressionLexer lexer = new ExpressionLexer(string.Concat("(", text, ")"), true, false); UriQueryExpressionParser exprParser = new UriQueryExpressionParser(ODataUriParserSettings.DefaultFilterLimit /* default limit for parsing key value */, lexer); var tmp = (new FunctionCallParser(lexer, exprParser)).ParseArgumentListOrEntityKeyList(); if (lexer.CurrentToken.Kind != ExpressionTokenKind.End) { instance = null; return(false); } if (tmp.Length == 0) { instance = Empty; return(true); } foreach (FunctionParameterToken t in tmp) { string valueText = null; LiteralToken literalToken = t.ValueToken as LiteralToken; if (literalToken != null) { valueText = literalToken.OriginalText; // disallow "{...}" if enableUriTemplateParsing is false (which could have been seen as valid function parameter, e.g. array notation) if (!enableUriTemplateParsing && UriTemplateParser.IsValidTemplateLiteral(valueText)) { instance = null; return(false); } } else { DottedIdentifierToken dottedIdentifierToken = t.ValueToken as DottedIdentifierToken; // for enum if (dottedIdentifierToken != null) { valueText = dottedIdentifierToken.Identifier; } } if (valueText != null) { if (t.ParameterName == null) { if (namedValues != null) { instance = null; // We cannot mix named and non-named values. return(false); } CreateIfNull(ref positionalValues); positionalValues.Add(valueText); } else { if (positionalValues != null) { instance = null; // We cannot mix named and non-named values. return(false); } CreateIfNull(ref namedValues); namedValues.Add(t.ParameterName, valueText); } } else { instance = null; return(false); } } instance = new SegmentArgumentParser(namedValues, positionalValues, false, enableUriTemplateParsing); return(true); }
/// <summary> /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: { // advance to the equal sign this.lexer.NextToken(); string filterText = this.ReadQueryOption(); UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier); filterOption = filterParser.ParseFilter(filterText); break; } case ExpressionConstants.QueryOptionOrderby: { // advance to the equal sign this.lexer.NextToken(); string orderByText = this.ReadQueryOption(); UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); orderByOptions = orderbyParser.ParseOrderBy(orderByText); break; } case ExpressionConstants.QueryOptionTop: { // advance to the equal sign this.lexer.NextToken(); string topText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long top; if (!long.TryParse(topText, out top) || top < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText)); } topOption = top; break; } case ExpressionConstants.QueryOptionSkip: { // advance to the equal sign this.lexer.NextToken(); string skipText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long skip; if (!long.TryParse(skipText, out skip) || skip < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText)); } skipOption = skip; break; } case ExpressionConstants.QueryOptionCount: { // advance to the equal sign this.lexer.NextToken(); string countText = this.ReadQueryOption(); switch (countText) { case ExpressionConstants.KeywordTrue: { countOption = true; break; } case ExpressionConstants.KeywordFalse: { countOption = false; break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText)); } } break; } case ExpressionConstants.QueryOptionLevels: { levelsOption = ResolveLevelOption(); break; } case ExpressionConstants.QueryOptionSearch: { // advance to the equal sign this.lexer.NextToken(); string searchText = this.ReadQueryOption(); SearchParser searchParser = new SearchParser(this.MaxSearchDepth); searchOption = searchParser.ParseSearch(searchText); break; } case ExpressionConstants.QueryOptionSelect: { // advance to the equal sign this.lexer.NextToken(); string selectText = this.ReadQueryOption(); SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier); selectOption = innerSelectParser.ParseSelect(); break; } case ExpressionConstants.QueryOptionExpand: { // advance to the equal sign this.lexer.NextToken(); string expandText = this.ReadQueryOption(); // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. IEdmStructuredType targetEntityType = null; if (this.resolver != null && this.parentEntityType != null) { var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty; // it is a navigation property, need to find the type. // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", // find navigation property Trips of Friends, then get Entity type of Trips. if (parentProperty != null) { targetEntityType = parentProperty.ToEntityType(); } } SelectExpandParser innerExpandParser = new SelectExpandParser( resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, this.enableCaseInsensitiveBuiltinIdentifier, this.enableNoDollarQueryOptions); expandOption = innerExpandParser.ParseExpand(); break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Constructor. /// </summary> /// <param name="maxDepth">The maximum depth of each part of the query - a recursion limit.</param> /// <param name="lexer">The ExpressionLexer containing text to be parsed.</param> internal UriQueryExpressionParser(int maxDepth, ExpressionLexer lexer) : this(maxDepth) { Debug.Assert(lexer != null, "lexer != null"); this.lexer = lexer; }