我有一个程序,允许用户在DataGridView列标题的文本框中输入过滤器。然后将此文本解析为FilterOperations的列表。
目前,我标记该字符串,然后在饥饿的For循环中构建列表。
我可以使用哪种设计模式来摆脱繁琐的施工过程?
我还可以采取其他措施来改进设计吗?
在当前状态下,很难添加对另一个运算符,数据类型的支持,或者构建除过滤列表之外的其他内容。可以说,我需要用构建表达式(即将成为事实)或构建SQL Where子句来替换过滤器列表。
该过滤器遵循此语法,并且对于字符串,数字和日期时间有效:
远程操作员
LOWERLIMIT .. UPPERLIMIT
29..52将被解析为过滤器列表“ x> = 29”和“ x <= 52”中的两个元素
低于
.. upperLimit
..52将被解析为“ x <52”
比...更棒
lowerLimit ..
29 ..将解析为“ x> 29”
通配符
*someText*
在SQL中将等于x LIKE“%someText%”
字符串字面量
' ..或*之类的运算符在单引号之间被忽略'
所以我定义了三个代币
适用于..的RangeOperator
*的通配符
纯值和单引号中的值的文本
public static FilterList<T> Parse<T>(string filter, string columnname, Type dataType) where T : class
{
if (dataType != typeof(float) && dataType != typeof(DateTime) && dataType != typeof(string))
throw new NotSupportedException(String.Format("Data Type is not supported '{0}'", dataType));
Token[] filterParts = tokenize(filter);
filterParts = cleanUp(filterParts);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < filterParts.Length; i++)
{
Token currentToken = filterParts[i];
//BereichsFilter prüfen und bauen
if (currentToken.TokenType == TokenType.RangeOperator)
{
if (filterParts.Length < 2)
{
throw new FilterException("Missing argument for RangeOperator");
}
if (filterParts.Length > 3)
{
throw new FilterException("RangeOperator can't be mixed with other operators");
}
if (i == 0)
{
if (filterParts.Length == 2)
{
//Bis Operator
Token right = filterParts[1];
if (right.TokenType != TokenType.Text)
throw new FilterException("TextToken expected");
if (String.IsNullOrEmpty(right.Text))
throw new FilterException("Text must have value");
if (right.Text.StartsWith("."))
throw new FilterException("Text starting with a dot is not valid");
if (dataType == typeof(string))
return new FilterList<T> { { columnname, FilterOperator.Less, right.Text } };
//filterString = String.Format("({0} < '{1}' OR {0} IS NULL)", columnname, right.Text);
if (dataType == typeof(float))
{
float rightF;
if (!float.TryParse(right.Text, out rightF))
throw new FilterException(
String.Format("right parameter has wrong format '{0}'", right.Text));
return new FilterList<T> { { columnname, FilterOperator.Less, rightF } };
//filterString = String.Format("({0} < {1} OR {0} IS NULL)", columnname, rightF.ToString(CultureInfo.InvariantCulture));
}
if (dataType == typeof(DateTime))
{
DateTime rightDt = parseDateTime(right.Text);
return new FilterList<T> { { columnname, FilterOperator.Less, rightDt } };
//filterString = String.Format("({0} < '{1}' OR {0} IS NULL)", columnname, rightDT.ToString(CultureInfo.InvariantCulture));
}
break;
}
throw new FilterException("too many arguments");
}
if (i == 1)
{
if (filterParts.Length == 2)
{
//Von Operator
Token left = filterParts[0];
if (left.TokenType != TokenType.Text)
throw new FilterException("TextToken expected");
if (String.IsNullOrEmpty(left.Text))
throw new FilterException("Argument must have value");
if (dataType == typeof(string))
return new FilterList<T> { { columnname, FilterOperator.Greater, left.Text } };
//filterString = String.Format("({0} > '{1}')", columnname, left.Text);
if (dataType == typeof(float))
{
float leftF;
if (!float.TryParse(left.Text, out leftF))
throw new FilterException(String.Format(
"left parameter has wrong format '{0}'", left.Text));
return new FilterList<T> { { columnname, FilterOperator.Greater, leftF } };
//filterString = String.Format("({0} > {1})", columnname, leftF.ToString(CultureInfo.InvariantCulture));
}
if (dataType == typeof(DateTime))
{
DateTime leftDt = parseDateTime(left.Text);
return new FilterList<T> { { columnname, FilterOperator.Greater, leftDt } };
//filterString = String.Format("({0} > '{1}')", columnname, leftDT.ToString(CultureInfo.InvariantCulture));
}
break;
}
else
{
//BereichsOperator
Token left = filterParts[0];
if (left.TokenType != TokenType.Text)
throw new FilterException("TextToken expected");
if (String.IsNullOrEmpty(left.Text))
throw new FilterException("parameter must have value");
Token right = filterParts[2];
if (right.TokenType != TokenType.Text)
throw new FilterException("TextToken expected");
if (String.IsNullOrEmpty(right.Text))
throw new FilterException("parameter must have value");
if (dataType == typeof(string))
return new FilterList<T>
{
{columnname, FilterOperator.GreaterOrEqual, left.Text},
{columnname, FilterOperator.LessOrEqual, right.Text}
};
//filterString = String.Format("{0} >= '{1}' AND {0} <= '{2}'", columnname, left.Text, right.Text);
if (dataType == typeof(float))
{
float rightF;
if (!float.TryParse(right.Text, out rightF))
throw new FilterException(
String.Format("right parameter has wrong format '{0}'", right.Text));
float leftF;
if (!float.TryParse(left.Text, out leftF))
throw new FilterException(String.Format(
"left parameter has wrong format'{0}'", left.Text));
return new FilterList<T>
{
{columnname, FilterOperator.GreaterOrEqual, leftF},
{columnname, FilterOperator.LessOrEqual, rightF}
};
//filterString = String.Format("{0} >= {1} AND {0} <= {2}", columnname, leftF.ToString(CultureInfo.InvariantCulture), leftF.ToString(CultureInfo.InvariantCulture));
}
if (dataType == typeof(DateTime))
{
DateTime rightDt = parseDateTime(right.Text);
DateTime leftDt = parseDateTime(left.Text);
return new FilterList<T>
{
{columnname, FilterOperator.GreaterOrEqual, leftDt},
{columnname, FilterOperator.LessOrEqual, rightDt}
};
//filterString = String.Format("{0} >= '{1}' AND {0} <= '{2}'", columnname, leftDT.ToString(CultureInfo.InvariantCulture), rightDT.ToString(CultureInfo.InvariantCulture));
}
break;
}
}
throw new FilterException("unexpected parameter");
}
//Stringsuche Bauen
if (currentToken.TokenType == TokenType.Wildcard)
{
if (dataType != typeof(string))
throw new FilterException("Operator not allowed with this Data Type");
//Fehler wenn Datentyp kein string
sb.Append("%");
}
else if (currentToken.TokenType == TokenType.Text)
sb.Append(escape(currentToken.Text));
}
//Filterung auf Zeichenfolge
string text = sb.ToString();
if (dataType == typeof(string))
return new FilterList<T> { { columnname, FilterOperator.Like, text } };
//filterString = String.Format("{0} LIKE '{1}' ESCAPE '\\'", columnname, text);
if (dataType == typeof(DateTime))
{
DateTime dt = parseDateTime(text);
return new FilterList<T> { { columnname, FilterOperator.Equal, dt } };
//filterString = String.Format("{0} = '{1}'", columnname, DT.ToString(CultureInfo.InvariantCulture));
}
if (dataType == typeof(float))
{
float f;
if (!float.TryParse(text, out f))
throw new FilterException(String.Format("parameter has wrong format '{0}'", text));
return new FilterList<T> { { columnname, FilterOperator.Equal, f } };
//filterString = String.Format("{0} = {1}", columnname, F.ToString(CultureInfo.InvariantCulture));
}
return null;
}
您需要找到一个基于解析表达式文法的C#代码生成器。它使您可以定义一个语法,然后由生成器将其转换为代码。然后,该代码将能够按照您期望的语法来解析文本。
一个非常快速的google-fu表明,peg-sharp可以起作用。
为了学习使用PEG,您可以尝试PEG.js的在线版本,该版本几乎可以在您最终使用的工作流程中使用:
作为概念证明,这是您的语法的一个临时实现,您可以将粘贴复制到PEG.js中(我想一个人可以设法将其嵌入stackoverflow小部件中):
语法如下:
start
= filters
filters
= left:filter " " right:filters { return {filter: left, operation: "AND", filters: right};}
/ filter
filter
= applicableRange:range {return {type: "range", range: applicableRange};}
/ openWord:wildcard {return {type: "wildcard", word: openWord};}
/ simpleWord:word {return simpleWord;}
/ sentence:sentence {return sentence;}
sentence
= "'" + letters:[0-9a-zA-Z *.]* "'" {return {type: "sentence", value: letters.join("")};}
word "aword"
= letters:[0-9a-zA-Z]+ { return {type: "word", value: letters.join("")}; }
wildcard
=
"*" word:word "*" {return {type: "wildcardBoth", value: word};}
/ "*" word:word {return {type: "wildcardStart", value: word};}
/ word:word "*" {return {type: "wildcardEnd", value: word};}
range "range"
= left:word? ".." right:word? {return {from: left, to: right};}
基本上,语法使您可以定义语言的基本组成部分以及如何将它们相互联系起来。例如,过滤器可以是范围,通配符,单词,句子或什么都不是(至少这是我在定义语法时所追求的;最后一个选择是结束过滤器中的递归)。
与这些块一起,您可以定义遇到这些块时的输出。在这种情况下,我输出一个JSON对象,该对象表示应该进行哪种类型的过滤以及该过滤器将具有哪些参数。
如果使用以下输入来测试语法:
'testing range' 123..456 123.. ..abc 'and testing wildcards' word1* *word2 *word3* cool heh
您将获得一个结构,该结构描述应根据语法构建的过滤器:
{
"filter": {
"type": "sentence",
"value": "testing range"
},
"operation": "AND",
"filters": {
"filter": {
"type": "range",
"range": {
"from": {
"type": "word",
"value": "123"
},
"to": {
"type": "word",
"value": "456"
}
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "range",
"range": {
"from": {
"type": "word",
"value": "123"
},
"to": null
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "range",
"range": {
"from": null,
"to": {
"type": "word",
"value": "abc"
}
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "sentence",
"value": "and testing wildcards"
},
"operation": "AND",
"filters": {
"filter": {
"type": "wildcard",
"word": {
"type": "wildcardEnd",
"value": {
"type": "word",
"value": "word1"
}
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "wildcard",
"word": {
"type": "wildcardStart",
"value": {
"type": "word",
"value": "word2"
}
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "wildcard",
"word": {
"type": "wildcardBoth",
"value": {
"type": "word",
"value": "word3"
}
}
},
"operation": "AND",
"filters": {
"filter": {
"type": "word",
"value": "cool"
},
"operation": "AND",
"filters": {
"type": "word",
"value": "heh"
}
}
}
}
}
}
}
}
}
}
对于C#生成器,原理是相同的:将语法编译为能够解析您的输入的某些C#代码,并定义当解析碰到这个或那个块时应该发生的情况。
如果发生更改,您将需要重新编译语法(尽管可以轻松地将其包含在构建步骤中),但是您将能够生成表示已解析的过滤器的结构,并使用它来过滤搜索结果。
PEG的一大优势是该格式众所周知,并且有大量在线学习该格式的资源,因此该知识将可以转移到其他语言/用途
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句