Регулярное выражение Python для чтения CSV-подобных строк - PullRequest
13 голосов
/ 06 февраля 2010

Я хочу проанализировать входящие CSV-подобные строки данных. Значения разделяются запятыми (и могут быть начальные и конечные пробелы вокруг запятых), и могут быть заключены в кавычки либо с «, либо с». Например, это допустимая строка:

    data1, data2  ,"data3'''",  'data4""',,,data5,

но этот искажен:

    data1, data2, da"ta3", 'data4',

- кавычки могут начинаться или заканчиваться только пробелами.

Такие искаженные строки следует распознавать - лучше всего было бы как-то пометить искаженное значение в строке, но если регулярное выражение не соответствует всей строке, то это также допустимо.

Я пытаюсь написать регулярное выражение, способное разобрать это, используя match () из findall (), но у каждого отдельного регулярного выражения, с которым я работаю, есть некоторые проблемы с крайними случаями.

Так, может, кто-то с опытом разбора чего-то подобного мог бы помочь мне в этом? (Или, может быть, это слишком сложно для регулярных выражений, и я должен просто написать функцию)

EDIT1:

csv модуль здесь не очень полезен:

    >>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2',''')))
    [['2', ' "dat', 'a1"', " 'dat", "a2'", '']]

    >>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2',''')))
    [['2', 'dat,a1', "'dat", "a2'", '']]

- разве это можно настроить?

РЕДАКТИРОВАТЬ 2: Несколько языковых изменений - я надеюсь, что теперь это более правильный английский

EDIT3: Спасибо за все ответы, теперь я почти уверен, что регулярное выражение здесь не очень хорошая идея, поскольку (1) охват всех граничных случаев может быть сложным (2) вывод записи не является регулярным. Написав это, я решил проверить упомянутый pyparsing и либо использовать его, либо написать собственный анализатор, похожий на FSM.

Ответы [ 6 ]

12 голосов
/ 06 февраля 2010

Хотя модуль csv является правильным ответом, регулярное выражение, которое может это сделать, вполне выполнимо:

import re

r = re.compile(r'''
    \s*                # Any whitespace.
    (                  # Start capturing here.
      [^,"']+?         # Either a series of non-comma non-quote characters.
      |                # OR
      "(?:             # A double-quote followed by a string of characters...
          [^"\\]|\\.   # That are either non-quotes or escaped...
       )*              # ...repeated any number of times.
      "                # Followed by a closing double-quote.
      |                # OR
      '(?:[^'\\]|\\.)*'# Same as above, for single quotes.
    )                  # Done capturing.
    \s*                # Allow arbitrary space before the comma.
    (?:,|$)            # Followed by a comma or the end of a string.
    ''', re.VERBOSE)

line = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""

print r.findall(line)

# That prints: ['data1', 'data2', '"data3\'\'\'"', '\'data4""\'', 'data5']

РЕДАКТИРОВАТЬ: Чтобы проверить строки, вы можете повторно использовать приведенное выше регулярное выражение с небольшими дополнениями:

import re

r_validation = re.compile(r'''
    ^(?:    # Capture from the start.
      # Below is the same regex as above, but condensed.
      # One tiny modification is that it allows empty values
      # The first plus is replaced by an asterisk.
      \s*([^,"']*?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*(?:,|$)
    )*$    # And don't stop until the end.
    ''', re.VERBOSE)

line1 = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""
line2 = r"""data1, data2, da"ta3", 'data4',"""

if r_validation.match(line1):
    print 'Line 1 is valid.'
else:
    print 'Line 1 is INvalid.'

if r_validation.match(line2):
    print 'Line 2 is valid.'
else:
    print 'Line 2 is INvalid.'

# Prints:
#    Line 1 is valid.
#    Line 2 is INvalid.
7 голосов
/ 06 февраля 2010

Хотя это может быть возможно при некоторой комбинации предварительной обработки, использования модуля csv, последующей обработки и использования регулярных выражений, ваши заявленные требования не вписываются в структуру модуля csv или, возможно, с регулярными выражениями (в зависимости от сложности вложенных кавычек, с которыми вам, возможно, придется работать).

