Pyparsing:白色空间有时很重要...有时候不要

时间:2014-04-09 12:40:54

标签: python pyparsing

我想为包含多个部分的文件创建语法(如下面的PARAGRAPH)。

一个部分以其关键字(例如PARAGRAPH)开头,后面是标题(此处为标题),其内容在以下行中,一行内容是该部分的一行。 它就像一个带有标题,列和行的表。

在下面的示例(tablefile)中,我将限制部分包含一列和一行。

表文件的自上而下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? 什么是忽略这种最后换行符的更好方法?

另外,我想允许标签和空格在文件中的任何位置而不会受到干扰。 唯一需要的行为是使用TABPARAGRAPH启动段落内容以启动该行。 这也意味着在段落中和段落之间跳过空白行(带有制表符和空格或没有)。

因此我添加了这一行:

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没有尝试忽略空行,而是在类似makefile的语法分析器(NL = LineEnd().suppress())中对它们进行标记化。

Any python module for customized BNF parser?

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)

1 个答案:

答案 0 :(得分:2)

您的BNF仅包含CR,但您解析代码以使用LF终止。这是有意的吗? BNF supports LF(Unix),CR(Mac)和CRLF(Win)EOL:

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