解析嵌套列表并为每个有效列表返回原始字符串

Parsing nested lists and returning original strings for every valid list

假设我有一个字符串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. 有点相关:

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

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

locatedExpr 将 return 一个 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}'})