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