在Python中解析字符串:如何分割换行符而忽略引号内的换行符

5 投票
4 回答
2649 浏览
提问于 2025-04-18 08:25

我有一段文字需要在Python中处理。

这是一串字符,我想把它分割成一行一行的列表。不过,如果换行符(\n)出现在引号里面,那就得忽略它。

比如说:

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

应该被处理成下面这样的行列表:

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

我试过用 split('\n') 来处理,但我不知道怎么忽略引号里的内容。

有什么好主意吗?

谢谢!

4 个回答

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
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)
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。)

8

这里有一个简单得多的解决方案。

我们可以用 (?:"[^"]*"|.)+ 来匹配一组内容。也就是说,它可以匹配“引号里的东西或者不是换行符的东西”。

举个例子:

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"

撰写回答