解析自定义Filtersyntax的最佳方法

BoeseB

我有一个程序,允许用户在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声明(左窗口)
  • javascript解析器会动态更新(右上角窗口)
  • 解析器解析您的输入并产生结果(右下方窗口)

作为概念证明,这是您的语法的一个临时实现,您可以将粘贴复制到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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

编写自定义异常的最佳方法

来自分类Dev

自定义动作栏的最佳方法

来自分类Dev

编写自定义异常的最佳方法

来自分类Dev

创建自定义查找的最佳方法

来自分类Dev

解析Google自定义搜索引擎结果的最佳方法

来自分类Dev

解析和验证自定义的方法

来自分类Dev

定义自定义授权策略的最佳方法

来自分类Dev

从自定义UITableViewCell操作UIImagePickerController的最佳方法

来自分类Dev

用Java编写自定义异常名称的最佳方法

来自分类Dev

进行自定义排序的最佳方法是什么?

来自分类Dev

GWT:制作自定义ListBox的最佳方法是什么

来自分类Dev

用Java编写自定义异常名称的最佳方法

来自分类Dev

什么是在WPF中自定义绑定文本的最佳方法

来自分类Dev

自定义Bootstrap的最佳方法是什么

来自分类Dev

创建自定义android按钮的最佳方法

来自分类Dev

GSON自定义解析

来自分类Dev

使用自定义ArrayAdapter时无法解析方法超级

来自分类Dev

无法使用自定义方法解析某些内容

来自分类Dev

克隆无法自定义结构的自定义结构是您每次使用它的最佳方法吗?

来自分类Dev

克隆无法自定义结构的自定义结构是您每次使用它的最佳方法吗?

来自分类Dev

android:自定义背景的最佳做法

来自分类Dev

自定义统计的最佳做法

来自分类Dev

存储自定义功能的最佳位置?

来自分类Dev

创建自定义脚本的最佳实践

来自分类Dev

Symfony - 自定义捆绑最佳实践

来自分类Dev

获取Android设备的用户定义的自定义名称的最佳方法

来自分类Dev

获取Android设备的用户定义的自定义名称的最佳方法

来自分类Dev

创建自定义方法安全性表达式的最佳方法

来自分类Dev

制作自定义弹出式非页内广告的最佳方法是什么?

Related 相关文章

  1. 1

    编写自定义异常的最佳方法

  2. 2

    自定义动作栏的最佳方法

  3. 3

    编写自定义异常的最佳方法

  4. 4

    创建自定义查找的最佳方法

  5. 5

    解析Google自定义搜索引擎结果的最佳方法

  6. 6

    解析和验证自定义的方法

  7. 7

    定义自定义授权策略的最佳方法

  8. 8

    从自定义UITableViewCell操作UIImagePickerController的最佳方法

  9. 9

    用Java编写自定义异常名称的最佳方法

  10. 10

    进行自定义排序的最佳方法是什么?

  11. 11

    GWT:制作自定义ListBox的最佳方法是什么

  12. 12

    用Java编写自定义异常名称的最佳方法

  13. 13

    什么是在WPF中自定义绑定文本的最佳方法

  14. 14

    自定义Bootstrap的最佳方法是什么

  15. 15

    创建自定义android按钮的最佳方法

  16. 16

    GSON自定义解析

  17. 17

    使用自定义ArrayAdapter时无法解析方法超级

  18. 18

    无法使用自定义方法解析某些内容

  19. 19

    克隆无法自定义结构的自定义结构是您每次使用它的最佳方法吗?

  20. 20

    克隆无法自定义结构的自定义结构是您每次使用它的最佳方法吗?

  21. 21

    android:自定义背景的最佳做法

  22. 22

    自定义统计的最佳做法

  23. 23

    存储自定义功能的最佳位置?

  24. 24

    创建自定义脚本的最佳实践

  25. 25

    Symfony - 自定义捆绑最佳实践

  26. 26

    获取Android设备的用户定义的自定义名称的最佳方法

  27. 27

    获取Android设备的用户定义的自定义名称的最佳方法

  28. 28

    创建自定义方法安全性表达式的最佳方法

  29. 29

    制作自定义弹出式非页内广告的最佳方法是什么?

热门标签

归档