mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-21 21:12:50 +01:00
This commit is contained in:
parent
f9c7fb4cb0
commit
faa363edbb
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user