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

时间:2015-01-23 15:46:28

标签: python parsing date pyparsing

我正在构建一个简单的解析器,它接受如下查询: 'show fizi承诺从2010年1月1日到11月2日' 到目前为止,我有:

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'格式。有没有办法让整个字符串无论如何格式化。在关键字和关键字之后立即捕获单词。非常感谢任何帮助。

3 个答案:

答案 0 :(得分:2)

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

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))

答案 1 :(得分:1)

我建议使用类似sqlparse的东西来处理所有奇怪的边缘情况。如果你不得不处理更高级的案例,那么从长远来看这可能是一个更好的选择。

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

来自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)]})

然后,您可以使用deloreanarrow之类的库来尝试智能地处理日期部分 - 或者只使用常规旧的dateutil。

答案 2 :(得分:1)

您可以使pyparsing解析器在匹配的内容中非常宽松,然后使用解析操作执行更严格的值检查。如果您的日期字符串都是非空白字符,这一点尤其容易。

例如,假设我们想要解析一个月的名称,但出于某种原因,我们不希望我们的解析器表达式只执行`oneOf(' 1月2月3月......等等)。我们可以放置一个占位符,它只会将一组Word字符解析为下一个不符合条件的字符(空格或标点符号)。

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)

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

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

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

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