diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index e19a5f39..f971eb1e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -884,7 +884,7 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) _textParser.ValidateToken(TokenId.StringLiteral); var text = _textParser.CurrentToken.Text; - var parsedStringValue = StringParser.ParseString(_textParser.CurrentToken.Text); + var parsedStringValue = StringParser.ParseString(_textParser.CurrentToken.Text, _textParser.CurrentToken.Pos); if (_textParser.CurrentToken.Text[0] == '\'') { @@ -916,7 +916,9 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) _textParser.NextToken(); } - parsedStringValue = StringParser.ParseStringAndReplaceDoubleQuotes(text, _textParser.CurrentToken.Pos); + parsedStringValue = _parsingConfig.StringLiteralParsing == StringLiteralParsingType.ReplaceTwoDoubleQuotesByASingleDoubleQuote ? + StringParser.ParseStringAndReplaceTwoDoubleQuotesByASingleDoubleQuote(text, _textParser.CurrentToken.Pos) : + StringParser.ParseString(text, _textParser.CurrentToken.Pos); return _constantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); } diff --git a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs index 18dcb2b2..93d2f019 100644 --- a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs @@ -10,10 +10,10 @@ namespace System.Linq.Dynamic.Core.Parser; /// internal static class StringParser { - private const string Pattern = @""""""; - private const string Replacement = "\""; + private const string TwoDoubleQuotes = "\"\""; + private const string SingleDoubleQuote = "\""; - public static string ParseString(string s, int pos = default) + internal static string ParseString(string s, int pos = default) { if (s == null || s.Length < 2) { @@ -41,20 +41,20 @@ public static string ParseString(string s, int pos = default) } } - public static string ParseStringAndReplaceDoubleQuotes(string s, int pos) + internal static string ParseStringAndReplaceTwoDoubleQuotesByASingleDoubleQuote(string input, int position) { - return ReplaceDoubleQuotes(ParseString(s, pos), pos); + return ReplaceTwoDoubleQuotesByASingleDoubleQuote(ParseString(input, position), position); } - private static string ReplaceDoubleQuotes(string s, int pos) + private static string ReplaceTwoDoubleQuotesByASingleDoubleQuote(string input, int position) { try { - return Regex.Replace(s, Pattern, Replacement); + return Regex.Replace(input, TwoDoubleQuotes, SingleDoubleQuote); } catch (Exception ex) { - throw new ParseException(ex.Message, pos, ex); + throw new ParseException(ex.Message, position, ex); } } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index ec3a164a..27c5530e 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -240,4 +240,10 @@ public IQueryableAnalyzer QueryableAnalyzer /// Caches constant expressions to enhance performance. Periodic cleanup is performed to manage cache size, governed by this configuration. /// public CacheConfig? ConstantExpressionCacheConfig { get; set; } + + /// + /// Defines the type of string literal parsing that will be performed. + /// Default value is StringLiteralParsingType.Default. + /// + public StringLiteralParsingType StringLiteralParsing { get; set; } = StringLiteralParsingType.Default; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/StringLiteralParsingType.cs b/src/System.Linq.Dynamic.Core/StringLiteralParsingType.cs new file mode 100644 index 00000000..1edbe2fb --- /dev/null +++ b/src/System.Linq.Dynamic.Core/StringLiteralParsingType.cs @@ -0,0 +1,18 @@ +namespace System.Linq.Dynamic.Core; + +/// +/// Defines the types of string literal parsing that can be performed. +/// +public enum StringLiteralParsingType : byte +{ + /// + /// Represents the default string literal parsing type. + /// [Default] + /// + Default = 0, + + /// + /// Represents a string literal parsing type where two consecutive double quotes are replaced by a single double quote. + /// + ReplaceTwoDoubleQuotesByASingleDoubleQuote = 1 +} diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 2c535fb7..6e1bcadf 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -894,6 +894,21 @@ public void DynamicExpressionParser_ParseLambda_StringLiteralStartEmbeddedQuote_ Assert.Equal("\"\"test\"", rightValue); } + [Theory] // #786 + [InlineData("Escaped", "\"{\\\"PropertyA\\\":\\\"\\\"}\"")] + [InlineData("Verbatim", @"""{\""PropertyA\"":\""\""}""")] + // [InlineData("Raw", """"{\"PropertyA\":\"\"}"""")] // TODO : does not work ??? + public void DynamicExpressionParser_ParseLambda_StringLiteral_EscapedJson(string _, string expression) + { + // Act + var result = DynamicExpressionParser + .ParseLambda(typeof(object), expression) + .Compile() + .DynamicInvoke(); + + result.Should().Be("{\"PropertyA\":\"\"}"); + } + [Fact] public void DynamicExpressionParser_ParseLambda_StringLiteral_MissingClosingQuote() { @@ -1468,7 +1483,10 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio resultIncome.Should().Be("Income == 5"); // Act : string - var expressionTextUserName = "StaticHelper.Filter(\"UserName == \"\"x\"\"\")"; + // Replace " with \" + // Replace \" with \\\" + var _ = StaticHelper.Filter("UserName == \"x\" "); + var expressionTextUserName = "StaticHelper.Filter(\"UserName == \\\"x\\\"\")"; var lambdaUserName = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextUserName, user); var funcUserName = (Expression>)lambdaUserName; @@ -1480,7 +1498,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio } [Fact] - public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression1String() + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpressionString() { // Arrange var config = new ParsingConfig @@ -1490,31 +1508,12 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexEx var user = new User(); - // Act - var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = 5"""", """"""""))"", """"))"; - var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); - var func = (Expression>)lambda; - - var compile = func.Compile(); - var result = (bool?)compile.DynamicInvoke(user); - - // Assert - result.Should().Be(false); - } - - [Fact] - public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression2String() - { - // Arrange - var config = new ParsingConfig - { - CustomTypeProvider = new TestCustomTypeProvider() - }; - - var user = new User(); + // Replace " with \" + // Replace \" with \\\" + var _ = StaticHelper.In(Guid.NewGuid(), StaticHelper.SubSelect("Identity", "LegalPerson", "StaticHelper.In(ParentId, StaticHelper.SubSelect( \"LegalPersonId\", \"PointSiteTD\", \"Identity = 5\", \"\")) ", "")); + var expressionText = "StaticHelper.In(Id, StaticHelper.SubSelect(\"Identity\", \"LegalPerson\", \"StaticHelper.In(ParentId, StaticHelper.SubSelect(\\\"LegalPersonId\\\", \\\"PointSiteTD\\\", \\\"Identity = 5\\\", \\\"\\\"))\", \"\"))"; // Act - var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = "" + StaticHelper.ToExpressionString(StaticHelper.Get(""CurrentPlace""), 2) + """""", """"""""))"", """"))"; var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); var func = (Expression>)lambda; diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index cc8f782d..689462e1 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -93,6 +93,7 @@ public void StringParser_Parse_SingleQuotedString(string input, string expectedR [InlineData("\"\\\"\\\"\"", "\"\"")] [InlineData("\"AB YZ 19 \uD800\udc05 \u00e4\"", "AB YZ 19 \uD800\udc05 \u00e4")] [InlineData("\"\\\\\\\\192.168.1.1\\\\audio\\\\new\"", "\\\\192.168.1.1\\audio\\new")] + [InlineData("\"{\\\"PropertyA\\\":\\\"\\\"}\"", @"{""PropertyA"":""""}")] // #786 public void StringParser_Parse_DoubleQuotedString(string input, string expectedResult) { // Act diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs index 7bfd3b46..69fc5ce3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs @@ -37,7 +37,7 @@ public static StaticHelperSqlExpression SubSelect(string columnName, string obje CustomTypeProvider = new TestCustomTypeProvider() }; - expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); // Failed Here! + expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); } return new StaticHelperSqlExpression