Застереження
2014-12-01 Оновлення: Відповідь нижче працює лише для одного дуже конкретного формату CSV. Як правильно зазначив DG у коментарях , це рішення не відповідає визначенню CSV RFC 4180, а також не відповідає формату Microsoft Excel. Це рішення просто демонструє, як можна проаналізувати один (нестандартний) рядок вводу CSV, що містить поєднання типів рядків, де рядки можуть містити екрановані лапки та коми.
Нестандартне рішення CSV
Як правильно вказує austincheney , вам дійсно потрібно проаналізувати рядок від початку до кінця, якщо ви хочете належним чином обробляти рядки, що містять цитати, які можуть містити символи, що втекли. Крім того, OP не чітко визначає, що насправді є "рядком CSV". Спочатку ми повинні визначити, що являє собою дійсний рядок CSV та його окремі значення.
Дано: Визначення "рядка CSV"
Для цілей цього обговорення "рядок CSV" складається з нуля або більше значень, де кілька значень відокремлюються комою. Кожне значення може складатися з:
- Рядок із подвійними лапками (може містити одинарні лапки, що не є).
- Рядок із одинарними лапками (може містити подвійні лапки).
- Рядок без цитат ( не може містити лапки, коми чи зворотні скісні риски).
- Порожнє значення. (Значення всього пробілу вважається порожнім.)
Правила / Примітки:
- Значення, вказані в лапках, можуть містити коми.
- Значення, що вказані в лапках, можуть містити що-небудь, наприклад, наприклад
'that\'s cool'
.
- Значення, що містять лапки, коми чи зворотні скісні риски, повинні бути вказані в лапках.
- Значення, що містять пробіли, що ведуть або завершують, повинні бути вказані в лапках.
- Зворотна коса риска видаляється з усіх:
\'
в одиничних значеннях, що вказані в лапки.
- Зворотну косу риску видалено з усіх:
\"
у подвійних значеннях лапок.
- Рядки, що не цитуються, обрізаються з будь-якого проміжного та кінцевого пробілів.
- Розділювач комами може мати сусідній пробіл (який ігнорується).
Знайти:
Функція JavaScript, яка перетворює дійсний рядок CSV (як визначено вище) у масив значень рядка.
Рішення:
Регулярні вирази, що використовуються цим рішенням, є складними. І (IMHO) усі нетривіальні регулярні вирази повинні бути представлені у режимі вільного інтервалу з великою кількістю коментарів та відступу. На жаль, JavaScript не дозволяє режим вільного інтервалу. Таким чином, регулярні вирази, реалізовані цим рішенням, спочатку представлені у власному синтаксисі регулярних виразів (виражених із використанням зручного синтаксису r'''...'''
необроблених багаторядкових рядків Python ).
Спочатку тут регулярний вираз, який підтверджує, що рядок CVS відповідає наведеним вище вимогам:
Регулярний вираз для перевірки "рядка 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. Він застосовується неодноразово, доки більше не знайдено збігів (і всі значення проаналізовано).
Регулярний вираз для синтаксичного аналізу одного значення з дійсного рядка 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.
"""
Зверніть увагу, що існує одне особливе значення, якому цей регулярний вираз не відповідає - саме останнє значення, коли це значення порожнє. Цей спеціальний випадок "останнього останнього значення" перевіряється та обробляється наступною функцією JavaScript.
Функція 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 has three elements:
a[0] = {string, duppi, du}
a[1] = {23}
a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array has zero elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array has two 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 has three 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 has three 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 has eight 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"
Це насправді не є обмеженням, оскільки будь-який підрядок може бути представлений як одинарне, так і подвійне лапкове значення. Зауважте також, що це рішення представляє лише одне можливе визначення для "значень, розділених комами".
Редагувати історію
- 19.05.2014: додано застереження.
- 2014-12-01: Переміщено застереження до початку.