В сложных случаях синтаксического анализа, pyparsing - это всегда хороший пакет, к которому можно прибегнуть. Если это не одноразовая ситуация, она, скорее всего, даст самый простой и понятный результат, возможно, за счет небольшого дополнительного усилия. Однако примите во внимание, что эти инвестиции быстро окупятся, поскольку вы избавите себя от дополнительных усилий по отладке решений regex для обработки угловых случаев ...

Скорее всего, вы легко найдете примеры синтаксического анализа CSV на основе pyparsing: этого вопроса может быть достаточно, чтобы начать работу.

4 голосов
/ 06 февраля 2010

Python имеет стандартный библиотечный модуль для чтения CSV-файлов:

import csv

reader = csv.reader(open('file.csv'))

for line in reader:
    print line

Для вашего примера введите:

['data1', ' data2 ', "data3'''", ' \'data4""\'', '', '', 'data5', '']

EDIT:

вам нужно добавить skipinitalspace = True, чтобы разрешить пробелы перед двойными кавычками для предоставленных вами дополнительных примеров. Пока не уверен насчет одинарных кавычек.

>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]

>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]
2 голосов
/ 07 февраля 2010

Невозможно дать вам ответ, потому что вы не полностью указали протокол, который используется автором.

Очевидно, он содержит такие правила, как:

Если поле содержит запятые или одинарные кавычки, заключите его в двойные кавычки.
Иначе, если в поле есть двойные кавычки, заключите его в одинарные кавычки.
Примечание: результат все еще действителен, если вы поменяете местами двойные и одинарные в приведенных выше 2 пунктах.
Иначе не цитируйте это.
В результирующем поле могут быть добавлены или добавлены пробелы (или другие пробелы?).
Поля с расширенными полями собраны в строку, разделены запятыми и оканчиваются переводом строки (LF или CRLF).

Что не упоминается, так это то, что писатель делает в следующих случаях:
(0) поле содержит ОБА одинарные кавычки и двойные кавычки
(1) поле содержит начальные пробелы без перевода строки
(2) поле содержит завершающие пробелы без перевода строки
(3) поле содержит любые новые строки.
Если автор игнорирует любой из этих случаев, укажите, какие результаты вы хотите.

Вы также упоминаете, что «кавычки могут быть добавлены или завершены только пробелами» - вы наверняка имеете в виду и запятые, в противном случае ваш пример 'data4""',,,data5, завершится неудачно с первой запятой.

Как закодированы ваши данные?

1 голос
/ 06 февраля 2010

Возможно, это звучит слишком просто, но на самом деле, судя по всему, вы ищете строку, содержащую либо [a-zA-Z0-9] ["'] + [a-zA-Z0-9], I значит, без глубокого тестирования данных на самом деле вам нужна кавычка или двойная кавычка (или любая комбинация) между буквами (вы также можете добавить цифры).

Исходя из того, что вы спрашивали, действительно не важно, что это CSV, важно, что у вас есть данные, которые не соответствуют. Что я считаю, просто делая поиск буквы, а затем любую комбинацию одного или нескольких "или" и другого письма.

Теперь вы ищете "количество" или просто распечатку строки, в которой оно содержится, чтобы вы знали, какие из них вернуться и исправить?

Извините, я не знаю регулярных выражений Python, но в Perl это будет выглядеть примерно так:

# Look for one or more letter/number at least one ' or " or more and at least one    
#  or more letter/number
if ($line =~ m/[a-zA-Z0-9]+['"]+[a-zA-Z0-9]+/ig)
{
    # Prints the line if the above regex is found
    print $line;

}

Просто конвертируйте это, когда смотрите на строку.

Извините, если я неправильно понял вопрос

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

0 голосов
/ 07 февраля 2010

Если ваша цель - преобразовать данные в XML (или JSON, или YAML), посмотрите на этот пример для синтаксиса Желатин , который выдает следующий вывод:

<xml>
  <line>
    <column>data1</column>
    <column>data2  </column>
    <column>data3'''</column>
    <column>data4""</column>
    <column/>
    <column/>
    <column>data5</column>
    <column/>
  </line>
</xml>

Обратите внимание, что у желатина также есть Python API:

from Gelatin.util import compile, generate_to_file
syntax = compile('syntax.gel')
generate_to_file(syntax, 'input.csv', 'output.xml', 'xml')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...