Как разделить, но игнорировать разделители в цитируемых строках, в python?
Мне нужно разбить строку, как это, на точки с запятой. Но я не хочу разбивать на точки с запятой, которые находятся внутри строки (' или "). Я не разбираю файл; просто простая строка без разрывов строк.
part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5
результат должен быть:
- часть 1
- "это ; часть 2;"
- 'это ; часть 3'
- часть 4
- это"; часть" 5
Я полагаю, это можно сделать с помощью регулярного выражения но если нет, я открыт для другого подхода.
15 ответов:
большинство ответов кажутся слишком сложными. Ты не нужны обратные ссылки. Ты не нужно зависеть от того, ре или нет.метод findAll дает перекрывающихся совпадений. Учитывая, что вход не может быть проанализирован с помощью модуля csv, поэтому регулярное выражение-это довольно хорошо, все, что вам нужно, это вызвать re.разделить с шаблоном, который соответствует полю.
обратите внимание, что это гораздо проще здесь, чтобы соответствовать полю, чем это, чтобы соответствовать разделитель:
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'"]однако это Клудж. Есть предложения получше?
re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)каждый раз, когда он находит точку с запятой, lookahead сканирует всю оставшуюся строку, убедившись, что есть четное число одинарных кавычек и четное число двойных кавычек. (Одинарные кавычки внутри полей с двойными кавычками или наоборот игнорируются.) Если lookahead выполняется успешно, точка с запятой является разделителем.
в отличие от Дункан, который соответствует полям, а не разделителям, у этого нет проблем с пустыми полями. (Даже не последний одно: в отличие от многих других
splitреализации, Python не отбрасывает автоматически конечные пустые поля.)
вот аннотированный 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']
>>> 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']
у вас, похоже,есть строка с запятой. Почему бы не использовать
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'].если вы можете изменить данные, чтобы они содержали только одинарные или двойные кавычки в соответствующих местах, это должно работать нормально, но это немного отрицает вопрос.
>>> 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']
хотя это может быть сделано с помощью PCRE через lookaheads/behinds/backreferences, на самом деле это не совсем задача, для которой предназначено регулярное выражение из-за необходимости соответствовать сбалансированным парам котировок.
вместо этого, вероятно, лучше всего просто сделать мини-машину состояния и проанализировать строку таким образом.
Edit
как оказалось, благодаря удобной дополнительной функции 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']
мы можем создать собственную функцию
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
поскольку у вас нет '\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']
хотя я уверен, что есть чистое регулярное выражение (до сих пор мне нравится ответ @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'](Я никогда не собрали что-то в этом роде, не стесняйтесь критиковать мою форму!)
мой подход состоит в том, чтобы заменить все нецитируемые вхождения точки с запятой другим символом, который никогда не появится в тексте, а затем разделить на этот символ. Следующий код используется повторно.подфункция с аргументом функции для поиска и замены всех вхождений a
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, измените его на глобальный, если вам нужно.
обобщенное решение:
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", '']такое решение:
- захватывает все пустые группы (в том числе в начале и в конце)
- работает для большинства популярных разделителей, включая пробел, вкладку и запятая
- обрабатывает кавычки внутри кавычек другого типа как не специальные символы
- если встречается несопоставимая некотируемая цитата, остатки строки обрабатываются как цитируется
хотя тема старая и предыдущие ответы работают хорошо, я предлагаю свою собственную реализацию функции 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']преимущество в том, что эта функция работает с пустыми полями и с любым количеством разделителей в строка.
надеюсь, что это помогает!
это показалось мне полу-элегантным решением.
Новое Решение:
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;.сска';
- " 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, но она содержит то, что вы хотите.
Comments