Как разделить, но игнорировать разделители в цитируемых строках, в python?
Мне нужно разбить строку, как это, на точку с запятой. Но я не хочу разбивать точки с запятой, которые находятся внутри строки ( "или" ). Я не разбираюсь в файле, просто простая строка без разрывов строк.
part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5
Результат должен быть:
- часть 1
- "это: часть 2;"
- 'это; часть 3 '
- часть 4
- это "есть часть" 5
Я полагаю, что это можно сделать с помощью регулярного выражения, но если нет; Я открыт для другого подхода.
Ответы
Ответ 1
Большинство ответов кажутся массово сложными. Вы не нуждаетесь в обратных ссылках. Вы не должны зависеть от того, разрешает ли re.findall совпадение совпадений. Учитывая, что вход не может быть проанализирован с модулем csv, так что регулярное выражение является вполне приемлемым способом, все, что вам нужно, - это вызвать re.split с шаблоном, который соответствует полю.
Обратите внимание, что здесь намного проще совместить поле, чем соответствовать разделителю:
import re
data = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
PATTERN = re.compile(r'''((?:[^;"']|"[^"]*"|'[^']*')+)''')
print PATTERN.split(data)[1::2]
а выход:
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
Как правильно отмечает Жан-Люк Насиф Коэльо, это неправильно обрабатывает пустые группы. В зависимости от ситуации, которая может или не имеет значения. Если это имеет значение, можно будет обрабатывать его, например, заменяя ';;'
на ';<marker>;'
, где <marker>
должна быть некоторая строка (без точек с запятой), которая, как вы знаете, не отображается в данных до разделения, Также вам необходимо восстановить данные после:
>>> marker = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]
Однако это куд. Любые лучшие предложения?
Ответ 2
re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)
Каждый раз, когда он находит точку с запятой, lookahead сканирует всю оставшуюся строку, удостоверяясь, что существует четное количество одиночных кавычек и четное количество двойных кавычек. (Одиночные кавычки внутри полей с двойными кавычками или наоборот) игнорируются.) Если результат выглядит успешно, точка с запятой является разделителем.
В отличие от Duncan solution, который соответствует полям, а не разделителям, у этого нет проблем с пустыми полями. (Даже не последний: в отличие от многих других реализаций split
, Python не отбрасывает автоматически пустые поля.)
Ответ 3
>>> a='A,"B,C",D'
>>> a.split(',')
['A', '"B', 'C"', 'D']
It failed. Now try csv module
>>> import csv
>>> from StringIO import StringIO
>>> data = StringIO(a)
>>> data
<StringIO.StringIO instance at 0x107eaa368>
>>> reader = csv.reader(data, delimiter=',')
>>> for row in reader: print row
...
['A,"B,C",D']
Ответ 4
Вот аннотированный подход pyparsing:
from pyparsing import (printables, originalTextFor, OneOrMore,
quotedString, Word, delimitedList)
# unquoted words can contain anything but a semicolon
printables_less_semicolon = printables.replace(';','')
# capture content between ';'s, and preserve original text
content = originalTextFor(
OneOrMore(quotedString | Word(printables_less_semicolon)))
# process the string
print delimitedList(content, ';').parseString(test)
дает
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4',
'this "is ; part" 5']
Используя pyparsing при условии quotedString
, вы также получите поддержку экранированных кавычек.
Вы также не понимали, как обрабатывать ведущие пробелы до или после разделителя с запятой, и ни одно из ваших полей в вашем примере текста не имеет. Pyparsing будет анализировать "a; b; c" как:
['a', 'b', 'c']
Ответ 5
У вас, кажется, есть разделительная колонка с полутонами. Почему бы не использовать модуль csv
для выполнения всей тяжелой работы?
Сверху моей головы это должно работать
import csv
from StringIO import StringIO
line = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
data = StringIO(line)
reader = csv.reader(data, delimiter=';')
for row in reader:
print row
Это должно дать вам что-то вроде
("part 1", "this is ; part 2;", 'this is ; part 3', "part 4", "this \"is ; part\" 5")
Edit:
К сожалению, это не совсем работает (даже если вы используете StringIO, как я и предполагал) из-за смешанных строковых кавычек (как одиночных, так и двойных). Фактически вы получаете
['part 1', 'this is ; part 2;', "'this is ", " part 3'", 'part 4', 'this "is ', ' part" 5']
.
Если вы можете изменить данные, чтобы содержать только одиночные или двойные кавычки в соответствующих местах, он должен работать нормально, но этот вопрос немного сокращает вопрос.
Ответ 6
Хотя это можно сделать с помощью PCRE с помощью lookaheads/behinds/backreferences, на самом деле это не задача, для которой регулярное выражение создано из-за необходимости сопоставления сбалансированных пар кавычек.
Вместо этого, вероятно, лучше всего просто создать мини-машину состояний и проанализировать эту строку.
Изменить
Как оказалось, из-за удобной дополнительной функции Python re.findall
, которая гарантирует совпадение совпадений, это может быть более просто сделать с регулярным выражением в Python, чем могло бы быть иначе. Подробнее см. Комментарии.
Однако, если вам интересно, как может выглядеть реализация без регулярного выражения:
x = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
results = [[]]
quote = None
for c in x:
if c == "'" or c == '"':
if c == quote:
quote = None
elif quote == None:
quote = c
elif c == ';':
if quote == None:
results.append([])
continue
results[-1].append(c)
results = [''.join(x) for x in results]
# results = ['part 1', '"this is ; part 2;"', "'this is ; part 3'",
# 'part 4', 'this "is ; part" 5']
Ответ 7
>>> x = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> import re
>>> re.findall(r'''(?:[^;'"]+|'(?:[^']|\\.)*'|"(?:[^']|\\.)*")+''', x)
['part 1', "this is ';' part 2", "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
Ответ 8
мы можем создать функцию собственного
def split_with_commas_outside_of_quotes(string):
arr = []
start, flag = 0, False
for pos, x in enumerate(string):
if x == '"':
flag= not(flag)
if flag == False and x == ',':
arr.append(string[start:pos])
start = pos+1
arr.append(string[start:pos])
return arr
Ответ 9
Это регулярное выражение будет делать это: (?:^|;)("(?:[^"]+|"")*"|[^;]*)
Ответ 10
так как у вас нет '\n', используйте его для замены любого ';' это не строка цитаты
>>> new_s = ''
>>> is_open = False
>>> for c in s:
... if c == ';' and not is_open:
... c = '\n'
... elif c in ('"',"'"):
... is_open = not is_open
... new_s += c
>>> result = new_s.split('\n')
>>> result
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
Ответ 11
Несмотря на то, что я уверен, что есть чистое регулярное выражение (пока мне нравится @noiflection ответ), вот быстрый и грязный ответ без регулярных выражений.
s = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
inQuotes = False
current = ""
results = []
currentQuote = ""
for c in s:
if not inQuotes and c == ";":
results.append(current)
current = ""
elif not inQuotes and (c == '"' or c == "'"):
currentQuote = c
inQuotes = True
elif inQuotes and c == currentQuote:
currentQuote = ""
inQuotes = False
else:
current += c
results.append(current)
print results
# ['part 1', 'this is ; part 2;', 'this is ; part 3', 'part 4', 'this is ; part 5']
(Я никогда не собирал ничего подобного, не стесняйтесь критиковать мою форму!)
Ответ 12
Мой подход заключается в замене всех некомандных вхождений полуколоны на другой символ, который никогда не будет отображаться в тексте, а затем разделить на этот символ. Следующий код использует функцию re.sub с аргументом функции для поиска и замены всех вхождений строки srch
, не заключенных в одинарные или двойные кавычки или парсы, скобки или фигурные скобки, с строкой repl
:
def srchrepl(srch, repl, string):
"""
Replace non-bracketed/quoted occurrences of srch with repl in string.
"""
resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
+ srch + """])|(?P<rbrkt>[)\]}])""")
return resrchrepl.sub(_subfact(repl), string)
def _subfact(repl):
"""
Replacement function factory for regex sub method in srchrepl.
"""
level = 0
qtflags = 0
def subf(mo):
nonlocal level, qtflags
sepfound = mo.group('sep')
if sepfound:
if level == 0 and qtflags == 0:
return repl
else:
return mo.group(0)
elif mo.group('lbrkt'):
if qtflags == 0:
level += 1
return mo.group(0)
elif mo.group('quote') == "'":
qtflags ^= 1 # toggle bit 1
return "'"
elif mo.group('quote') == '"':
qtflags ^= 2 # toggle bit 2
return '"'
elif mo.group('rbrkt'):
if qtflags == 0:
level -= 1
return mo.group(0)
return subf
Если вам не нужны символы в квадратных скобках, вы можете значительно упростить этот код.
Скажем, вы хотели использовать трубку или вертикальную полосу в качестве заменяющего символа, вы бы сделали:
mylist = srchrepl(';', '|', mytext).split('|')
Кстати, это использует nonlocal
из Python 3.1, изменив его на глобальный, если вам нужно.
Ответ 13
Обобщенное решение:
import re
regex = '''(?:(?:[^{0}"']|"[^"]*(?:"|$)|'[^']*(?:'|$))+|(?={0}{0})|(?={0}$)|(?=^{0}))'''
delimiter = ';'
data2 = ''';field 1;"field 2";;'field;4';;;field';'7;'''
field = re.compile(regex.format(delimiter))
print(field.findall(data2))
Выходы:
['', 'field 1', '"field 2"', '', "'field;4'", '', '', "field';'7", '']
Это решение:
- захватывает все пустые группы (включая в начале и в конце)
- работает для самых популярных разделителей, включая пробел, вкладку и
запятая
- обрабатывает кавычки внутри кавычек другого типа как неспециальные символы
- если встречается непревзойденная некотируемая цитата, обрабатывает остатки строки как указано
Ответ 14
Хотя тема старая и предыдущие ответы работают хорошо, я предлагаю собственную реализацию функции split в python.
Это прекрасно работает, если вам не нужно обрабатывать большое количество строк, и легко настраивается.
Вот моя функция:
# l is string to parse;
# splitchar is the separator
# ignore char is the char between which you don't want to split
def splitstring(l, splitchar, ignorechar):
result = []
string = ""
ignore = False
for c in l:
if c == ignorechar:
ignore = True if ignore == False else False
elif c == splitchar and not ignore:
result.append(string)
string = ""
else:
string += c
return result
Таким образом, вы можете запустить:
line= """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
splitted_data = splitstring(line, ';', '"')
результат:
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
Преимущество состоит в том, что эта функция работает с пустыми полями и любым количеством разделителей в строке.
Надеюсь это поможет!
Ответ 15
Вместо разделения на шаблон разделителя, просто захватите все, что вам нужно:
>>> import re
>>> data = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> re.findall(r';([\'"][^\'"]+[\'"]|[^;]+)', ';' + data)
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ', ' part" 5']
Ответ 16
Это показалось мне полу-элегантным решением.
Новое решение:
import re
reg = re.compile('(\'|").*?\\1')
pp = re.compile('.*?;')
def splitter(string):
#add a last semicolon
string += ';'
replaces = []
s = string
i = 1
#replace the content of each quote for a code
for quote in reg.finditer(string):
out = string[quote.start():quote.end()]
s = s.replace(out, '**' + str(i) + '**')
replaces.append(out)
i+=1
#split the string without quotes
res = pp.findall(s)
#add the quotes again
#TODO this part could be faster.
#(lineal instead of quadratic)
i = 1
for replace in replaces:
for x in range(len(res)):
res[x] = res[x].replace('**' + str(i) + '**', replace)
i+=1
return res
Старое решение:
Я выбираю совпадение, если была открытая цитата, и ждать ее закрытия, а совпадение - с точкой с запятой. каждая "часть", которую вы хотите сопоставить, должна заканчиваться точкой с запятой.
так что это соответствует следующим:
- 'Foobar;.sska';
- "akjshd; asjkdhkj..";
- asdkjhakjhajsd.jhdf;
код:
mm = re.compile('''((?P<quote>'|")?.*?(?(quote)\\2|);)''')
res = mm.findall('''part 1;"this is ; part 2;";'this is ; part 3';part 4''')
вам может потребоваться выполнить некоторую постобработку для res, но она содержит то, что вы хотите.