I am working on a library that parses query strings and evaluates the result for a given context. The query has the format FIELD OPERATOR VALUE (AND|OR) ...
. The list of operators is constant, but the field's type can be customized (default primary types are supported right now - other types can be added).
To enable the user of the library to overwrite the behavior of the operators, there is a FieldComparer
class that evaluates 2 values for a given operator. It uses the default comparer of the type, but can be extended to provide specialized behavior.
The StringFieldComparer
is such a specialized comparer for fields of type string. It adds support for wildcards and regex.
Operators
public enum Operator
{
Equals,
LessThen,
LessThenOrEquals,
GraterThan,
GraterThanOrEquals,
NotEquals,
Matches,
NotMatches,
}
FieldComparer
public class FieldComparer<TFieldValue>
{
protected FieldComparer()
{ }
public virtual bool Compare(Operator op, TFieldValue left, TFieldValue right)
{
var comparer = Comparer<TFieldValue>.Default;
switch (op)
{
case Operator.Equals:
return comparer.Compare(left, right) == 0;
case Operator.NotEquals:
return comparer.Compare(left, right) != 0;
case Operator.GraterThan:
return comparer.Compare(left, right) < 0;
case Operator.GraterThanOrEquals:
return comparer.Compare(left, right) <= 0;
case Operator.LessThen:
return comparer.Compare(left, right) > 0;
case Operator.LessThenOrEquals:
return comparer.Compare(left, right) >= 0;
default:
throw new NotSupportedException($"Operator '{op}' is not supported for type '{typeof(TFieldValue)}'");
}
}
internal static FieldComparer<TFieldValue> Default { get; } = new FieldComparer<TFieldValue>();
}
StringFieldComparer
public class StringFieldComparer : FieldComparer<string>
{
private readonly Dictionary<string, Regex> myRegexes = new Dictionary<string, Regex>();
public override bool Compare(Operator op, string left, string right)
{
switch (op)
{
case Operator.Matches:
case Operator.NotMatches:
var regex = GetRegex(left);
var match = regex.IsMatch(right);
return op == Operator.Matches ? match : !match;
case Operator.Equals:
case Operator.NotEquals:
regex = GetWildcardRegex(left);
match = regex.IsMatch(right);
return op == Operator.Equals ? match : !match;
default:
return base.Compare(op, left, right);
}
}
private Regex GetWildcardRegex(string left)
{
var pattern = Regex.Escape(left)
.Replace(@"\*", ".*")
.Replace(@"\?", ".");
pattern = "^" + pattern + "$";
return GetRegex(pattern);
}
private Regex GetRegex(string left)
{
Regex regex;
if (!myRegexes.TryGetValue(left, out regex))
{
regex = new Regex(left, RegexOptions.Compiled | RegexOptions.IgnoreCase);
myRegexes.Add(left, regex);
}
return regex;
}
}
Usage
var comparerStr = new StringFieldComparer();
comparerStr.Compare(Operator.Equals, "Hello*", "Hello World"); // true
comparerStr.Compare(Operator.Matches, "He.lo.*", "Hello World"); // true
var comparerInt = FieldComparer<int>.Default;
comparerInt.Compare(Operator.Equals, 1, 1); // true
comparerInt.Compare(Operator.LessThan, 1, 4); // false
I am not really happy with the method StringFieldComparer.Compare
because the op
will be checked twice for Equals
/NotEquals
and Match
/NotMatch
. Is there a way to realize it with only one check without creating the regex unnecessarily?
As always, any feedback is welcome! :)