Создайте простой синтаксический анализатор, способный анализировать различные форматы даты с помощью PyParse.

Я создаю простой синтаксический анализатор, который принимает запрос, подобный следующему: «показать коммиты fizi с 01.01.2010 по 02.11.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

Я хотел бы, чтобы даты были более общими. Это означает, что пользователь может указать 20 января 2010 г. или другой формат даты. Я нашел хороший онлайн-анализ даты, который делает именно это. Он принимает дату в виде строки, а затем анализирует ее. Итак, что мне осталось, так это передать этой функции строку даты, которую я получаю от своего синтаксического анализатора. Как мне заняться токенизацией и захватом двух строк даты. На данный момент он фиксирует только формат 'y/m/d'. Есть ли способ просто получить всю строку независимо от того, как она отформатирована. Что-то вроде захвата слова сразу после ключевых слов и . Любая помощь приветствуется.


person Fizi    schedule 23.01.2015    source источник
comment
Стоит взглянуть на dateuti, а затем использовать dateutil.parser.parse на токене, который вы извлек. Также может быть полезно — в зависимости от остальной части грамматики — использовать строки в кавычках, такие как from "Jan 20" to "Apr 5" или подобные, чтобы сделать токенизацию немного более явной, где требуется более гибкий (или неоднозначный) синтаксический анализ.   -  person Jon Clements♦    schedule 23.01.2015
comment
Проблема, с которой я сейчас сталкиваюсь, заключается в извлечении самого токена. Я не знаю, как извлечь строку даты, которая может быть в разных форматах. Если я смогу это сделать, я смогу подключить строку к функции, которая анализирует все форматы даты.   -  person Fizi    schedule 23.01.2015
comment
stackoverflow.com/a/28092019/975114   -  person Amit Verma    schedule 23.01.2015


Ответы (3)


Простой подход заключается в том, чтобы указать дату. Грубый пример выглядит примерно так, но вам нужно будет приспособиться к вашей текущей грамматике, если это необходимо:

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))
person Jon Clements♦    schedule 23.01.2015
comment
Спасибо. Это казалось самым простым подходом к тому, что я пытался выполнить. - person Fizi; 26.01.2015

Я предлагаю использовать что-то вроде 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)]})

Затем вы можете использовать такие библиотеки, как delorean или стрелка, которые пытаются разумно обрабатывать часть даты - или просто использовать обычный старый dateutil.

person Mali Akmanalp    schedule 23.01.2015
comment
Мои запросы не являются операторами sql. Я намерен сделать его более естественным языком и, следовательно, использовать PyParse, но я рассмотрю sqlparse - person Fizi; 23.01.2015
comment
@matsjoyce: не был уверен в этикете, спасибо. Физи: а, понятно. Редактирование ответа. - person Mali Akmanalp; 23.01.2015
comment
Спасибо за ответ. Если я не ошибаюсь в вашем коде, то я думаю, что он будет анализировать даты только в формате «11/2/2006», но у меня также могут быть «1,07 января» или «23 января 2014 года». Вы и другие авторы здесь дали мне достаточно информации, чтобы решить эту проблему, так что еще раз спасибо :) - person Fizi; 26.01.2015

Вы можете сделать синтаксический анализатор pyparsing очень мягким в том, что он соответствует, а затем сделать действие синтаксического анализа более строгой проверкой значений. Это особенно легко сделать, если ваши строки даты состоят из символов, отличных от пробелов.

Например, предположим, что мы хотели проанализировать название месяца, но по какой-то причине не хотим, чтобы наше выражение анализатора просто делало `oneOf('Январь, Февраль, Март ... и т. д.'). Мы могли бы добавить заполнитель, который будет просто анализировать группу символов 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)

(После того, как вы закончите тестирование, вы можете удалить оператор печати из середины действия синтаксического анализа — я просто включил его, чтобы показать, что он вызывается в процессе синтаксического анализа.)

Если вы можете обойтись без формата даты с разделителями-пробелами, вы можете написать свой синтаксический анализатор как:

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

и тогда вы могли бы принять 1/1/2001, 29-10-1929 и так далее. Опять же, вы также будете сопоставлять такие строки, как 32237--/234//234/7, очевидно, недопустимую дату, поэтому вы можете написать действие проверки правильности синтаксического анализа, чтобы проверить действительность строки. В действии синтаксического анализа вы можете реализовать собственную логику проверки или обратиться к внешней библиотеке. (Вам придется с осторожностью относиться к таким датам, как «4/3/2013», если вы терпимо относитесь к разным локали, поскольку существует множество вариантов выбора «сначала месяц» и «сначала дата», и эта строка может легко означать 3 апреля или 4 марта.) Вы также можете сделать так, чтобы действие синтаксического анализа выполняло фактическое преобразование за вас, так что при обработке проанализированных токенов строка будет фактической датой и временем Python.

person PaulMcG    schedule 23.01.2015
comment
Рад, что вы стали авторитетным источником... Я рассматривал возможность захвата .skipTo(ignore=False) между токенами from и to, но решил, что проще заставить пользователя ввести строку в кавычках, которую можно бросить на dateutil.parser.parse - person Jon Clements♦; 24.01.2015
comment
Большое спасибо за подробный ответ. - person Fizi; 26.01.2015