如何摆脱尾随空格

How to get rid of trailing whitespaces

我正在尝试在此处的 pyparsing 中完成 ISC 样式 (Bind9/DHCP) 配置解析器(在 GitHub、Google 等搜索了很长时间之后).

ISC 风格的配置文件具有以下古怪的文本属性:

最接近 ISC 风格配置语法(也在 pyparsing 中)的编码风格是 NGINX,我在 there on GitHub 上看过。但这将意味着放弃 pyparsing 的自动空白处理,因为如果可能的话,我想保留它。

当我开始执行输入模糊单元测试时,我已经制作的 PyParsing 语法语法树现在处于不稳定状态。

[['server', 'example.com']]
[['server', 'example.com ']]
[['server', 'example.com      ']]
[['server', 'example.com']]
[['server', 'example.com ']]
[['server', 'example.com     ']]
[['server', 'example.com    ']]
[['server', 'example.com      ']]
[['server', 'example.com                     ']]
['options', ['server', 'example.com     '], ['server2', 'example2.net   ']]

我有语法代码片段:

lbrack = Literal("{").suppress()
rbrack = Literal("}").suppress()
period = Literal(".")
semicolon = Literal(";").suppress()

domain_name = Word(srange("[0-9A-Za-z]"), min=1, max=63)
domain_name.setName("domain")
fqdn = originalTextFor(domain_name - \
                       originalTextFor(period - \
                                       domain_name) * (0, 16) - \
                       Optional(period))
fqdn.setName("fully-qualified domain name")
orig_fqdn = originalTextFor(fqdn).setName('FQDN')
options_server = Group(Keyword("server") - fqdn - semicolon)
options_server2 = Group(Keyword("server2") - fqdn - semicolon)
options_group = Optional(options_server) & \
                      Optional(options_server2) \

我仍然无法摆脱尾随的空格。

已尝试以下方法无济于事:

iwsp = Optional(Word("[ \t]")).suppress() # Ignore WhiteSPace
options_server = Group(Keyword("server") - fqdn - iwsp - semicolon)

我做错了什么?

一个完整的工作 Python 片段包含在下面:

#!/usr/bin/env python3

from pyparsing import Literal, Word, srange, \
    originalTextFor, Optional, ParseException, \
    OneOrMore, Keyword, ZeroOrMore, \
    ParseSyntaxException, Group

lbrack = Literal("{").suppress()
rbrack = Literal("}").suppress()
period = Literal(".")
semicolon = Literal(";").suppress()

domain_name = Word(srange("[0-9A-Za-z]"), min=1, max=63)
domain_name.setName("domain")
fqdn = originalTextFor(domain_name - \
                       originalTextFor(period - \
                                       domain_name) * (0, 16) - \
                       Optional(period))
fqdn.setName("fully-qualified domain name")
orig_fqdn = originalTextFor(fqdn).setName('FQDN')
options_server = Group(Keyword("server") - fqdn - semicolon)
options_server2 = Group(Keyword("server2") - fqdn - semicolon)
options_group = Optional(options_server) & \
                      Optional(options_server2) \
                      # | had a bunch of other options commented out
options_clause = Keyword("options") - \
                     lbrack - \
                     options_group - \
                     rbrack - \
                     semicolon
statement = options_clause # | had a bunch of other clauses commented out
isc_style_syntax = statement


def parse_me(parse_element, test_data):

    greeting = parse_element.parseString(test_data, parseAll=True)
    greeting.pprint(indent=4)


if __name__ == '__main__':
    parse_me(options_server, "server example.com;")
    parse_me(options_server, "server example.com ;")
    parse_me(options_server, "server example.com\t;")
    parse_me(options_server, "server\texample.com;")
    parse_me(options_server, "server\texample.com ;")
    parse_me(options_server, "server\texample.com\t;")
    parse_me(options_server, "server     example.com    ;")
    parse_me(options_server, "server\t \texample.com \t ;")
    parse_me(options_server, "server\t\t\texample.com\t\t\t;")
    parse_me(statement, "options { server\t \texample.com \t;\n server2\t\t\t\t\t\t\t\t\t\t\t\t example2.net\t;\n}\n ;") 

问题是:

fqdn = originalTextFor(domain_name - \
                   originalTextFor(period - \
                                   domain_name) * (0, 16) - \
                   Optional(period))

由于存在重复和尾随的 Optional 位,originalTextFor 似乎一直在读取和拉入字符,直到它在重复时实际失败。但是,如果您将其更改为:

fqdn = Combine(domain_name - \
                   originalTextFor(period + \
                                   domain_name) * (0, 16) - \
                   Optional(period))

然后你的 fqdn 将只包含非空白字符。

ParserElements 还带有自己的 runTests 方法,可以更轻松地为多个输入编写快速测试:

options_server.runTests("""
    server example.com;
    server example.com   ;
    server example.com   .z;
    server example.com.;
""")

将打印:

server example.com;
[['server', 'example.com']]
[0]:
  ['server', 'example.com']


server example.com   ;
[['server', 'example.com']]
[0]:
  ['server', 'example.com']


server example.com   .z;
                     ^(FATAL)
FAIL: Expected ";" (at char 21), (line:1, col:22)


server example.com.;
                   ^(FATAL)
FAIL: Expected domain (at char 19), (line:1, col:20)

(您的所有制表符测试用例并没有真正被检查,因为默认情况下 pyparsing 在开始解析之前将制表符扩展为空格。您必须调用 expr.parseWithTabs() 来禁用此功能。)