在 pyparsing 中,当使用 infixNotation 时满足某些条件时,我可以将空格视为标记吗?

In pyparsing, can I treat whitespace as a token when certain conditions are met when using infixNotation?

我正在尝试使用 pyparsing==2.4.7 来解析具有 field:value 格式的搜索查询。

我要解析的字符串示例包括:

field1:value1
field1:value1 field2:value2
field1:value1 AND field2:value2
(field1:value1a OR field1:value1b) field2:value2
(field1:value1a | field1:value1b) & (field2:value2a | field2:value2b)

注意几点:

我已经编写了一个有效的解析器(代码基于 ),但仅适用于所有运算符都存在的情况(ANDOR):

import pyparsing as pp
from pyparsing import Word, alphas, alphanums, White, Combine, OneOrMore, Literal, oneOf 

field_name = Word(alphanums).setResultsName('field_name')

search_value = Word(alphanums + '-').setResultsName('search_value')

operator = Literal(':')

query = field_name + operator + search_value

AND = oneOf(['AND', 'and', '&', ' '])
OR = oneOf(['OR', 'or', '|'])
NOT = oneOf(['NOT', 'not', '!'])

query_expr = pp.infixNotation(query, [
    (NOT, 1, pp.opAssoc.RIGHT, ),
    (AND, 2, pp.opAssoc.LEFT, ),
    (OR, 2, pp.opAssoc.LEFT, ),
])

class ComparisonExpr:
    def __init__(self, tokens):
        self.tokens = tokens
    def __str__(self):
        return "Comparison:('field': {!r}, 'operator': {!r}, 'value': {!r})".format(*self.tokens)
    def __repr__(self):
        return self.__str__()

query.addParseAction(ComparisonExpr)

sample = "(field1:value1a | field1:value1b) & (field2:value2a | field2:value2b)"

result = query_expr.parseString(sample).asList()

from pprint import pprint
>>> pprint(result)

[[[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'),
   '|',
   Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')],
  '&',
  [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'),
   '|',
   Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]]]

但是,如果我尝试使用缺少运算符的 sample,解析器似乎会在需要运算符的位置停止:

sample = "(field1:value1a | field1:value1b) (field2:value2a | field2:value2b)"

result = query_expr.parseString(sample).asList()
from pprint import pprint
pprint(result)

[[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'),
  '|',
  Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')]]

如果没有运算符分隔项,是否有办法使空格成为“隐式 AND”?

简答:

将您对 AND 的定义替换为:

AND = oneOf(['AND', 'and', '&']) | pp.Empty()

其他一些建议:

为了更轻松地 post-parse 处理,您可能希望 Empty() 实际发出一个“&”运算符。您可以通过解析操作来做到这一点:

AND = oneOf(['AND', 'and', '&']) | pp.Empty().addParseAction(lambda: "&")

事实上,您可以将所有运算符规范化为“&”、“|”和“!”,再次跳过任何“if operator == 'AND' 或 operator == 'and' 或 ..." 代码。将您的解析操作放在整个表达式上:

AND = (oneOf(['AND', 'and', '&']) | pp.Empty()).addParseAction(lambda: "&")
OR = oneOf(['OR', 'or', '|']).addParseAction(lambda: "|")
NOT = oneOf(['NOT', 'not', '!']).addParseAction(lambda: "!")

此外,考虑到您现在接受“”等同于“&”,您应该让 pyparsing 像对待关键字一样对待您的运算符——这样如果“oregon”不是“or egon”就不会混淆。将 asKeyword 参数添加到所有 oneOf 表达式中:

AND = (oneOf(['AND', 'and', '&'], asKeyword=True)
       | pp.Empty()).addParseAction(lambda: "&")
OR = oneOf(['OR', 'or', '|'], asKeyword=True).addParseAction(lambda: "|")
NOT = oneOf(['NOT', 'not', '!'],  asKeyword=True).addParseAction(lambda: "!")

最后,当您想编写测试字符串时,您可以跳过字符串循环或捕获 ParseExceptions - 只需使用 runTests:

query_expr.runTests("""\
    (field1:value1a | field1:value1b) & (field2:value2a | field2:value2b)
    (field1:value1a | field1:value1b) (field2:value2a | field2:value2b)
    """)

将打印每个测试字符串,然后是解析结果或解析异常以及发生异常的'^':

(field1:value1a | field1:value1b) & (field2:value2a | field2:value2b)
[[[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')], '&', [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]]]
[0]:
  [[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')], '&', [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]]
  [0]:
    [Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')]
  [1]:
    &
  [2]:
    [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]

(field1:value1a | field1:value1b) (field2:value2a | field2:value2b)
[[[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')], '&', [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]]]
[0]:
  [[Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')], '&', [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]]
  [0]:
    [Comparison:('field': 'field1', 'operator': ':', 'value': 'value1a'), '|', Comparison:('field': 'field1', 'operator': ':', 'value': 'value1b')]
  [1]:
    &
  [2]:
    [Comparison:('field': 'field2', 'operator': ':', 'value': 'value2a'), '|', Comparison:('field': 'field2', 'operator': ':', 'value': 'value2b')]