保留nestedExpr中的换行符

时间:2017-04-16 17:24:55

标签: python newline pyparsing

nestedExpr是否可以保留换行符?

这是一个简单的例子:

import pyparsing as pp

# Parse expressions like: \name{body}
name = pp.Word( pp.alphas )
body = pp.nestedExpr( '{', '}' )
expr = '\\' + name('name') + body('body')

# Example text to parse
txt = '''
This \works{fine}, but \it{
    does not
    preserve newlines
}
'''

# Show results
for e in expr.searchString(txt):
    print 'name: ' + e.name
    print 'body: ' + str(e.body) + '\n'

输出:

name: works
body: [['fine']]

name: it
body: [['does', 'not', 'preserve', 'newlines']]

正如您所看到的,尽管正文中有换行符,但第二个表达式(\it{ ...)的正文仍被解析,但我希望结果将每一行存储在一个单独的子数组中。这个结果使得无法区分身体内容与单行与多行。

2 个答案:

答案 0 :(得分:2)

直到几分钟前我才回答你的答案,我已经提出了这个方法:

body = pp.nestedExpr( '{', '}', content = (pp.LineEnd() | name.setWhitespaceChars(' ')))

body更改为此定义会产生以下结果:

name: works
body: [['fine']]

name: it
body: [['\n', 'does', 'not', '\n', 'preserve', 'newlines', '\n']]

编辑:

等等,如果您想要的是单独的行,那么这可能更符合您的要求:

single_line = pp.OneOrMore(name.setWhitespaceChars(' ')).setParseAction(' '.join)
multi_line = pp.OneOrMore(pp.Optional(single_line) + pp.LineEnd().suppress())
body = pp.nestedExpr( '{', '}', content = multi_line | single_line )

给出了:

name: works
body: [['fine']]

name: it
body: [['does not', 'preserve newlines']]

答案 1 :(得分:0)

此扩展(基于nestedExpr版本2.1.10的代码)的行为更接近于我希望返回的“嵌套表达式”:

import string
from pyparsing import *

defaultWhitechars = string.whitespace
ParserElement.setDefaultWhitespaceChars(defaultWhitechars)

def fencedExpr( opener="(", closer=")", content=None, ignoreExpr=None, stripchars=defaultWhitechars ):

    if content is None:
        if isinstance(opener,basestring) and isinstance(closer,basestring):
            if len(opener) == 1 and len(closer)==1:
                if ignoreExpr is not None:
                    content = Combine(OneOrMore( ~ignoreExpr + CharsNotIn(opener+closer,exact=1)))
                else:
                    content = empty.copy() + CharsNotIn(opener+closer)
            else:
                if ignoreExpr is not None:
                    content = OneOrMore( ~ignoreExpr + ~Literal(opener) + ~Literal(closer))
                else:
                    content = OneOrMore( ~Literal(opener) + ~Literal(closer) )
        else:
            raise ValueError("opening and closing arguments must be strings if no content expression is given")

    if stripchars is not None:
        content.setParseAction(lambda t:t[0].strip(stripchars))

    ret = Forward()
    if ignoreExpr is not None:
        ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) )
    else:
        ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content )  + Suppress(closer) )
    ret.setName('nested %s%s expression' % (opener,closer))
    return ret

恕我直言,它解决了一些问题:

  1. 原始实现使用默认ParserElement.DEFAULT_WHITE_CHARS中的content,这似乎是出于懒惰;它只在ParserElement类本身之外使用了五次,其中四次在函数nestedExpr中(另一种用法在LineEnd中,并且它手动删除\n)。对nestedExpr添加命名参数很容易,但为了公平起见,我们也可以使用ParserElement.setDefaultWhitespaceChars来实现相同的目标。

  2. 第二个问题是,默认情况下,content表达式本身会忽略空白字符,并附加解析操作lambda t:t[0].strip(),其中调用strip而不输入,这意味着它{ {3}}。我个人认为更有意义的是不要忽略内容中的任何空格,而是在结果中有选择地去除它们。出于这个原因,我在原始实现中删除了带有CharsNotIn的令牌,并引入了默认为stripchars的{​​{1}}参数。

  3. 很高兴对此采取任何建设性的批评。