Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH committed Apr 5, 2024
1 parent ac5c81a commit 5b6e84c
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 36 deletions.
6 changes: 4 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ private AnyOf<Expression, Type> 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] == '\'')
{
Expand Down Expand Up @@ -916,7 +916,9 @@ private AnyOf<Expression, Type> 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);
}
Expand Down
16 changes: 8 additions & 8 deletions src/System.Linq.Dynamic.Core/Parser/StringParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ namespace System.Linq.Dynamic.Core.Parser;
/// </summary>
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)
{
Expand Down Expand Up @@ -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);
}
}
}
6 changes: 6 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public CacheConfig? ConstantExpressionCacheConfig { get; set; }

/// <summary>
/// Defines the type of string literal parsing that will be performed.
/// Default value is <c>StringLiteralParsingType.Default</c>.
/// </summary>
public StringLiteralParsingType StringLiteralParsing { get; set; } = StringLiteralParsingType.Default;
}
18 changes: 18 additions & 0 deletions src/System.Linq.Dynamic.Core/StringLiteralParsingType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace System.Linq.Dynamic.Core;

/// <summary>
/// Defines the types of string literal parsing that can be performed.
/// </summary>
public enum StringLiteralParsingType : byte
{
/// <summary>
/// Represents the default string literal parsing type.
/// [Default]
/// </summary>
Default = 0,

/// <summary>
/// Represents a string literal parsing type where two consecutive double quotes are replaced by a single double quote.
/// </summary>
ReplaceTwoDoubleQuotesByASingleDoubleQuote = 1
}
49 changes: 24 additions & 25 deletions test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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<Func<User, string>>)lambdaUserName;

Expand All @@ -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
Expand All @@ -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<Func<User, bool>>)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<Func<User, bool>>)lambda;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static StaticHelperSqlExpression SubSelect(string columnName, string obje
CustomTypeProvider = new TestCustomTypeProvider()
};

expFilter = DynamicExpressionParser.ParseLambda<User, bool>(config, true, filter); // Failed Here!
expFilter = DynamicExpressionParser.ParseLambda<User, bool>(config, true, filter);
}

return new StaticHelperSqlExpression
Expand Down

0 comments on commit 5b6e84c

Please sign in to comment.