Подтвердить что ты не робот

Как разделить, но игнорировать разделители в цитируемых строках, в python?

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

part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5

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

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

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

4b9b3361

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