【问题标题】:Parsing nested lists and returning original strings for every valid list解析嵌套列表并为每个有效列表返回原始字符串
【发布时间】:2020-06-07 04:33:06
【问题描述】:

假设我有一个字符串s = '{aaaa{bc}xx{d{e}}f}',它具有嵌套列表的结构。我想对其进行分层表示,同时能够访问与有效子列表相对应的子字符串。为简单起见,让我们忘记层次结构,我只想要一个与有效子列表相对应的子字符串列表,例如:

['{aaaa{bc}xx{d{e}}f}', '{bc}', '{d{e}}', '{e}']

使用nestedExpr,可以获得嵌套结构,其中包含所有有效的子列表:

import pyparsing as pp

s = '{aaaa{bc}xx{d{e}}f}'
not_braces = pp.CharsNotIn('{}')
expr = pp.nestedExpr('{', '}', content=not_braces)
res = expr('L0 Contents').parseString(s)
print(res.dump())

打印:

[['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']]
- L0 Contents: [['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']]
  [0]:
    ['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']
    [0]:
      aaaa
    [1]:
      ['bc']
    [2]:
      xx
    [3]:
      ['d', ['e']]
      [0]:
        d
      [1]:
        ['e']
    [4]:
      f

为了获得解析元素的原始字符串表示,我必须将它包装到pyparsing.originalTextFor()。但是,这将从结果中删除所有子列表:

s = '{aaaa{bc}xx{d{e}}f}'
not_braces = pp.CharsNotIn('{}')
expr = pp.nestedExpr('{', '}', content=not_braces)
res = pp.originalTextFor(expr)('L0 Contents').parseString(s)
print(res.dump())

打印:

['{aaaa{bc}xx{d{e}}f}']
- L0 Contents: '{aaaa{bc}xx{d{e}}f}'

实际上,originalTextFor() 包装器扁平化了其中的所有内容。

问题。是否有替代originalTextFor() 的方法来保留其子解析元素的结构? (如果有一个不可丢弃的类似物会很好,它可以用于为已解析的子表达式创建命名标记)

请注意,scanString() 只会给我 0 级子列表,不会查看内部。我想,我可以使用setParseAction(),但是ParserElement 的内部操作模式没有记录,我还没有机会深入研究源代码。谢谢!

更新1.有点相关:https://stackoverflow.com/a/39885391/11932910https://stackoverflow.com/a/17411455/11932910

【问题讨论】:

  • 你感兴趣的输出是什么?
  • 我希望您不必深入研究 pyparsing 的内部结构来获得这种东西。解析动作可以操纵解析结果,然后将它们按修改后返回给 pyparsing。此外,您可以在pyparsing-docs.readthedocs.io/en/pyparsing_2.4.6/pyparsing.html 了解更多关于类和助手的广度
  • @Roy2012 我感兴趣的输出在第一个代码块引用中。

标签: python nested pyparsing


【解决方案1】:

不要使用originalTextFor,而是将nestedExpr 表达式包装在locatedExpr 中:

import pyparsing as pp
parser = pp.locatedExpr(pp.nestedExpr('{','}'))

locatedExpr 将返回一个 3 元素 ParseResults:

  • 开始位置
  • 解析值
  • 结束位置

然后,您可以将解析操作附加到此解析器以修改已解析的标记,并添加您自己的 original_string 命名结果,其中包含从输入字符串中分割出来的原始文本:

def extract_original_text(st, loc, tokens):
    start, tokens[:], end = tokens[0]
    tokens['original_string'] = st[start:end]
parser.addParseAction(extract_original_text)

现在使用这个解析器来解析和转储结果:

result = parser.parseString(s)
print(result.dump())

打印:

['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']
- original_string: '{aaaa{bc}xx{d{e}}f}'

并使用以下命令访问original_string 结果:

print(result.original_string)

编辑 - 如何将 original_string 附加到每个嵌套子结构

