我有一个C#项目,允许用户使用正则表达式创建数据过滤器。他们可以根据需要添加任意数量的过滤器。每个过滤器都包含一个字段和用户键入的正则表达式。
现在它适用于所有AND逻辑。我循环遍历每个过滤器,如果它不匹配,我设置skip = true并退出循环。然后,如果skip == true,我跳过该记录并且不包括它。因此,每个过滤器必须匹配才能包含该字段。
但是,现在他们希望能够添加更复杂的逻辑规则。例如,如果他们创建了4个过滤规则。他们希望能够指定: 1和2和(3或4) 或者他们可能想要指定 1或2或3或4 或者他们可能想要指定 (1和2和3)或4 等等...我认为你明白了。
我添加了一个文本框,可以输入他们想要的逻辑。
我一直在绞尽脑汁,我对如何使这项工作感到难过。我唯一的结论是以某种方式能够创建一个动态的IF语句,该语句基于他们在文本框中键入的文本,但我不知道这是否可能。
似乎应该有一个简单的方法来做到这一点,但我无法弄明白。如果有人能帮助我,我会非常感激。
谢谢!
答案 0 :(得分:2)
这是一个完整的测试,可以按照您想要的方式使用正则表达式和AND,OR和括号。请注意,这仅支持运算符AND
和OR
以及括号(
和)
,并且期望输入形式有点(正则表达式不能包含空格)。解析可以改进,想法保持不变。
以下是整体测试:
var input = ".* AND [0-9]+ AND abc OR (abc AND def)";
var rpn = ParseRPN(input);
var test = GetExpression(new Queue<string>(rpn.Reverse())).Compile();
test("abc"); // false
test("abc0"); // true
test("abcdef"); // true
以下是反转波兰表示法的解析:
public Queue<string> ParseRPN(string input)
{
// improve the parsing into tokens here
var output = new Queue<string>();
var ops = new Stack<string>();
input = input.Replace("(","( ").Replace(")"," )");
var split = input.Split(' ');
foreach (var token in split)
{
if (token == "AND" || token == "OR")
{
while (ops.Count > 0 && (ops.Peek() == "AND" || ops.Peek() == "OR"))
{
output.Enqueue(ops.Pop());
}
ops.Push(token);
}
else if (token == "(") ops.Push(token);
else if (token == ")")
{
while (ops.Count > 0 && ops.Peek() != "(")
{
output.Enqueue(ops.Pop());
}
ops.Pop();
}
else output.Enqueue(token); // it's a number
}
while (ops.Count > 0)
{
output.Enqueue(ops.Pop());
}
return output;
}
魔法GetExpression
:
public Expression<Func<string,bool>> GetExpression(Queue<string> input)
{
var exp = input.Dequeue();
if (exp == "AND") return GetExpression(input).And(GetExpression(input));
else if (exp == "OR") return GetExpression(input).Or(GetExpression(input));
else return (test => Regex.IsMatch(test, exp));
}
请注意,这确实依赖PredicateBuilder
,但所使用的扩展函数在这里完整:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T> () { return f => true; }
public static Expression<Func<T, bool>> False<T> () { return f => false; }
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}
}
答案 1 :(得分:1)
如下所示 - 定义操作类以表示二进制操作并构建树:
interface IFilter
{
bool Filter(Record r);
}
class SimpleFilter : IFilter
{
bool Filter(Record r)
{
return RegExpMatch(r);
}
}
class AndFilter : IFilter
{
public AndFilter(IFilter left, IFilter right) {}
bool Filter(Record r)
{
return left.Filter(r) && right.Filter(r);
}
}
class OrFilter : IFilter
{
public OrFilter(IFilter left, IFilter right) {}
bool Filter(Record r)
{
return left.Filter(r) || right.Filter(r);
}
}
答案 2 :(得分:0)
第一步是将表达式解析为抽象语法树。为此,您可以使用shunting-yard algorithm。
完成此操作后,您可以使用虚拟方法或界面递归评估树。例如,您可以拥有一个SimpleNode
类,它表示一个简单的表达式(如示例中的1
)并且可以对其进行评估。然后你有AndNode
代表AND
操作,并有两个子节点。它评估子节点并返回是否都成功。
答案 3 :(得分:0)
对规范模式的解释(带有示例代码)应该有所帮助。
答案 4 :(得分:0)
可能有图书馆为你做这类事情,但在过去,我根据使用Predicate手动推出了这些内容; 使用系统; 使用System.Collections.Generic; 使用System.Linq; 使用System.Text; 使用System.Text.RegularExpressions;
namespace ConsoleApplication1
{
public enum CombineOptions
{
And,
Or,
}
public class FilterExpression
{
public string Filter { get; set; }
public CombineOptions Options { get; private set; }
public FilterExpression(string filter, CombineOptions options)
{
this.Filter = filter;
this.Options = options;
}
}
public static class PredicateExtensions
{
public static Predicate<T> And<T>
(this Predicate<T> original, Predicate<T> newPredicate)
{
return t => original(t) && newPredicate(t);
}
public static Predicate<T> Or<T>
(this Predicate<T> original, Predicate<T> newPredicate)
{
return t => original(t) || newPredicate(t);
}
}
public static class ExpressionBuilder
{
public static Predicate<string> BuildExpression(IEnumerable<FilterExpression> filterExpressions)
{
Predicate<string> predicate = (delegate
{
return true;
});
foreach (FilterExpression expression in filterExpressions)
{
string nextFilter = expression.Filter;
Predicate<string> nextPredicate = (o => Regex.Match(o, nextFilter).Success);
switch (expression.Options)
{
case CombineOptions.And:
predicate = predicate.And(nextPredicate);
break;
case CombineOptions.Or:
predicate = predicate.Or(nextPredicate);
break;
}
}
return predicate;
}
}
class Program
{
static void Main(string[] args)
{
FilterExpression f1 = new FilterExpression(@"data([A-Za-z0-9\-]+)$", CombineOptions.And);
FilterExpression f2 = new FilterExpression(@"otherdata([A-Za-z0-9\-]+)$", CombineOptions.And);
FilterExpression f3 = new FilterExpression(@"otherdata([A-Za-z0-9\-]+)$", CombineOptions.Or);
// result will be false as "data1" does not match both filters
Predicate<string> pred2 = ExpressionBuilder.BuildExpression(new[] { f1, f2 });
bool result = pred2.Invoke("data1");
// result will be true as "data1" matches 1 of the 2 Or'd filters
Predicate<string> pred3 = ExpressionBuilder.BuildExpression(new[] { f1, f3 });
result = pred3.Invoke("data1");
}
}
}
现在您需要做的就是解析'括号'以确定将FilterExpressions发送到BuildExpression方法的顺序。您可能需要针对更复杂的表达式进行调整,但希望这会有所帮助。