diff --git a/Sieve/Models/FilterTerm.cs b/Sieve/Models/FilterTerm.cs index f2fd6ee..800e3bd 100644 --- a/Sieve/Models/FilterTerm.cs +++ b/Sieve/Models/FilterTerm.cs @@ -6,43 +6,36 @@ namespace Sieve.Models { public class FilterTerm : IFilterTerm, IEquatable { - public FilterTerm() { } - - private const string EscapedPipePattern = @"(?=", - "<=", - ">", - "<", - "@=", - "_=" - }; + private const string BackslashToEscape = @"\\"; + private const string OperatorsRegEx = @"(!@=\*|!_=\*|!=\*|!@=|!_=|==\*|@=\*|_=\*|==|!=|>=|<=|>|<|@=|_=)"; + private const string EscapeNegPatternForOper = @"(? t.Trim()).ToArray(); + var filterSplits = Regex.Split(value,EscapeNegPatternForOper).Select(t => t.Trim()).ToArray(); + Names = Regex.Split(filterSplits[0], EscapedPipePattern).Select(t => t.Trim()).ToArray(); - Values = filterSplits.Length > 1 - ? Regex.Split(filterSplits[1], EscapedPipePattern) + + if (filterSplits.Length > 2) + { + foreach (var match in Regex.Matches(filterSplits[2],EscapePosPatternForOper)) + { + var matchStr = match.ToString(); + filterSplits[2] = filterSplits[2].Replace('\\' + matchStr, matchStr); + } + + Values = Regex.Split(filterSplits[2], EscapedPipePattern) .Select(t => t.Replace(PipeToEscape, "|").Trim()) - .ToArray() - : null; - Operator = Array.Find(Operators, o => value.Contains(o)) ?? "=="; + .Select(t => t.Replace(BackslashToEscape, "\\").Trim()) + .ToArray(); + } + + Operator = Regex.Match(value,EscapeNegPatternForOper).Value; OperatorParsed = GetOperatorParsed(Operator); OperatorIsCaseInsensitive = Operator.EndsWith("*"); OperatorIsNegated = OperatorParsed != FilterOperator.NotEquals && Operator.StartsWith("!"); diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 866ece5..80b28ce 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -73,7 +73,7 @@ public General(ITestOutputHelper testOutputHelper) CategoryId = 2, TopComment = new Comment { Id = 1, Text = "D1" }, FeaturedComment = new Comment { Id = 7, Text = "D2" } - }, + } }.AsQueryable(); _comments = new List @@ -684,15 +684,116 @@ public void OrEscapedPipeValueFilteringWorks() DateCreated = DateTimeOffset.UtcNow.AddDays(-1), Text = "Here is | another comment" }, + new Comment + { + Id = 2, + DateCreated = DateTimeOffset.UtcNow.AddDays(-1), + Text = @"Here is \| another comment(1)" + } }.AsQueryable(); - var model = new SieveModel() + var model = new SieveModel { - Filters = "Text==Here is \\| a comment|Here is \\| another comment", + Filters = @"Text==Here is \| a comment|Here is \| another comment|Here is \\\| another comment(1)", }; var result = _processor.Apply(model, comments); - Assert.Equal(2, result.Count()); + Assert.Equal(3, result.Count()); + } + + [Theory] + [InlineData("CategoryId==1,(CategoryId|LikeCount)==50")] + [InlineData("(CategoryId|LikeCount)==50,CategoryId==1")] + public void CanFilterWithEscape(string filter) + { + var model = new SieveModel + { + Filters = filter + }; + + var result = _processor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); + + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } + + [Theory] + [InlineData(@"Title@=\\")] + public void CanFilterWithEscapedBackSlash(string filter) + { + var posts = new List + { + new Post + { + Id = 1, + Title = "E\\", + LikeCount = 4, + IsDraft = true, + CategoryId = 1, + TopComment = new Comment { Id = 1, Text = "E1" }, + FeaturedComment = new Comment { Id = 7, Text = "E2" } + } + }.AsQueryable(); + + var model = new SieveModel + { + Filters = filter + }; + + var result = _processor.Apply(model, posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); + + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } + + [Theory] + [InlineData(@"Title@=\== ")] + [InlineData(@"Title@=\!= ")] + [InlineData(@"Title@=\> ")] + [InlineData(@"Title@=\< ")] + [InlineData(@"Title@=\<= ")] + [InlineData(@"Title@=\>= ")] + [InlineData(@"Title@=\@= ")] + [InlineData(@"Title@=\_= ")] + [InlineData(@"Title@=!\@= ")] + [InlineData(@"Title@=!\_= ")] + [InlineData(@"Title@=\@=* ")] + [InlineData(@"Title@=\_=* ")] + [InlineData(@"Title@=\==* ")] + [InlineData(@"Title@=\!=* ")] + [InlineData(@"Title@=!\@=* ")] + public void CanFilterWithEscapedOperators(string filter) + { + var posts = new List + { + new Post + { + Id = 1, + Title = @"Operators: == != > < >= <= @= _= !@= !_= @=* _=* ==* !=* !@=* !_=* ", + LikeCount = 1, + IsDraft = true, + CategoryId = 1, + TopComment = new Comment { Id = 1, Text = "F1" }, + FeaturedComment = new Comment { Id = 7, Text = "F2" } + } + }.AsQueryable(); + + var model = new SieveModel + { + Filters = filter, + }; + + var result = _processor.Apply(model, posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); + + Assert.NotNull(entry); + Assert.Equal(1, resultCount); } + } }