Я должен разделить строку как это на точках с запятой. Но я не хочу разделять на точках с запятой, которые являются в строке (' или"). Я не анализирую файл; просто простая строка без разрывов строки.
part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5
Результат должен быть:
Я предполагаю, что это может быть сделано с regex, но если нет; я открыт для другого подхода.
Большинство ответов кажутся чрезмерно сложными. Вам не нужны обратные ссылки. Вам не нужно зависеть от того, дает ли 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 = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]
Однако это кладж. Есть предложения получше?
Мне это показалось полу-элегантным решением.
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
Я выбираю сопоставление, если была начальная цитата, и жду ее закрытия, а сопоставление - точку с запятой в конце. каждая «часть», которую вы хотите сопоставить, должна заканчиваться точкой с запятой. , поэтому это соответствует примерно так:
Код:
mm = re.compile('''((?P<quote>'|")?.*?(?(quote)\\2|);)''')
res = mm.findall('''part 1;"this is ; part 2;";'this is ; part 3';part 4''')
вам может потребоваться постобработка res, но он содержит то, что вы хотите.
Поскольку у вас нет '\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']
Мой подход заключается в том, чтобы заменить все некавыченные вхождения точки с запятой другим символом, который никогда не появится в тексте, а затем разделить этот символ. Следующий код использует функцию 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('|')
BTW, здесь используется nonlocal
из Python 3.1, измените его на global, если нужно.
Кажется, у вас есть строка, разделенная точкой с запятой. Почему бы не использовать модуль 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
Это должно дать вам что-то вроде
(«часть 1», "this is; part 2;", 'this is; part 3', "part 4", "this \" is; part \ "5")
Редактировать:
К сожалению, это не совсем работает (даже если вы используете StringIO, как я и предполагал) из-за смешанных строковых кавычек (как одинарных, так и двойных). На самом деле вы получите
['часть 1', 'это; part 2; ', "' this is", "part 3 '",' part 4 ',' this "is ',' part" 5 ']
.
Если вы можете изменить данные, чтобы они содержали только одинарные или двойные кавычки в соответствующих местах, все должно работать нормально, но это немного отменяет вопрос.
Хотя это можно сделать с помощью PCRE через lookaheads/behinds/backreferences, это не совсем та задача, для которой предназначен regex, из-за необходимости сопоставления сбалансированных пар кавычек.
Вместо этого, вероятно, лучше просто сделать мини-машину состояний и разбирать строку таким образом.
Как оказалось, благодаря удобной дополнительной функции Python re.findall
, которая гарантирует непересекающиеся совпадения, это может быть более просто сделать с помощью regex в 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']
Этот regex сделает это: (?:^|;)("(?:[^"]+|"")*"|[^;]*)
re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)
Каждый раз, когда он находит точку с запятой, предварительный просмотр просматривает всю оставшуюся строку, проверяя наличие четного числа одинарных кавычек и четного количества двойных кавычек. (Одиночные кавычки внутри полей, заключенных в двойные кавычки, и наоборот, игнорируются.) Если просмотр вперед завершается успешно, точка с запятой является разделителем.
В отличие от решения Дункана , которое сопоставляет поля, а не разделители, здесь нет проблем с пустыми полями. (Даже не последний: в отличие от многих других реализаций 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']
>>> 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']
Хотя я уверен есть чистое решение с регулярным выражением (пока мне нравится ответ @ 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']
(Я никогда не создавал ничего подобного, не стесняйтесь критиковать мою форму!)