Подтвердить что ты не робот

Элегантный синтаксический анализ текстового файла

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

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

Транскрипты создаются по адресу www.providesupport.com и отправляются по электронной почте в учетную запись, после чего я извлекаю вложенное текстовое вложение из электронной почты.

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

Вот пример файла расшифровки:

Chat Transcript

Visitor: Random Website Visitor 
Operator: Milton
Company: Initech
Started: 16 Oct 2008 9:13:58
Finished: 16 Oct 2008 9:45:44

Random Website Visitor: Where do i get the cover sheet for the TPS report?
* There are no operators available at the moment. If you would like to leave a message, please type it in the input field below and click "Send" button
* Call accepted by operator Milton. Currently in room: Milton, Random Website Visitor.
Milton: Y-- Excuse me. You-- I believe you have my stapler?
Random Website Visitor: I really just need the cover sheet, okay?
Milton: it not okay because if they take my stapler then I'll, I'll, I'll set the building on fire...
Random Website Visitor: oh i found it, thanks anyway.
* Random Website Visitor is now off-line and may not reply. Currently in room: Milton.
Milton: Well, Ok. But… that the last straw.
* Milton has left the conversation. Currently in room:  room is empty.

Visitor Details
---------------
Your Name: Random Website Visitor
Your Question: Where do i get the cover sheet for the TPS report?
IP Address: 255.255.255.255
Host Name: 255.255.255.255
Referrer: Unknown
Browser/OS: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)
4b9b3361

Ответ 1

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

Ответ 2

С помощью Perl вы можете использовать Parse::RecDescent

Это просто, и ваша грамматика будет поддерживаться позже.

Ответ 3

Возможно, вы захотите рассмотреть полный генератор синтаксического анализатора.

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

Они особенно недостаточны, если имеет смысл контекст подстроки.

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

Regexes отличные и все, но если вам нужен синтаксический анализатор, они не заменяют.

Ответ 4

Здесь два анализатора на основе lepl библиотека генератора парсеров. Оба они дают одинаковый результат.

from pprint import pprint
from lepl import AnyBut, Drop, Eos, Newline, Separator, SkipTo, Space

# field = name , ":" , value
name, value = AnyBut(':\n')[1:,...], AnyBut('\n')[::'n',...]    
with Separator(~Space()[:]):
    field = name & Drop(':') & value & ~(Newline() | Eos()) > tuple

header_start   = SkipTo('Chat Transcript' & Newline()[2])
header         = ~header_start & field[1:] > dict
server_message = Drop('* ') & AnyBut('\n')[:,...] & ~Newline() > 'Server'
conversation   = (server_message | field)[1:] > list
footer_start   = 'Visitor Details' & Newline() & '-'*15 & Newline()
footer         = ~footer_start & field[1:] > dict
chat_log       = header & ~Newline() & conversation & ~Newline() & footer

pprint(chat_log.parse_file(open('chat.log')))

Более строгий парсер

from pprint import pprint
from lepl import And, Drop, Newline, Or, Regexp, SkipTo

def Field(name, value=Regexp(r'\s*(.*?)\s*?\n')):
    """'name , ":" , value' matcher"""
    return name & Drop(':') & value > tuple

Fields = lambda names: reduce(And, map(Field, names))

header_start   = SkipTo(Regexp(r'^Chat Transcript$') & Newline()[2])
header_fields  = Fields("Visitor Operator Company Started Finished".split())
server_message = Regexp(r'^\* (.*?)\n') > 'Server'
footer_fields  = Fields(("Your Name, Your Question, IP Address, "
                         "Host Name, Referrer, Browser/OS").split(', '))

with open('chat.log') as f:
    # parse header to find Visitor and Operator names
    headers, = (~header_start & header_fields > dict).parse_file(f)
    # only Visitor, Operator and Server may take part in the conversation
    message = reduce(Or, [Field(headers[name])
                          for name in "Visitor Operator".split()])
    conversation = (message | server_message)[1:]
    messages, footers = ((conversation > list)
                         & Drop('\nVisitor Details\n---------------\n')
                         & (footer_fields > dict)).parse_file(f)

pprint((headers, messages, footers))

Вывод:

({'Company': 'Initech',
  'Finished': '16 Oct 2008 9:45:44',
  'Operator': 'Milton',
  'Started': '16 Oct 2008 9:13:58',
  'Visitor': 'Random Website Visitor'},
 [('Random Website Visitor',
   'Where do i get the cover sheet for the TPS report?'),
  ('Server',
   'There are no operators available at the moment. If you would like to leave a message, please type it in the input field below and click "Send" button'),
  ('Server',
   'Call accepted by operator Milton. Currently in room: Milton, Random Website Visitor.'),
  ('Milton', 'Y-- Excuse me. You-- I believe you have my stapler?'),
  ('Random Website Visitor', 'I really just need the cover sheet, okay?'),
  ('Milton',
   "it not okay because if they take my stapler then I'll, I'll, I'll set the building on fire..."),
  ('Random Website Visitor', 'oh i found it, thanks anyway.'),
  ('Server',
   'Random Website Visitor is now off-line and may not reply. Currently in room: Milton.'),
  ('Milton', "Well, Ok. But… that the last straw."),
  ('Server',
   'Milton has left the conversation. Currently in room:  room is empty.')],
 {'Browser/OS': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; InfoPath.1; .NET CLR 2.0.50727)',
  'Host Name': '255.255.255.255',
  'IP Address': '255.255.255.255',
  'Referrer': 'Unknown',
  'Your Name': 'Random Website Visitor',
  'Your Question': 'Where do i get the cover sheet for the TPS report?'})

Ответ 5

Построить парсер? Я не могу решить, достаточно ли для вас достаточно данных, но это может быть интересно изучить.

Ответ 6

Использование многострочных, запрограммированных регулярных выражений может несколько смягчить проблему обслуживания. Попробуйте избежать однострочного регулярного выражения!

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

visitor = text.find(/Visitor:(.*)/)
operator = text.find(/Operator:(.*)/)
body = text.find(/whatever....)

вместо

text.match(/Visitor:(.*)\nOperator:(.*)...whatever to giant regex/m) do
  visitor = $1
  operator = $2
  etc.
end

Затем это упрощает изменение того, как анализируется какой-либо конкретный элемент. Что касается синтаксического анализа файла со многими "блоками чата", просто простое одно регулярное выражение, которое соответствует одному блоку чата, перебирает текст и передает данные сопоставления из этой группы в другие группы.

Это, очевидно, повлияет на производительность, но если вы не обрабатываете огромные файлы, я бы не беспокоился.

Ответ 7

Рассмотрим использование Ragel http://www.complang.org/ragel/

То, что власть mounrel под капотом. Разбор строки несколько раз замедляет процесс.

Ответ 8

Я использовал библиотеку классов PyParsing Paul McGuire, и я продолжаю ее впечатлять тем, что она хорошо документирована, легко запускается, и правила легко настраиваются и поддерживаются. BTW, правила выражаются в вашем коде python. Разумеется, файл журнала имеет достаточную регулярность для синтаксического анализа каждой строки как отдельного блока.

Ответ 9

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