Этот вопрос уже задавался и на него уже много раз давался ответ. Некоторые примеры: [1], [2]. Но, похоже, нет чего-то более общего. То, что я ищу, - это способ разделить строки запятыми, которые не находятся в кавычках или парах разделителей. Например:
s1 = 'obj<1, 2, 3>, x(4, 5), "msg, with comma"'
должен быть разбит на список из трех элементов
['obj<1, 2, 3>', 'x(4, 5)', '"msg, with comma"']
Теперь проблема в том, что это может усложниться, поскольку мы можем рассматривать пары <> и ().
s2 = 'obj<1, sub<6, 7>, 3>, x(4, y(8, 9), 5), "msg, with comma"'
который следует разделить на:
['obj<1, sub<6, 7>, 3>', 'x(4, y(8, 9), 5)', '"msg, with comma"']
Наивное решение без использования регулярных выражений состоит в том, чтобы проанализировать строку, ища символы ,<(. Если найдены < или (, начинаем считать четность. Мы можем разделить запятую только в том случае, если четность равна нулю. Например, скажем, мы хотим разделить s2, мы можем начать с parity = 0, и когда мы достигнем s2[3], мы столкнемся с <, что увеличит четность на 1. Четность уменьшится только при встрече > или ) и увеличится при встрече < или ( . Пока четность не равна 0, мы можем просто игнорировать запятые и не выполнять никакого разделения.
Вопрос в том, есть ли способ быстро сделать это с помощью регулярных выражений? Я действительно изучал это решение, но похоже, что оно не охватывает приведенные мной примеры.
Более общая функция будет выглядеть примерно так:
def split_at(text, delimiter, exceptions):
"""Split text at the specified delimiter if the delimiter is not
within the exceptions"""
Некоторые варианты использования будут такими:
split_at('obj<1, 2, 3>, x(4, 5), "msg, with comma"', ',', [('<', '>'), ('(', ')'), ('"', '"')]
Сможет ли регулярное выражение справиться с этим или необходимо создать специализированный парсер?
Ответ 3 · рейтинг 4 · dawg
Если у вас есть рекурсивные вложенные выражения, вы можете разделить их запятыми и проверить, что они совпадают, сделав это с помощью pyparsing. :
import pyparsing as pp
def CommaSplit(txt):
''' Replicate the function of str.split(',') but do not split on nested expressions or in quoted strings'''
com_lok=[]
comma = pp.Suppress(',')
# note the location of each comma outside an ignored expression:
comma.setParseAction(lambda s, lok, toks: com_lok.append(lok))
ident = pp.Word(pp.alphas+"_", pp.alphanums+"_") # python identifier
ex1=(ident+pp.nestedExpr(opener='<', closer='>')) # Ignore everthing inside nested '< >'
ex2=(ident+pp.nestedExpr()) # Ignore everthing inside nested '( )'
ex3=pp.Regex(r'("|\').*?\1') # Ignore everything inside "'" or '"'
atom = ex1 | ex2 | ex3 | comma
expr = pp.OneOrMore(atom) + pp.ZeroOrMore(comma + atom )
try:
result=expr.parseString(txt)
except pp.ParseException:
return [txt]
else:
return [txt[st:end] for st,end in zip([0]+[e+1 for e in com_lok],com_lok+[len(txt)])]
tests='''\
obj<1, 2, 3>, x(4, 5), "msg, with comma"
nesteobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), "msg, with comma"
nestedobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), 'msg, with comma', additional<1, sub<6, 7>, 3>
bare_comma<1, sub(6, 7), 3>, x(4, y(8, 9), 5), , 'msg, with comma', obj<1, sub<6, 7>, 3>
bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3)
'''
for te in tests.splitlines():
result=CommaSplit(te)
print(te,'==>\n\t',result)
Отпечатки:
obj<1, 2, 3>, x(4, 5), "msg, with comma" ==>
['obj<1, 2, 3>', ' x(4, 5)', ' "msg, with comma"']
nesteobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), "msg, with comma" ==>
['nesteobj<1, sub<6, 7>, 3>', ' nestedx(4, y(8, 9), 5)', ' "msg, with comma"']
nestedobj<1, sub<6, 7>, 3>, nestedx(4, y(8, 9), 5), 'msg, with comma', additional<1, sub<6, 7>, 3> ==>
['nestedobj<1, sub<6, 7>, 3>', ' nestedx(4, y(8, 9), 5)', " 'msg, with comma'", ' additional<1, sub<6, 7>, 3>']
bare_comma<1, sub(6, 7), 3>, x(4, y(8, 9), 5), , 'msg, with comma', obj<1, sub<6, 7>, 3> ==>
['bare_comma<1, sub(6, 7), 3>', ' x(4, y(8, 9), 5)', ' ', " 'msg, with comma'", ' obj<1, sub<6, 7>, 3>']
bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3) ==>
["bad_close<1, sub<6, 7>, 3), x(4, y(8, 9), 5), 'msg, with comma', obj<1, sub<6, 7>, 3)"]
Текущее поведение такое же, как '(something does not split), b, "in quotes", c'.split','), включая сохранение начальных пробелов и кавычек. Тривиально удалить кавычки и начальные пробелы из полей.
Измените else под try на:
else:
rtr = [txt[st:end] for st,end in zip([0]+[e+1 for e in com_lok],com_lok+[len(txt)])]
if strip_fields:
rtr=[e.strip().strip('\'"') for e in rtr]
return rtr