在Python中解析字符串:如何分割换行符而忽略引号内的换行符
我有一段文字需要在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"