【问题标题】:Parse delimited string with escape characters in a robust way以稳健的方式解析带有转义字符的分隔字符串
【发布时间】:2020-03-05 17:00:20
【问题描述】:

我想这个问题足够基本,答案肯定已经存在,但我的 google-fu 技能一定是欠缺的。

我需要解析以下格式的字符串: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 中完成这项任务的最 Pythonic(即简单、可读、优雅)和健壮的方法是什么?正则表达式合适吗?

我尝试编写一个正则表达式,该表达式使用否定的lookbehind 来检测: 之前的奇数个%s,但显然lookbehinds 不能具有可变长度。

【问题讨论】:

标签: python string parsing


【解决方案1】:

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

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 元组,以及 ; 标记之后的行的其余部分(可能包含也可能不包含注释)。

【讨论】:

    【解决方案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
    

    【讨论】:

    • 输出不正确。 a%%:b 应该被解析为 a%b
    • @reynoldsnlp 啊,是的,在上次更改之前确实有效。再次修复。
    【解决方案3】:

    根据@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 对象。

    【讨论】:

      猜你喜欢
      • 2016-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-23
      相关资源
      最近更新 更多