mirror of
				https://github.com/Biarity/Sieve.git
				synced 2025-10-31 00:39:12 +01:00 
			
		
		
		
	This commit is contained in:
		| @@ -137,11 +137,12 @@ More formally: | |||||||
|         * You can also have multiple names (for OR logic) by enclosing them in brackets and using a pipe delimiter, eg. `(LikeCount|CommentCount)>10` asks if `LikeCount` or `CommentCount` is `>10` |         * You can also have multiple names (for OR logic) by enclosing them in brackets and using a pipe delimiter, eg. `(LikeCount|CommentCount)>10` asks if `LikeCount` or `CommentCount` is `>10` | ||||||
|     * `{Operator}` is one of the [Operators](#operators) |     * `{Operator}` is one of the [Operators](#operators) | ||||||
|     * `{Value}` is the value to use for filtering |     * `{Value}` is the value to use for filtering | ||||||
|  |         * You can also have multiple values (for OR logic) by using a pipe delimiter, eg. `Title@=new|hot` will return posts with titles that contain the text "`new`" or "`hot`" | ||||||
| * `page` is the number of page to return | * `page` is the number of page to return | ||||||
| * `pageSize` is the number of items returned per page  | * `pageSize` is the number of items returned per page  | ||||||
|  |  | ||||||
| Notes: | Notes: | ||||||
| * Don't forget to **remove commas (`,`), brackets (`(`, `)`), and pipes (`|`) from any `{Value}` fields** | * You can use backslashes to escape commas and pipes within value fields | ||||||
| * You can have spaces anywhere except *within* `{Name}` or `{Operator}` fields | * You can have spaces anywhere except *within* `{Name}` or `{Operator}` fields | ||||||
| * Here's a [good example on how to work with enumerables](https://github.com/Biarity/Sieve/issues/2) | * Here's a [good example on how to work with enumerables](https://github.com/Biarity/Sieve/issues/2) | ||||||
| * Another example on [how to do OR logic](https://github.com/Biarity/Sieve/issues/8) | * Another example on [how to do OR logic](https://github.com/Biarity/Sieve/issues/8) | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ EndProject | |||||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{904F25A9-5CBD-42AE-8422-ADAB98F4B4B7}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{904F25A9-5CBD-42AE-8422-ADAB98F4B4B7}" | ||||||
| 	ProjectSection(SolutionItems) = preProject | 	ProjectSection(SolutionItems) = preProject | ||||||
| 		.editorconfig = .editorconfig | 		.editorconfig = .editorconfig | ||||||
|  | 		README.md = README.md | ||||||
| 	EndProjectSection | 	EndProjectSection | ||||||
| EndProject | EndProject | ||||||
| Global | Global | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| namespace Sieve.Models | namespace Sieve.Models | ||||||
| { | { | ||||||
| @@ -7,6 +8,8 @@ namespace Sieve.Models | |||||||
|     { |     { | ||||||
|         public FilterTerm() { } |         public FilterTerm() { } | ||||||
|  |  | ||||||
|  |         private const string EscapedPipePattern = @"(?<!($|[^\\])(\\\\)*?\\)\|"; | ||||||
|  |  | ||||||
|         private static readonly string[] Operators = new string[] { |         private static readonly string[] Operators = new string[] { | ||||||
|                     "==*", |                     "==*", | ||||||
|                     "@=*", |                     "@=*", | ||||||
| @@ -25,9 +28,10 @@ namespace Sieve.Models | |||||||
|         { |         { | ||||||
|             set |             set | ||||||
|             { |             { | ||||||
|                 var filterSplits = value.Split(Operators, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()).ToArray(); |                 var filterSplits = value.Split(Operators, StringSplitOptions.RemoveEmptyEntries) | ||||||
|                 Names = filterSplits[0].Split('|').Select(t => t.Trim()).ToArray(); |                     .Select(t => t.Trim()).ToArray(); | ||||||
|                 Value = filterSplits.Length > 1 ? filterSplits[1] : null; |                 Names = Regex.Split(filterSplits[0], EscapedPipePattern).Select(t => t.Trim()).ToArray(); | ||||||
|  |                 Values = filterSplits.Length > 1 ? Regex.Split(filterSplits[1], EscapedPipePattern).Select(t => t.Trim()).ToArray() : null; | ||||||
|                 Operator = Array.Find(Operators, o => value.Contains(o)) ?? "=="; |                 Operator = Array.Find(Operators, o => value.Contains(o)) ?? "=="; | ||||||
|                 OperatorParsed = GetOperatorParsed(Operator); |                 OperatorParsed = GetOperatorParsed(Operator); | ||||||
|                 OperatorIsCaseInsensitive = Operator.Contains("*"); |                 OperatorIsCaseInsensitive = Operator.Contains("*"); | ||||||
| @@ -39,7 +43,7 @@ namespace Sieve.Models | |||||||
|  |  | ||||||
|         public FilterOperator OperatorParsed { get; private set; } |         public FilterOperator OperatorParsed { get; private set; } | ||||||
|  |  | ||||||
|         public string Value { get; private set; } |         public string[] Values { get; private set; } | ||||||
|  |  | ||||||
|         public string Operator { get; private set; } |         public string Operator { get; private set; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,6 @@ | |||||||
|         string Operator { get; } |         string Operator { get; } | ||||||
|         bool OperatorIsCaseInsensitive { get; } |         bool OperatorIsCaseInsensitive { get; } | ||||||
|         FilterOperator OperatorParsed { get; } |         FilterOperator OperatorParsed { get; } | ||||||
|         string Value { get; } |         string[] Values { get; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ using System.Collections.Generic; | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Runtime.Serialization; | using System.Runtime.Serialization; | ||||||
|  | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| namespace Sieve.Models | namespace Sieve.Models | ||||||
| { | { | ||||||
| @@ -13,6 +14,8 @@ namespace Sieve.Models | |||||||
|         where TFilterTerm : IFilterTerm, new() |         where TFilterTerm : IFilterTerm, new() | ||||||
|         where TSortTerm : ISortTerm, new() |         where TSortTerm : ISortTerm, new() | ||||||
|     { |     { | ||||||
|  |         private const string EscapedCommaPattern = @"(?<!($|[^\\])(\\\\)*?\\),"; | ||||||
|  |  | ||||||
|         [DataMember] |         [DataMember] | ||||||
|         public string Filters { get; set; } |         public string Filters { get; set; } | ||||||
|  |  | ||||||
| @@ -30,7 +33,7 @@ namespace Sieve.Models | |||||||
|             if (Filters != null) |             if (Filters != null) | ||||||
|             { |             { | ||||||
|                 var value = new List<TFilterTerm>(); |                 var value = new List<TFilterTerm>(); | ||||||
|                 foreach (var filter in Filters.Split(',')) |                 foreach (var filter in Regex.Split(Filters, EscapedCommaPattern)) | ||||||
|                 { |                 { | ||||||
|                     if (string.IsNullOrWhiteSpace(filter)) continue; |                     if (string.IsNullOrWhiteSpace(filter)) continue; | ||||||
|  |  | ||||||
| @@ -69,7 +72,7 @@ namespace Sieve.Models | |||||||
|             if (Sorts != null) |             if (Sorts != null) | ||||||
|             { |             { | ||||||
|                 var value = new List<TSortTerm>(); |                 var value = new List<TSortTerm>(); | ||||||
|                 foreach (var sort in Sorts.Split(',')) |                 foreach (var sort in Regex.Split(Sorts, EscapedCommaPattern)) | ||||||
|                 { |                 { | ||||||
|                     if (string.IsNullOrWhiteSpace(sort)) continue; |                     if (string.IsNullOrWhiteSpace(sort)) continue; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -180,33 +180,37 @@ namespace Sieve.Services | |||||||
|                     if (property != null) |                     if (property != null) | ||||||
|                     { |                     { | ||||||
|                         var converter = TypeDescriptor.GetConverter(property.PropertyType); |                         var converter = TypeDescriptor.GetConverter(property.PropertyType); | ||||||
|  |  | ||||||
|                         dynamic constantVal = converter.CanConvertFrom(typeof(string)) |  | ||||||
|                                                   ? converter.ConvertFrom(filterTerm.Value) |  | ||||||
|                                                   : Convert.ChangeType(filterTerm.Value, property.PropertyType); |  | ||||||
|  |  | ||||||
|                         Expression filterValue = GetClosureOverConstant(constantVal, property.PropertyType); |  | ||||||
|  |  | ||||||
|                         dynamic propertyValue = Expression.PropertyOrField(parameterExpression, property.Name); |                         dynamic propertyValue = Expression.PropertyOrField(parameterExpression, property.Name); | ||||||
|  |  | ||||||
|                         if (filterTerm.OperatorIsCaseInsensitive) |                         foreach (var filterTermValue in filterTerm.Values) | ||||||
|                         { |                         { | ||||||
|                             propertyValue = Expression.Call(propertyValue, |  | ||||||
|                                 typeof(string).GetMethods() |  | ||||||
|                                 .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); |  | ||||||
|  |  | ||||||
|                             filterValue = Expression.Call(filterValue, |                             dynamic constantVal = converter.CanConvertFrom(typeof(string)) | ||||||
|                                 typeof(string).GetMethods() |                                                       ? converter.ConvertFrom(filterTermValue) | ||||||
|                                 .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); |                                                       : Convert.ChangeType(filterTermValue, property.PropertyType); | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         if (innerExpression == null) |                             Expression filterValue = GetClosureOverConstant(constantVal, property.PropertyType); | ||||||
|                         { |  | ||||||
|                             innerExpression = GetExpression(filterTerm, filterValue, propertyValue); |  | ||||||
|                         } |                             if (filterTerm.OperatorIsCaseInsensitive) | ||||||
|                         else |                             { | ||||||
|                         { |                                 propertyValue = Expression.Call(propertyValue, | ||||||
|                             innerExpression = Expression.Or(innerExpression, GetExpression(filterTerm, filterValue, propertyValue)); |                                     typeof(string).GetMethods() | ||||||
|  |                                     .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); | ||||||
|  |  | ||||||
|  |                                 filterValue = Expression.Call(filterValue, | ||||||
|  |                                     typeof(string).GetMethods() | ||||||
|  |                                     .First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0)); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             if (innerExpression == null) | ||||||
|  |                             { | ||||||
|  |                                 innerExpression = GetExpression(filterTerm, filterValue, propertyValue); | ||||||
|  |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 innerExpression = Expression.Or(innerExpression, GetExpression(filterTerm, filterValue, propertyValue)); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     else |                     else | ||||||
| @@ -215,7 +219,7 @@ namespace Sieve.Services | |||||||
|                             new object[] { |                             new object[] { | ||||||
|                                             result, |                                             result, | ||||||
|                                             filterTerm.Operator, |                                             filterTerm.Operator, | ||||||
|                                             filterTerm.Value |                                             filterTerm.Values | ||||||
|                             }, dataForCustomMethods); |                             }, dataForCustomMethods); | ||||||
|  |  | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ namespace SieveUnitTests.Entities | |||||||
|         [Sieve(CanFilter = true, CanSort = true)] |         [Sieve(CanFilter = true, CanSort = true)] | ||||||
|         public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; |         public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; | ||||||
|  |  | ||||||
|  |         [Sieve(CanFilter = true)] | ||||||
|         public string Text { get; set; } |         public string Text { get; set; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ namespace SieveUnitTests | |||||||
|                 new Comment() { |                 new Comment() { | ||||||
|                     Id = 2, |                     Id = 2, | ||||||
|                     DateCreated = DateTimeOffset.UtcNow, |                     DateCreated = DateTimeOffset.UtcNow, | ||||||
|                     Text = "This is a brand new comment." |                     Text = "This is a brand new comment. ()" | ||||||
|                 }, |                 }, | ||||||
|             }.AsQueryable(); |             }.AsQueryable(); | ||||||
|         } |         } | ||||||
| @@ -148,7 +148,7 @@ namespace SieveUnitTests | |||||||
|                 Filters = "LikeCount==50", |                 Filters = "LikeCount==50", | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             Console.WriteLine(model.GetFiltersParsed()[0].Value); |             Console.WriteLine(model.GetFiltersParsed()[0].Values); | ||||||
|             Console.WriteLine(model.GetFiltersParsed()[0].Operator); |             Console.WriteLine(model.GetFiltersParsed()[0].Operator); | ||||||
|             Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed); |             Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed); | ||||||
|  |  | ||||||
| @@ -260,7 +260,7 @@ namespace SieveUnitTests | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         [TestMethod] |         [TestMethod] | ||||||
|         public void OrFilteringWorks() |         public void OrNameFilteringWorks() | ||||||
|         { |         { | ||||||
|             var model = new SieveModel() |             var model = new SieveModel() | ||||||
|             { |             { | ||||||
| @@ -275,5 +275,32 @@ namespace SieveUnitTests | |||||||
|             Assert.AreEqual(1, resultCount); |             Assert.AreEqual(1, resultCount); | ||||||
|             Assert.AreEqual(3, entry.Id); |             Assert.AreEqual(3, entry.Id); | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         [TestMethod] | ||||||
|  |         public void OrValueFilteringWorks() | ||||||
|  |         { | ||||||
|  |             var model = new SieveModel() | ||||||
|  |             { | ||||||
|  |                 Filters = "Title==C|D", | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             var result = _processor.Apply(model, _posts); | ||||||
|  |             Assert.AreEqual(2, result.Count()); | ||||||
|  |             Assert.IsTrue(result.Any(p => p.Id == 2)); | ||||||
|  |             Assert.IsTrue(result.Any(p => p.Id == 3)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [TestMethod] | ||||||
|  |         public void OrValueFilteringWorks2() | ||||||
|  |         { | ||||||
|  |             var model = new SieveModel() | ||||||
|  |             { | ||||||
|  |                 Filters = "Text@=(|)", | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             var result = _processor.Apply(model, _comments); | ||||||
|  |             Assert.AreEqual(1, result.Count()); | ||||||
|  |             Assert.AreEqual(2, result.FirstOrDefault().Id); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user