保留嵌套表达式中的换行符

3

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个回答

3

我直到几分钟前才看到你的答案,而我已经想出了这种方法:

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']]

我认为最好的答案来自软件包作者本人! :) 如果我的建议有点笨拙,请原谅,但我能否就这个问题问一下; 为什么在 “body” 的定义中使用“name”?我承认从我的问题中并不完全清楚,但我真正想知道的是括号内的原始内容,最好不受任何解析规则或标记化器的影响,以便稍后可以单独解析它们(根据父元素的内容可能会使用不同的解析规则)。 - Jonathan H
1
为了匹配任何内容,在name的位置上,你可能会使用类似于pp.Word(pp.printables, excludeChars="{}")的东西。你还可能需要使用pp.originalTextFor进行包装,以获取原始字符串内容。欢迎来到pyparsing! - PaulMcG

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. 原始实现在默认的content中使用ParserElement.DEFAULT_WHITE_CHARS,这似乎是因为懒惰而导致的;它只在ParserElement类本身之外使用了五次,其中四次在函数nestedExpr中使用(另一个用法是在LineEnd中,并手动删除了\n)。不过,我们可以很容易地向nestedExpr添加一个命名参数,虽然公平地说,我们也可以使用ParserElement.setDefaultWhitespaceChars来实现同样的效果。

  2. 第二个问题是,默认情况下,在content表达式本身中忽略空格字符,使用额外的解析操作lambda t:t[0].strip(),其中strip没有输入,意味着它删除所有Unicode空格字符。我个人认为,在内容中不忽略任何空格更有意义,而是在结果中有选择地去除它们。出于这个原因,我从原始实现中删除了CharsNotIn标记,并引入了参数stripchars,默认为string.whitespace

当然很乐意接受任何建设性的批评。


1
感谢您付出努力编写一些有效的补丁代码 - 我通常会收到有关我应该对pyparsing进行哪些更改的建议,但很少得到具体的代码补丁/实现。我认为您对nestedExpr的解释与我的略有不同,并尝试通过支持content参数来适应不同的嵌套规则,默认情况下为0个或多个以空格分隔的单词。如果给定了content表达式,则可能需要删除自动strip()解析操作,并让调用者在给定的参数上设置必要的strip、join或其他解析操作。 - PaulMcG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接