以稳健的方式分析带有转义字符的分隔字符串



我想这个问题已经足够基本了,答案肯定已经存在了,但我的谷歌傅技能肯定不够。

我需要使用以下格式解析字符串:upper:lower cc ; ! comment。字符%用于转义特殊字符%:; !:字符将upperlower分隔开来。;字符终止一行。空格字符用于对cc元素进行定界。使用!介绍注释。以下字符串应按如下所示进行解析:

a:b c ;        upper="a"   lower="b" cc="c" comment=""
a%::b c ;      upper="a:"  lower="b" cc="c" comment=""
a%%:b c ; ! x  upper="a%"  lower="b" cc="c" comment=" x"
a%!:b c ; ! x  upper="a!"  lower="b" cc="c" comment=" x"
a%%%::b c ;    upper="a%:" lower="b" cc="c" comment=""

在python中处理这项任务的最具python风格(即简单、可读、优雅(和健壮的方法是什么?正则表达式合适吗?

我尝试编写一个正则表达式,该表达式使用负后向查找来检测:之前的奇数个%s,但显然后向查找不能是可变长度的。

我认为regexp不能可靠地捕获转义状态。这是一个状态机风格的解析器。

def parse_line(s):
    fields = [""]
    in_escape = False
    for i, c in enumerate(s):
        if not in_escape:
            if c == "%":  # Start of escape
                in_escape = True
                continue
            if (len(fields) == 1 and c == ":") or (len(fields) == 2 and c == " "):  # Next field
                fields.append("")
                continue
            if c == ";":  # End-of-line
                break
        fields[-1] += c  # Regular or escaped character
        in_escape = False
    return (fields, s[i + 1:])

print(parse_line("a:b c ;"))
print(parse_line("a%::b c ;"))
print(parse_line("a%%:b c ; ! x"))
print(parse_line("a%!:b c ; ! x"))
print(parse_line("a%%%::b c defgh:!:heh;"))
print(parse_line("a%;"))
print(parse_line("a%;:b!unterminated-line"))

输出

(['a', 'b', 'c '], '')
(['a:', 'b', 'c '], '')
(['a%', 'b', 'c '], ' ! x')
(['a!', 'b', 'c '], ' ! x')
(['a%:', 'b', 'c defgh:!:heh'], '')
(['a;'], '')
(['a;', 'b!unterminated-line'], '')

即retval是解析字段的2元组,以及;标记之后的行的其余部分(其可以包含也可以不包含注释(。

类似于AKX的答案,但当我看到它时,我已经准备好了。此外,方法有点不同(更容易适应不同的格式(,结果可能也会稍微干净一些。

def parse(line):
    parts = [""]
    delims = ":  ; !"
    escape = False
    for c in line:
        if escape:
            parts[-1] += c
            escape = False
        elif c == "%":
            escape = True
        elif c == delims[:1]:
            parts += [""]
            delims = delims[1:]
        else:
            parts[-1] += c
    return [p for p in parts if p] if ";" not in delims else None

lines = ["a:b c ;","a%::b c ;","a%%:b c ; ! x","a%!:b c ; ! x","a%%%::b c ;","a:b incomplete"]
for line in lines:
    print(line, "t", parse(line))

基本上,这会逐个字符地迭代行,跟踪"转义模式",并使用下一个预期的分隔符检查当前字符。

输出:

a:b c ;        ['a', 'b', 'c']
a%::b c ;      ['a:', 'b', 'c']
a%%:b c ; ! x  ['a%', 'b', 'c', ' x']
a%!:b c ; ! x  ['a!', 'b', 'c', ' x']
a%%%::b c ;    ['a%:', 'b', 'c']
a:b incomplete None

根据@MichaelButscher的评论,我使用正则表达式编写了以下解决方案:

def parse_line(line):
    parsed = re.match(r'''( (?: %. | [^:] )+ )     # capture upper
                          (?: :                    # colon delimiter
                              ( (?: %. | [^ ] )+ ) # capture lower
                          )?                       # :lower is optional
                           +                      # space delimiter(s)
                          ( (?: %. | [^ ;] )+ )    # capture cont class
                           +;                     # space delimiter(s)
                          ( .* ) s* $                 # capture comment''',
                      line, re.X)
    groups = parsed.groups(default='')
    groups = [re.sub('%(.)', r'1', elem) for elem in groups]  # unescape
    return groups

这会产生以下结果:

>>> print(parse_line("a:b c ;"))
['a', 'b', 'c', '']
>>> print(parse_line("a%::b c ;"))
['a:', 'b', 'c', '']
>>> print(parse_line("a%%:b c ; ! x"))
['a%', 'b', 'c', ' ! x']
>>> print(parse_line("a%!:b c ; ! x"))
['a!', 'b', 'c', ' ! x']

格式不正确的条目返回NoneType对象。

最新更新