Как разделить, но игнорировать разделители в кавычках, в Python? - PullRequest
60 голосов
/ 07 мая 2010

Мне нужно разбить строку, например, на точки с запятой.Но я не хочу разбивать точки с запятой, которые находятся внутри строки ('или "). Я не анализирую файл; просто простая строка без разрывов строки.*

Результат должен быть:

  • часть 1
  • "это;часть 2; "
  • " это; часть 3 '
  • часть 4
  • это ";часть "5

Полагаю, это можно сделать с помощью регулярного выражения, но если нет, я открыт для другого подхода.

Ответы [ 16 ]

0 голосов
/ 03 февраля 2019

Вместо разделения на шаблон разделителя, просто запишите все, что вам нужно:

>>> 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']
0 голосов
/ 31 августа 2018

Хотя тема старая и предыдущие ответы работают хорошо, я предлагаю собственную реализацию функции 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']

Преимущество состоит в том, что эта функция работает с пустыми полями и любым количеством разделителей в строке.

Надеюсь, это поможет!

0 голосов
/ 08 марта 2017

Обобщенное решение:

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", '']

Это решение:

  • захватывает все пустые группы (в том числе в начале и в конце)
  • работает для большинства популярных разделителей, включая пробел, табуляцию и Запятая
  • обрабатывает кавычки внутри кавычек другого типа как не специальные символы
  • если встречается непревзойденная кавычка без кавычек, обрабатывается остаток строки как кавычка
0 голосов
/ 07 мая 2010

Мой подход состоит в том, чтобы заменить все не заключенные в кавычки вхождения точки с запятой другим символом, который никогда не появится в тексте, а затем разделить на этот символ.В следующем коде используется функция 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, измените его на глобальный, если вам нужно.

0 голосов
/ 07 мая 2010

Несмотря на то, что я уверен, что существует чистое решение для регулярных выражений (пока мне нравится ответ @ 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']

(я никогда не собирал что-то подобное, не стесняйтесь критиковать мою форму!)

0 голосов
/ 07 мая 2010

Мне показалось это полу-элегантным решением.

Новое решение:

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, но она содержит то, что вы хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...