构建一个能够使用 PyParse 解析不同日期格式的简单解析器

Build a simple parser that is able to parse different date formats using PyParse

我正在构建一个简单的解析器,它接受如下查询: 'show fizi commits from 1/1/2010 to 11/2/2006' 到目前为止我有:

class QueryParser(object):

def parser(self, stmnt):

    keywords = ["select", "from","to", "show","commits", "where", "group by", "order by", "and", "or"]
    [select, _from, _to, show, commits, where, groupby, orderby, _and, _or] = [ CaselessKeyword(word) for word in keywords ]

    user = Word(alphas+"."+alphas)
    user2 = Combine(user + "'s")

    startdate=self.getdate()
    enddate=self.getdate()

    bnf = (show|select)+(user|user2).setResultsName("user")+(commits).setResultsName("stats")\
    +Optional(_from+startdate.setResultsName("start")+_to+enddate.setResultsName("end"))

    a = bnf.parseString(stmnt)
    return a

def getdate(self):
    integer = Word(nums).setParseAction(lambda t: int(t[0]))
    date = Combine(integer('year') + '/' + integer('month') + '/' + integer('day'))
    #date.setParseAction(self.convertToDatetime)
    return date

我希望日期更通用。这意味着用户可以提供 2010 年 1 月 20 日或其他日期格式。我在网上找到了一个很好的日期解析,它正是这样做的。它将日期作为字符串,然后对其进行解析。所以我剩下的就是为该函数提供我从解析器获得的日期字符串。我如何着手标记和捕获两个日期字符串。现在它只捕获格式 'y/m/d' 格式。有没有办法只获取整个字符串,而不管其格式如何。诸如在关键字和之后捕获单词之类的东西。非常感谢任何帮助。

我建议使用像 sqlparse 这样的东西,它已经为你处理了所有奇怪的边缘情况。如果您必须处理更高级的案例,从长远来看,这可能是一个更好的选择。

编辑:为什么不将日期块解析为字符串?像这样:

from pyparsing import CaselessKeyword, Word, Combine, Optional, alphas, nums

class QueryParser(object):

    def parser(self, stmnt):

        keywords = ["select", "from", "to", "show", "commits", "where",
                    "groupby", "order by", "and", "or"]

        [select, _from, _to, show, commits, where, groupby, orderby, _and, _or]\
            = [CaselessKeyword(word) for word in keywords]

        user = Word(alphas + "." + alphas)
        user2 = Combine(user + "'s")

        startdate = Word(alphas + nums + "/")
        enddate = Word(alphas + nums + "/")

        bnf = (
            (show | select) + (user | user2).setResultsName("user") +
            (commits).setResultsName("stats") +
            Optional(
                _from + startdate.setResultsName("start") +
                _to + enddate.setResultsName("end"))
            )

        a = bnf.parseString(stmnt)
        return a

这给了我类似的东西:

In [3]: q.parser("show fizi commits from 1/1/2010 to 11/2/2006")
Out[3]: (['show', 'fizi', 'commits', 'from', '1/1/2010', 'to', '11/2/2006'], {'start': [('1/1/2010', 4)], 'end': [('11/2/2006', 6)], 'stats': [('commits', 2)], 'user': [('fizi', 1)]})

然后您可以使用像 delorean or arrow 这样的库来尝试智能地处理日期部分 - 或者只使用常规的旧 dateutil。

一个简单的方法是要求引用日期。一个粗略的例子是这样的,但如果需要,你需要调整以适应你当前的语法:

from pyparsing import CaselessKeyword, quotedString, removeQuotes
from dateutil.parser import parse as parse_date

dp = (
    CaselessKeyword('from') + quotedString.setParseAction(removeQuotes)('from') +
    CaselessKeyword('to') + quotedString.setParseAction(removeQuotes)('to')
)

res = dp.parseString('from "jan 20" to "apr 5"')
from_date = parse_date(res['from'])
to_date = parse_date(res['to'])
# from_date, to_date == (datetime.datetime(2015, 1, 20, 0, 0), datetime.datetime(2015, 4, 5, 0, 0))

您可以使 pyparsing 解析器在匹配的内容上非常宽松,然后让解析操作进行更严格的值检查。如果您的日期字符串都是非白色 space 字符,这将特别容易。

例如,假设我们想要解析月份名称,但出于某种原因不希望我们的解析器表达式只执行 `oneOf('January February March ...etc.')。我们可以放入一个占位符,该占位符将只解析 Word 字符组直到下一个不合格字符(白色 space 或标点符号)。

monthName = Word(alphas.upper(), alphas.lower())

所以这里我们的月份以大写字母开头,后跟 0 个或多个小写字母。显然这会匹配很多非月份名称,所以我们将添加一个解析操作来做额外的验证:

def validate_month(tokens):
    import calendar
    monthname = tokens[0]
    print "check if %s is a valid month name" % monthname
    if monthname not in calendar.month_name:
        raise ParseException(monthname + " is not a valid month abbreviation")

monthName.setParseAction(validate_month)

如果我们执行这两个语句:

print monthName.parseString("January")
print monthName.parseString("Foo")

我们得到

check if January is a valid month name
['January']
check if Foo is a valid month name
Traceback (most recent call last):
  File "dd.py", line 15, in <module>
    print monthName.parseString("Foo")
  File "c:\python27\lib\site-packages\pyparsing.py", line 1125, in parseString
    raise exc
pyparsing.ParseException: Foo is not a valid month abbreviation (at char 0), (line:1, col:1)

(完成测试后,您可以从解析操作的中间删除打印语句 - 我只是包含它以表明它在解析过程中被调用。)

如果您可以使用 space 分隔的日期格式,那么您可以将解析器编写为:

date = Word(nums,nums+'/-')

然后您可以接受 1/1/200129-10-1929 等等。同样,您还将匹配 32237--/234//234/7 之类的字符串,这显然不是有效日期,因此您可以编写一个验证解析操作来检查字符串的有效性。在解析操作中,您可以实现自己的验证逻辑,或调用外部库。 (如果你能容忍不同的语言环境,你将不得不警惕像“4/3/2013”​​这样的日期,因为月份优先和日期优先选项有多种选择,这个字符串很容易意味着 4 月 3 日或3 月 4 日。)您还可以让解析操作为您进行实际转换,这样当您处理解析后的标记时,字符串将是实际的 Python 日期时间。