要维护子结构上的原始字符串,需要比 nested_expr 完成更多的工作。您几乎必须实现自己的递归解析器。

要实现您自己的nested_expr 版本,您将从以下内容开始:

LBRACE, RBRACE = map(pp.Suppress, "{}")
expr = pp.Forward()

term = pp.Word(pp.alphas)
expr_group = pp.Group(LBRACE + expr + RBRACE)
expr_content = term | expr_group

expr <<= expr_content[...]

print(expr.parseString(sample).dump())

这将转储解析结果,没有“original_string”名称:

{aaaa{bc}xx{d{e}}f}
[['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']]
[0]:
  ['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']
  [0]:
    aaaa
  [1]:
    ['bc']
  [2]:
    xx
  [3]:
    ['d', ['e']]
    [0]:
      d
    [1]:
      ['e']
  [4]:
    f

要添加“original_string”名称,我们首先将 Group 更改为 locatedExpr 包装器。

expr_group = pp.locatedExpr(LBRACE + expr + RBRACE)

这会将开始和结束位置添加到每个嵌套子组(使用 nestedExpr 时您无法访问)。

{aaaa{bc}xx{d{e}}f}
[[0, 'aaaa', [5, 'bc', 9], 'xx', [11, 'd', [13, 'e', 16], 17], 'f', 19]]
[0]:
  [0, 'aaaa', [5, 'bc', 9], 'xx', [11, 'd', [13, 'e', 16], 17], 'f', 19]
  - locn_end: 19
  - locn_start: 0
  - value: ['aaaa', [5, 'bc', 9], 'xx', [11, 'd', [13, 'e', 16], 17], 'f']
    [0]:
      aaaa
    [1]:
      [5, 'bc', 9]
      - locn_end: 9
      - locn_start: 5
      - value: ['bc']
...

我们的解析操作现在也更加复杂了。

def extract_original_text(st, loc, tokens):
    # pop/delete names and list items inserted by locatedExpr
    # (save start and end locations to local vars)
    tt = tokens[0]
    start = tt.pop("locn_start")
    end = tt.pop("locn_end")
    tt.pop("value")
    del tt[0]
    del tt[-1]

    # add 'original_string' results name
    orig_string = st[start:end]
    tt['original_string'] = orig_string

expr_group.addParseAction(extract_original_text)

通过此更改,您现在将获得以下结构:

{aaaa{bc}xx{d{e}}f}
[['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']]
[0]:
  ['aaaa', ['bc'], 'xx', ['d', ['e']], 'f']
  - original_string: '{aaaa{bc}xx{d{e}}f}'
  [0]:
    aaaa
  [1]:
    ['bc']
    - original_string: '{bc}'
  [2]:
    xx
  [3]:
    ['d', ['e']]
    - original_string: '{d{e}}'
    [0]:
      d
    [1]:
      ['e']
      - original_string: '{e}'
  [4]:
    f

注意:当前版本的 ParseResults.dump 存在一个限制,它只显示键 子项,但不能同时显示两者 - 此输出需要修复以消除该限制,将在下一个发布pyparsing 版本。但是即使 dump() 没有显示这些子结构,它们也存在于你的实际结构中,你可以看到如果你打印出结果的 repr:

print(repr(result[0]))

(['aaaa', (['bc'], {'original_string': '{bc}'}), 'xx', (['d', (['e'], {'original_string': '{e}'})], {'original_string': '{d{e}}'}), 'f'], {'original_string': '{aaaa{bc}xx{d{e}}f}'})

【讨论】:

  • 是否可以递归地累积原始子字符串,以便在结果中得到'{aaaa{bc}xx{d{e}}f}'以及'{bc}'(等等)?
猜你喜欢
  • 1970-01-01
  • 2016-02-21
  • 2012-12-11
  • 2013-12-16
  • 2019-11-04
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
  • 2018-05-14
相关资源
最近更新 更多