Отказ
2014-12-01 Обновление: ответ ниже работает только для одного очень специфического формата CSV. Как правильно указал Д.Г. в комментариях, это решение НЕ соответствует определению CSV в RFC 4180, а также НЕ подходит для формата MS Excel. Это решение просто демонстрирует, как можно проанализировать одну (нестандартную) строку ввода CSV, которая содержит комбинацию типов строк, где строки могут содержать экранированные кавычки и запятые.
Нестандартное решение CSV
Как правильно указывает austincheney, вам действительно нужно анализировать строку от начала до конца, если вы хотите правильно обрабатывать строки в кавычках, которые могут содержать экранированные символы. Кроме того, OP не дает четкого определения, что такое «строка CSV». Сначала мы должны определить, что составляет действительную строку CSV и ее отдельные значения.
Дано: определение "CSV String"
Для целей этого обсуждения «строка CSV» состоит из нуля или более значений, где несколько значений разделены запятой. Каждое значение может состоять из:
- Строка в двойных кавычках. (может содержать неэкранированные одинарные кавычки.)
- Строка в одинарных кавычках. (может содержать неэкранированные двойные кавычки.)
- Строка без кавычек. (НЕ МОЖЕТ содержать кавычки, запятые или обратную косую черту.)
- Пустое значение. (Все пустые значения считаются пустыми.)
Правила / Примечания:
- Кавычки могут содержать запятые.
- Значения в кавычках могут содержать все экранированные символы, например,
'that\'s cool'
.
- Значения, содержащие кавычки, запятые или обратную косую черту, должны быть заключены в кавычки.
- Значения, содержащие начальные или конечные пробелы, должны заключаться в кавычки.
- Обратная косая черта удаляется из всех:
\'
в значениях, заключенных в одинарные кавычки.
- Обратная косая черта удаляется из всех:
\"
в двойных кавычках.
- Строки без кавычек обрезаются на любые начальные и конечные пробелы.
- Разделитель запятых может иметь соседний пробел (который игнорируется).
Найти:
Функция JavaScript, которая преобразует допустимую строку CSV (как определено выше) в массив строковых значений.
Решение:
Регулярные выражения, используемые этим решением, являются сложными. И (IMHO) все нетривиальных регулярных выражений должны быть представлены в свободном интервале с большим количеством комментариев и отступов. К сожалению, JavaScript не поддерживает режим свободного пробела. Таким образом, регулярные выражения, реализованные этим решением, сначала представлены в собственном синтаксисе регулярных выражений (выражается с помощью удобного Python: r'''...'''
синтаксис raw-multi-line-string).
Сначала приведем регулярное выражение, которое проверяет, соответствует ли строка CVS указанным выше требованиям:
Regex для проверки "строки CSV":
re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^ # Anchor to start of string.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
(?: # Zero or more additional values
, # Values separated by a comma.
\s* # Allow whitespace before value.
(?: # Group for value alternatives.
'[^'\\]*(?:\\[\S\s][^'\\]*)*' # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*" # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)* # or Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Allow whitespace after value.
)* # Zero or more additional values
$ # Anchor to end of string.
"""
Если строка соответствует приведенному выше регулярному выражению, то эта строка является допустимой строкой CSV (в соответствии с ранее указанными правилами) и может быть проанализирована с использованием следующего регулярного выражения. Затем используется следующее регулярное выражение для сопоставления одного значения из строки CSV. Он применяется повторно до тех пор, пока совпадения не будут найдены (и все значения будут проанализированы).
Regex для анализа одного значения из допустимой строки CSV:
re_value = r"""
# Match one value in valid CSV string.
(?!\s*$) # Don't match empty last value.
\s* # Strip whitespace before value.
(?: # Group for value alternatives.
'([^'\\]*(?:\\[\S\s][^'\\]*)*)' # Either $1: Single quoted string,
| "([^"\\]*(?:\\[\S\s][^"\\]*)*)" # or $2: Double quoted string,
| ([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*) # or $3: Non-comma, non-quote stuff.
) # End group of value alternatives.
\s* # Strip whitespace after value.
(?:,|$) # Field ends on comma or EOS.
"""
Обратите внимание, что существует одно специальное значение, которому это регулярное выражение не соответствует - самое последнее значение, когда это значение пусто. Этот специальный "пустое последнее значение" проверяется и обрабатывается следующей функцией js.
Функция JavaScript для анализа строки CSV:
// Return array of string values, or NULL if CSV string not well formed.
function CSVtoArray(text) {
var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
// Return NULL if input string is not well formed CSV string.
if (!re_valid.test(text)) return null;
var a = []; // Initialize array to receive values.
text.replace(re_value, // "Walk" the string using replace with callback.
function(m0, m1, m2, m3) {
// Remove backslash from \' in single quoted values.
if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
// Remove backslash from \" in double quoted values.
else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
else if (m3 !== undefined) a.push(m3);
return ''; // Return empty string.
});
// Handle special case of empty last value.
if (/,\s*$/.test(text)) a.push('');
return a;
};
Пример ввода и вывода:
В следующих примерах фигурные скобки используются для разделения {result strings}
. (Это помогает визуализировать начальные / конечные пробелы и строки нулевой длины.)
// Test 1: Test string from original question.
var test = "'string, duppi, du', 23, lala";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {string, duppi, du}
a[1] = {23}
a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array hes 0 elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array hes 2 elements:
a[0] = {}
a[1] = {} */
// Test 4: Double quoted CSV string having single quoted values.
var test = "'one','two with escaped \' single quote', 'three, with, commas'";
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped ' single quote}
a[2] = {three, with, commas} */
// Test 5: Single quoted CSV string having double quoted values.
var test = '"one","two with escaped \" double quote", "three, with, commas"';
var a = CSVtoArray(test);
/* Array hes 3 elements:
a[0] = {one}
a[1] = {two with escaped " double quote}
a[2] = {three, with, commas} */
// Test 6: CSV string with whitespace in and around empty and non-empty values.
var test = " one , 'two' , , ' four' ,, 'six ', ' seven ' , ";
var a = CSVtoArray(test);
/* Array hes 8 elements:
a[0] = {one}
a[1] = {two}
a[2] = {}
a[3] = { four}
a[4] = {}
a[5] = {six }
a[6] = { seven }
a[7] = {} */
Дополнительные примечания:
Это решение требует, чтобы строка CSV была "действительной". Например, значения без кавычек могут не содержать обратную косую черту или кавычки, например, следующая строка CSV НЕ допустима:
var invalid1 = "one, that's me!, escaped \, comma"
Это на самом деле не ограничение, потому что любая подстрока может быть представлена в виде значения в одинарных или двойных кавычках. Также обратите внимание, что это решение представляет только одно возможное определение: «Значения, разделенные запятыми».
Редактировать: 2014-05-19: Добавлен отказ от ответственности.
Редактировать: 2014-12-01: Перемещен отказ от ответственности наверх.