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

Pyparsing: белые пространства иногда имеют значение... иногда нет

Я хотел бы создать грамматику для файла, который содержит несколько разделов (например, 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)
4b9b3361

Ответ 1

В вашем BNF содержится только CR, но вы разбираете код для завершения с использованием LF. Это предназначено? BNF поддерживает LF (Unix), CR (Mac) и CRLF (Win) EOLs:

Rule_|_Def.__|_Meaning___
CR   | %x0D  | carriage return
LF   | %x0A  | linefeed
CRLF | CR LF | Internet standard newline