pyparsing中的可选字符串段

时间:2012-06-24 19:39:14

标签: python pyparsing

我正在使用pyparsing并尝试定义两个项目如下:

identifier = Word(alphas, alphanums).setName('identifier)

database_name = Optional(identifier.setResultsName('user') + Suppress('.')) + identifier.setResultsName('database')
table_name = database_name + Suppress('.') + identifier.setResultsName('table')

这个想法是匹配table_name,它会带一个包含两个或三个段的字符串,结果如下:

mark.foo.bar
=> tokens.user = 'mark'
   tokens.database = 'foo'
   tokens.table = 'bar'

或者如果缺少第一个分段:

foo.bar
=> tokens.user = ''  #anything is acceptable: none, empty string or just plain missing
   tokens.database = 'foo'
   tokens.table = 'bar'

table_name应始终具有两个段和一个点,或三个段(两个点),如上所述。一段是不可接受的。

database_name应该有一个段(数据库)或两个(user.database)。

使用database_name工作正常的实例 - 它会在一个或两个段上匹配。但是,table_name在某些情况下失败:

# Works for three segments
mark.foo.bar
=> tokens.user = 'mark'
   tokens.database = 'foo'
   tokens.table = 'bar'

# Fails for two
foo.bar
=> Expected "." (at char 7), (line:1m col:8)

我可以看到它的作用:foo.bar已与user.database匹配,现在它正在期待表示表名的第三个块。然而,这不是我想要的。

帮助?

1 个答案:

答案 0 :(得分:5)

问题是,当你匹配前导标识符时,你不知道它是否会成为用户字段,直到你查看了所有可能的表字段。不幸的是,这意味着您无法使用其主要的可选“用户”字段定义数据库名称,您必须定义一个全面的table_name表达式,包含两个或三个字段。

以下代码显示了3个用于解决主要可选标识符歧义的选项:

  1. 首先尝试匹配完整的3字段表单,如果失败,请尝试匹配2字段表单

  2. 在匹配可选的“用户”字段时明确地预测,使用FollowedBy仅匹配“用户”,如果后跟2*(DOT+identifier)

  3. 匹配任何长度的所有以点分隔的列表,并使用解析操作来验证是否只传递了2个或3个标识符,并指定结果名称

  4. 请参阅注释以了解每个选项的实现方式。 (请注意,为了简化代码,我还将完整的expr.setResultsName('something')替换为expr('something'),我认为整体更容易阅读。)

    from pyparsing import *
    
    identifier = Word(alphas, alphanums).setName('identifier')
    DOT = Suppress('.')
    
    # Option 1 - fully specified options
    full_database_name = identifier('user') + DOT + identifier('database')
    just_database_name = identifier('database')
    table_name = (full_database_name + DOT + identifier('table') | 
                  just_database_name + DOT + identifier('table'))
    
    # Option 2 - use FollowedBy to explicitly lookahead when checking for leading user
    table_name = (Optional(identifier('user') + FollowedBy(2*(DOT+identifier)) + DOT) + 
                    identifier('database') + DOT + identifier('table'))
    
    # Option 3 - use liberally matching expression, with a parse action to assign fields
    def assignTableFields(fields):
        if len(fields) == 2:
            fields['database'],fields['table'] = fields
        elif len(fields) == 3:
            fields['user'],fields['database'],fields['table'] = fields
        else:
            raise ParseException("wrong number of fields")
    table_name = delimitedList(identifier, delim='.').setParseAction(assignTableFields)
    
    for test in ("a.b.c", "b.c"):
        print test
        print table_name.parseString(test).dump()
        print
    

    你也可能发现这个过于自由的匹配器,因为它也允许交错的空格,因此"a . b"也有资格作为有效的表名。您可以定义另一个验证解析操作,并将其添加到table_name:

    def noWhitespace(source, locn, tokens):
        if not source[locn:].startswith('.'.join(tokens)):
            raise ParseException("found whitespace between fields")
    table_name.addParseAction(noWhitespace)
    

    请参阅此解析操作,我调用addParseAction而不是setParseAction,以便保留任何现有的解析操作(在选项3的情况下),并将此新的添加到要运行的解析行动链。