Я хотел бы создать грамматику для файла, который содержит несколько разделов (например, PARAGRAPH ниже).
Раздел начинается с его ключевого слова (например, PARAGRAPH), за ним следует заголовок (заголовок здесь) и имеет его содержимое в следующих строках, одна строка содержимого - это строка раздела. Как и в случае с таблицей с заголовком, столбцами и строками.
В приведенном ниже примере (файл таблицы) я ограничу разделы, чтобы иметь один столбец и одну строку.
Верхний BNF файла таблицы:
tablefile := paragraph*
paragraph := PARAGRAPH title CR
TAB content
title, content := \w+
грамматика Pyparsing:
Поскольку мне нужны разрывы строк и табуляция, которые нужно обрабатывать, мне нужно будет задать пробелы по умолчанию для ".".
def grammar():
'''
Bottom-up grammar definition
'''
ParserElement.setDefaultWhitespaceChars(' ')
TAB = White("\t").suppress()
CR = LineEnd().setName("Carriage Return").suppress()
PARAGRAPH = 'PARAGRAPH'
title = Word(alphas)
content = Word(alphas)
paragraph = (PARAGRAPH + title + CR
+ TAB + content)
tablefile = OneOrMore(paragraph)
tablefile.parseWithTabs()
return tablefile
Применение к примерам
Этот фиктивный пример легко сопоставляется:
PARAGRAPH someTitle
thisIsContent
Это другое меньше:
PARAGRAPH someTitle
thisIsContent
PARAGRAPH otherTitle
thisIsOtherContent
Он ждет PARAGRAPH
сразу после первого содержимого и наткнется на разрыв строки (помните setDefaultWhitespaceChars(' ')
).
Я вынужден добавить CR?
в конце PARAGRAPH
?
Что было бы лучшим способом игнорировать такие последние разрывы строк?
Кроме того, я хочу, чтобы вкладки и пробелы были где угодно в файле без помех.
Единственное необходимое поведение - начать содержимое абзаца с помощью TAB
и PARAGRAPH
, чтобы начать строку.
Это также означало бы пропускать пустые строки (с вкладками и пробелами или ничего) внутри и между абзацами.
Таким образом, я добавил эту строку:
tablefile.ignore(LineStart() + ZeroOrMore(White(' \t')) + LineEnd())
Но каждое требование, которое я только что выставил, похоже, противоречит моей необходимости устанавливать стандартные пробелы на ' '
и помещать меня в тупик.
В самом деле, это приведет к тому, что все сломается:
tablefile.ignore(CR)
tablefile.ignore(TAB)
Клей PARAGRAPH и TAB к началу строки
Если я хочу, чтобы \t
игнорировался как везде, где в тексте, но в начале строк.
Мне придется добавить их к символам пробела по умолчанию.
Таким образом, я нашел способ запретить любой символ пробела в начале строки.
Используя метод leaveWhitespace
. Этот метод сохраняет пробелы, с которыми он сталкивается, прежде чем сопоставить токен.
Следовательно, я могу приклеить несколько токенов к началу строки.
ParserElement.setDefaultWhitespaceChars('\t ')
SOL = LineStart().suppress()
EOL = LineEnd().suppress()
title = Word()
content = Word()
PARAGRAPH = Keyword('PARAGRAPH').leaveWhitespace()
TAB = Literal('\t').leaveWhitespace()
paragraph = (SOL + PARAGRAPH + title + EOL
+ SOL + TAB + content + EOL)
С помощью этого решения я решил проблему с TAB, где бы вы ни находились в тексте.
Разделение абзацев
Я получил решение PaulMcGuire (delimitedList
) после некоторого раздумья.
И я столкнулся с некоторыми проблемами.
В самом деле, вот два разных способа объявления разделителей строк между двумя абзацами. На мой взгляд, они должны быть эквивалентными. На практике это не так?
Краш-тест (не забудьте изменить пробелы с вкладками, если вы запустите его):
PARAGRAPH titleone
content1
PARAGRAPH titletwo
content2
Общая часть между двумя примерами:
ParserElement.setDefaultWhitespaceChars('\t ')
SOL = LineStart().suppress()
EOL = LineEnd().suppress()
title = Word()
content = Word()
PARAGRAPH = Keyword('PARAGRAPH').leaveWhitespace()
TAB = Literal('\t').leaveWhitespace()
Первый пример, рабочий:
paragraph = (SOL + PARAGRAPH + title + EOL
+ SOL + TAB + content + EOL)
tablefile = ZeroOrMore(paragraph)
Второй пример, не работает:
paragraph = (SOL + PARAGRAPH + title + EOL
+ SOL + TAB + content)
tablefile = delimitedList(paragraph, delim=EOL)
Разве они не должны быть эквивалентными? Второе исключение:
Expected end of text (at char 66), (line:4, col:1)
Это не большая проблема для меня, так как я могу, наконец, отступить, чтобы положить EOL в конце каждого абзаца, как в моей грамматике. Но я хотел подчеркнуть этот момент.
Игнорирование пустой строки, содержащей пробелы
Другим требованием, которое у меня было, было игнорировать пустые строки, содержащие пробелы (' \t'
).
Простая грамматика для этого была бы:
ParserElement.setDefaultWhitespaceChars(' \t')
SOL = LineStart().suppress()
EOL = LineEnd().suppress()
word = Word('a')
entry = SOL + word + EOL
grammar = ZeroOrMore(entry)
grammar.ignore(SOL + EOL)
В конце файл может содержать по одному слову в строке с любым пробелом в любом месте. И он должен игнорировать пустые строки.
К счастью, это так. Но это не влияет на объявление объявлений по умолчанию. И пустая строка, содержащая пробелы или вкладки, заставит синтаксический анализатор возбуждать исключение синтаксического анализа.
Такое поведение абсолютно не та, которую я ожидал. Это указано? Есть ли ошибка при этой простой попытке?
Я вижу в этом потоке, что PaulMcGuire не пытался игнорировать пустые строки, а вместо этого их жеменовать в make файле грамматический синтаксический анализатор (NL = LineEnd().suppress()
).
Любой модуль python для настроенного парсера BNF?
makefile_parser = ZeroOrMore( symbol_assignment
| task_definition
| NL )
Единственное решение, которое у меня есть сейчас, - это предварительная обработка файла и удаление пробелов, содержащихся в пустой строке, поскольку pyparsing правильно игнорирует пустую строку без пробелов в ней.
import os
preprocessed_file = os.tmpfile()
with open(filename, 'r') as file:
for line in file:
# Use rstrip to preserve heading TAB at start of a paragraph line
preprocessed_file.write(line.rstrip() + '\n')
preprocessed_file.seek(0)
grammar.parseFile(preprocessed_file, parseAll=True)