解析python中的字符串:如何在忽略引号内的换行符的同时拆分换行符

时间:2014-06-03 15:04:16

标签: python regex parsing

我有一个需要在python中解析的文本。

这是一个字符串,我想把它拆分成一个行列表, 但是,如果换行符(\ n)在引号内,那么我们应该忽略它。

例如:

abcd efgh ijk\n1234 567"qqqq\n---" 890\n
应将

解析为以下行的列表:

abcd efgh ijk
1234 567"qqqq\n---" 890

我已尝试split('\n'),但我不知道如何忽略引号。

有什么想法吗?

谢谢!

4 个答案:

答案 0 :(得分:7)

这是一个更容易的解决方案。

匹配(?:"[^"]*"|.)+的群组。即,“引号中的内容或不是换行符的内容”。

示例:

import re
re.findall('(?:"[^"]*"|.)+', text)

注意:这会将多个换行合并为一个,因为空行会被忽略。为避免这种情况,请同时提供一个空案例:(?:"[^"]*"|.)+|(?!\Z)

(?!\Z)是一种令人困惑的方式,可以说“不是字符串的结尾”。 (?! )为负面预测; \Z是“字符串的结尾”部分。


试验:

import re

texts = (
    'text',
    '"text"',
    'text\ntext',
    '"text\ntext"',
    'text"text\ntext"text',
    'text"text\n"\ntext"text"',
    '"\n"\ntext"text"',
    '"\n"\n"\n"\n\n\n""\n"\n"'
)

line_matcher = re.compile('(?:"[^"]*"|.)+')

for text in texts:
    print("{:>27} → {}".format(
        text.replace("\n", "\\n"),
        " [LINE] ".join(line_matcher.findall(text)).replace("\n", "\\n")
    ))

#>>>                        text → text
#>>>                      "text" → "text"
#>>>                  text\ntext → text [LINE] text
#>>>                "text\ntext" → "text\ntext"
#>>>        text"text\ntext"text → text"text\ntext"text
#>>>    text"text\n"\ntext"text" → text"text\n" [LINE] text"text"
#>>>            "\n"\ntext"text" → "\n" [LINE] text"text"
#>>>    "\n"\n"\n"\n\n\n""\n"\n" → "\n" [LINE] "\n" [LINE] "" [LINE] "\n"

答案 1 :(得分:4)

您可以将其拆分,然后将其缩小以将奇数为"的元素组合在一起:

txt = 'abcd efgh ijk\n1234 567"qqqq\n---" 890\n'
s = txt.split('\n')
reduce(lambda x, y: x[:-1] + [x[-1] + '\n' + y] if x[-1].count('"') % 2 == 1 else x + [y], s[1:], [s[0]])
# ['abcd efgh ijk', '1234 567"qqqq\n---" 890', '']

阐释:

if x[-1].count('"') % 2 == 1
# If there is an odd number of quotes to the last handled element
x[:-1] + [x[-1] + y]
# Append y to this element
else x + [y]
# Else append the element to the handled list

也可以这样写:

def splitWithQuotes(txt):
    s = txt.split('\n')
    res = []
    for item in s:
        if res and res[-1].count('"') % 2 == 1:
            res[-1] = res[-1] + '\n' + item
        else:
            res.append(item)
    return res
splitWithQuotes(txt)
# ['abcd efgh ijk', '1234 567"qqqq\n---" 890', '']

正如@Veedrac所指出的,这是O(n^2),但这可以通过跟踪"的数量来预防:

def splitWithQuotes(txt):
    s = txt.split('\n')
    res = []
    cnt = 0
    for item in s:
        if res and cnt % 2 == 1:
            res[-1] = res[-1] + '\n' + item
        else:
            res.append(item)
            cnt = 0
        cnt += item.count('"')
    return res
splitWithQuotes(txt)
# ['abcd efgh ijk', '1234 567"qqqq\n---" 890', '']

(最后一个空字符串是因为输入字符串末尾的最后一个\ n。)

答案 2 :(得分:1)

好吧,这似乎有效(假设报价得到适当平衡):

rx = r"""(?x)
    \n
    (?!
        [^"]*
        "
        (?=
            [^"]*
            (?:
                " [^"]* "
                [^"]*
            )*
            $
        )
    )
"""

测试:

str = """\
first
second "qqq
     qqq
     qqq
     " line
"third
    line" AND "spam
        ham" AND "more
            quotes"
end \
"""

import re


for x in re.split(rx, str):
    print '[%s]' % x

结果:

[first]
[second "qqq
     qqq
     qqq
     " line]
["third
    line" AND "spam
        ham" AND "more
            quotes"]
[end ]

如果上述内容对您来说太奇怪了,您也可以分两步完成:

str = re.sub(r'"[^"]*"', lambda m: m.group(0).replace('\n', '\x01'), str)
lines = [x.replace('\x01', '\n') for x in str.splitlines()]

for line in lines:
    print '[%s]' % line  # same result

答案 3 :(得分:1)

有很多方法可以实现这一目标。我提出了一个非常简单的问题:

splitted = [""]
for i, x in enumerate(re.split('"', text)):
    if i % 2 == 0:
        lines = x.split('\n')
        splitted[-1] += lines[0]
        splitted.extend(lines[1:])
    else:
        splitted[-1] += '"{0}"'.format(x)