Skip to content

Instantly share code, notes, and snippets.

@prince272
Created September 29, 2021 20:06
Show Gist options
  • Save prince272/e9e19de60484e9b07cd3afd4ba090d9b to your computer and use it in GitHub Desktop.
Save prince272/e9e19de60484e9b07cd3afd4ba090d9b to your computer and use it in GitHub Desktop.
using LinqKit;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace Rextester
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public static List<Person> People { get; } = new List<Person>()
{
new Person { Name = "Prince", Age = 12 },
new Person { Name = "Nadia", Age = 34 },
new Person { Name = "Hilary", Age = 45 },
new Person { Name = "Silkily", Age = 78 },
new Person { Name = "Robby", Age = 56 },
new Person { Name = "Johnson", Age = 83 },
new Person { Name = "James", Age = 86 },
};
public override string ToString()
{
return $"Name: {Name}, Age: {Age}";
}
}
public class Program
{
public static void Main(string[] args)
{
var predicate = new RuleParser<Person>()
.AddProperty(_ => _.Name)
.Parse($"name!@{Uri.EscapeDataString("Prince")}");
var filtedPeople = Person.People.Where(predicate.Compile()).ToList();
}
}
// credit: https://stackoverflow.com/questions/38426175/extract-all-possible-paths-from-expression-tree-and-evaluate-them-to-hold-true
public class RuleParser<TSource>
{
private static readonly Dictionary<string, RuleType> LogicMap = new Dictionary<string, RuleType>() {
{ "(" , RuleType.OpenParenthesis },
{ ")" , RuleType.CloseParenthesis },
{ "&&" , RuleType.And },
{ "||" , RuleType.Or },
};
private static readonly Dictionary<string, RuleType> OperatorMap = new Dictionary<string, RuleType>() {
{ "==" , RuleType.Equal },
{ "!=" , RuleType.NotEqual },
{ "<" , RuleType.LessThan },
{ "<=" , RuleType.LessThanOrEqual },
{ ">" , RuleType.GreaterThan },
{ ">=" , RuleType.GreaterThanOrEqual },
{ "=@" , RuleType.Contains },
{ "!@" , RuleType.Not | RuleType.Contains },
{ "=^" , RuleType.StartsWith },
{ "!^" , RuleType.Not | RuleType.StartsWith },
{ "=$" , RuleType.EndsWith },
{ "!$" , RuleType.Not | RuleType.EndsWith },
};
private readonly Dictionary<string, PropertyInfo> PropertyMap = new Dictionary<string, PropertyInfo>();
// credit: https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression
public virtual RuleParser<TSource> AddProperty<TLambda>(Expression<Func<TSource, TLambda>> propertyLambda, string propertyName = null)
{
var body = propertyLambda.Body;
if (!(body is MemberExpression member)
&& !(body is UnaryExpression unary
&& (member = unary.Operand as MemberExpression) != null))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"does not refer to a property.");
if (!(member.Member is PropertyInfo propInfo))
throw new ArgumentException($"Expression '{propertyLambda}' " +
"refers to a field, not a property.");
var type = typeof(TSource);
if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
throw new ArgumentException($"Expresion '{propertyLambda}' " +
"refers to a property that is not from type '{type}'.");
PropertyMap.Add(propertyName ?? propInfo.Name, propInfo);
return this;
}
public Expression<Func<TSource, bool>> Parse(string ruleString)
{
ruleString = CleanRuleString(ruleString);
var rule = ParseRuleString(ruleString);
if (rule != null) return BuildExpression(rule);
else return _ => true;
}
private Rule ParseRuleString(string ruleString)
{
var rules = new List<Rule>();
var reader = new StringReader(ruleString);
var logicChars = LogicMap.Keys.SelectMany(_ => _.ToCharArray()).ToArray();
var logicString = string.Empty;
var operatorChars = OperatorMap.Keys.SelectMany(_ => _.ToCharArray());
var operatorString = string.Empty;
while (reader.Peek() > -1)
{
var charValue = (char)reader.Read();
if (logicChars.Contains(charValue))
{
logicString += charValue;
}
else if (!logicChars.Any(_ => (logicString + charValue).EndsWith(_)))
{
if (LogicMap.TryGetValue(logicString, out RuleType logicRuleType))
{
var operatorKey = new string(operatorString.Where(_ => operatorChars.Contains(_)).ToArray());
var operatorRuleType = OperatorMap[operatorKey];
var field = operatorString.Split(operatorKey);
rules.Add(new Rule { Name = field[0], Value = field[1], Type = operatorRuleType });
rules.Add(new Rule { Type = logicRuleType });
operatorString = string.Empty;
logicString = string.Empty;
}
operatorString += charValue;
}
if (reader.Peek() <= -1)
{
if (!string.IsNullOrEmpty(operatorString))
{
var operatorKey = new string(operatorString.Where(_ => operatorChars.Contains(_)).ToArray());
var operatorRuleType = OperatorMap[operatorKey];
var field = operatorString.Split(operatorKey).Select(_ => Uri.UnescapeDataString(_)).ToArray();
rules.Add(new Rule { Name = field[0], Value = field[1], Type = operatorRuleType });
operatorString = string.Empty;
logicString = string.Empty;
}
}
}
rules = TransformToPolishNotation(rules);
var enumerator = rules.GetEnumerator();
return enumerator.MoveNext() ? CreateRule(ref enumerator) : null;
}
private string CleanRuleString(string ruleString)
{
return new string(ruleString.ToCharArray().Where(c => !char.IsWhiteSpace(c)).ToArray());
}
private static Expression<Func<TSource, bool>> ComputeExpression(PropertyInfo propertyInfo, RuleType operatorRuleType, object value)
{
static ConstantExpression ComputeConstantExpressionBasedOnPropertyExpressionTargetTypeAndValue(Type memberType, object value)
{
var ce = Expression.Constant(value, memberType);
return ce;
}
var constantExpression = ComputeConstantExpressionBasedOnPropertyExpressionTargetTypeAndValue(propertyInfo.PropertyType, value);
var parameter = Expression.Parameter(propertyInfo.DeclaringType, "_");
var propertyExpression = Expression.Property(parameter, propertyInfo);
Expression logicExpression = null;
if (operatorRuleType.HasFlag(RuleType.Equal))
{
logicExpression = Expression.Equal(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.NotEqual))
{
logicExpression = Expression.NotEqual(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.LessThan))
{
logicExpression = Expression.LessThan(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.LessThanOrEqual))
{
logicExpression = Expression.LessThanOrEqual(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.GreaterThan))
{
logicExpression = Expression.GreaterThan(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.GreaterThanOrEqual))
{
logicExpression = Expression.GreaterThanOrEqual(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.Equal))
{
logicExpression = Expression.NotEqual(propertyExpression, constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.Contains))
{
logicExpression = Expression.Call(propertyExpression, typeof(string).GetMethod("Contains", new[] { typeof(string) }), constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.StartsWith))
{
logicExpression = Expression.Call(propertyExpression, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), constantExpression);
}
else if (operatorRuleType.HasFlag(RuleType.EndsWith))
{
logicExpression = Expression.Call(propertyExpression, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), constantExpression);
}
else
{
throw new InvalidOperationException($"Value '{operatorRuleType}' is not supported. (Parameter '{nameof(operatorRuleType)}')");
}
if (operatorRuleType.HasFlag(RuleType.Not))
{
logicExpression = Expression.Not(logicExpression);
}
return Expression.Lambda<Func<TSource, bool>>(logicExpression, new[] { parameter });
}
private Expression<Func<TSource, bool>> BuildExpression(Rule rule)
{
var predicate = PredicateBuilder.New<TSource>();
if (OperatorMap.Values.Contains(rule.Type))
{
var propertyInfo = PropertyMap.SingleOrDefault(_ => string.Equals(_.Key, rule.Name, StringComparison.CurrentCultureIgnoreCase)).Value;
if (propertyInfo != null)
{
var propertyValue = TypeHelper.ConvertTo(rule.Value, propertyInfo.PropertyType);
predicate = predicate.Start(ComputeExpression(propertyInfo, rule.Type, propertyValue));
}
}
foreach (var subRule in rule.SubRules)
{
if (rule.Type == RuleType.And)
predicate = predicate.And(BuildExpression(subRule));
else if (rule.Type == RuleType.Or)
predicate = predicate.Or(BuildExpression(subRule));
}
return predicate;
}
private List<Rule> TransformToPolishNotation(List<Rule> infixRuleList)
{
var outputQueue = new Queue<Rule>();
var stack = new Stack<Rule>();
foreach (var rule in infixRuleList)
{
if (OperatorMap.Values.Contains(rule.Type))
{
outputQueue.Enqueue(rule);
}
else
{
switch (rule.Type)
{
case RuleType.And:
case RuleType.Or:
case RuleType.OpenParenthesis:
{
stack.Push(rule);
}
break;
case RuleType.CloseParenthesis:
{
while (stack.Peek().Type != RuleType.OpenParenthesis)
{
outputQueue.Enqueue(stack.Pop());
}
stack.Pop();
}
break;
}
}
}
while (stack.Count > 0)
{
outputQueue.Enqueue(stack.Pop());
}
return outputQueue.Reverse().ToList();
}
private Rule CreateRule(ref List<Rule>.Enumerator ruleEnumerator)
{
var rule = ruleEnumerator.Current;
if (OperatorMap.Values.Contains(rule.Type))
{
ruleEnumerator.MoveNext();
return rule;
}
else
{
ruleEnumerator.MoveNext();
if (rule.Type == RuleType.And || rule.Type == RuleType.Or)
{
rule.SubRules.Insert(0, CreateRule(ref ruleEnumerator));
rule.SubRules.Insert(0, CreateRule(ref ruleEnumerator));
}
}
return rule;
}
}
[Flags]
public enum RuleType
{
OpenParenthesis = 1 << 0,
CloseParenthesis = 1 << 1,
And = 1 << 2,
Or = 1 << 3,
Not = 1 << 4,
Equal = 1 << 5,
NotEqual = 1 << 6,
LessThan = 1 << 7,
LessThanOrEqual = 1 << 8,
GreaterThan = 1 << 9,
GreaterThanOrEqual = 1 << 10,
Contains = 1 << 11,
StartsWith = 1 << 12,
EndsWith = 1 << 13,
}
public class Rule
{
public RuleType Type { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public List<Rule> SubRules { get; } = new List<Rule>();
}
public sealed class TypeHelper
{
public static object ConvertTo(object value, Type destinationType)
{
return ConvertTo(value, destinationType, CultureInfo.InvariantCulture);
}
public static object ConvertTo(object value, Type destinationType, CultureInfo culture)
{
if (value == null)
return null;
var sourceType = value.GetType();
var destinationConverter = TypeDescriptor.GetConverter(destinationType);
if (destinationConverter.CanConvertFrom(value.GetType()))
return destinationConverter.ConvertFrom(null, culture, value);
var sourceConverter = TypeDescriptor.GetConverter(sourceType);
if (sourceConverter.CanConvertTo(destinationType))
return sourceConverter.ConvertTo(null, culture, value, destinationType);
if (destinationType.IsEnum && value is int)
return Enum.ToObject(destinationType, (int)value);
if (!destinationType.IsInstanceOfType(value))
return Convert.ChangeType(value, destinationType, culture);
return value;
}
public static T ConvertTo<T>(object value)
{
//return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
return (T)ConvertTo(value, typeof(T));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment