Чи можна визначити формат csv за допомогою регулярного вираження?


19

Нещодавно ми з колегою посперечалися, чи здатний чистий регулярний вираз повністю інкапсулювати формат csv, таким чином, щоб він міг аналізувати всі файли з будь-яким заданим символом втечі, цитатою та роздільником.

Регекс не повинен бути здатний змінювати ці символи після створення, але він не повинен провалюватися в будь-якому іншому краєвому випадку.

Я стверджував, що це неможливо лише для токенізатора. Єдиний регулярний вираз, який міг би зробити це - дуже складний стиль PCRE, який виходить за рамки просто токенізації.

Я шукаю щось у напрямку:

... формат csv - це граматика без контексту, і, як такий, неможливо розібратися з регулярним виразом ...

Або я помиляюся? Чи можливо проаналізувати csv за допомогою просто виража POSIX?

Наприклад, якщо обидва знаки введення і цитата є ", ці два рядки є дійсними csv:

"""this is a test.""",""
"and he said,""What will be, will be."", to which I replied, ""Surely not!""","moving on to the next field here..."

це не CSV, так як ніде не вкладається гніздування (IIRC)
щурячий вирод

1
але які крайові випадки? може, в CSV є більше, ніж я коли-небудь думав?
c69

1
@ c69 Як щодо втечі та цитати, це обидва ". Тоді діє наступне:"""this is a test.""",""
Спенсер Ратбун

Ви спробували звідси regexp ?
dasblinkenlight

1
Вам потрібно стежити за крайовими справами, але регулярний вираз повинен бути в змозі токенізувати CSV, як ви його описали. Зворотному вираженню не потрібно рахувати довільну кількість лапок - йому потрібно порахувати лише 3, що регулярні вирази можуть робити. Як уже згадували інші, вам слід спробувати записати чітко
виражене

Відповіді:


20

Приємно в теорії, жахливо на практиці

Під CSV я вважаю, що ви маєте на увазі конвенцію, описану в RFC 4180 .

При збігу основних даних CSV тривіально:

"data", "more data"

Примітка: BTW набагато ефективніше використовувати функцію .split ('/ n'). Split ('"') для дуже простих і добре структурованих даних, таких як. Державна машина), яка витрачає багато часу на відстеження часу, коли ви починаєте додавати крайові випадки, наприклад, знаки втечі.

Наприклад, ось найповніший рядок відповідності регулярного виразу, який я знайшов:

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.
"""

Він обгрунтовано обробляє значення, що містяться в одному та подвійному значеннях, але не нові рядки у значеннях, котирування, що уникнули тощо.

Джерело: Переповнення стека - Як я можу розібрати рядок із JavaScript

Це стає кошмаром, коли вводяться загальні кейси, як ...

"such as ""escaped""","data"
"values that contain /n newline chars",""
"escaped, commas, like",",these"
"un-delimited data like", this
"","empty values"
"empty trailing values",        // <- this is completely valid
                                // <- trailing newline, may or may not be included

Одного лише кращого випадку нового рядка як вартості достатньо, щоб зламати 99,9999% парсерів на основі RegEx, виявлених у дикій природі. Єдиною "розумною" альтернативою є використання узгодження RegEx для основного символу управління / неконтролювання (тобто термінал проти нетермінального) токенізації в парі з державною машиною, що використовується для аналізу вищого рівня.

Джерело: Досвід, інакше відомий як великий біль і страждання.

Я автор jquery-CSV , єдиного на базі javascript, повністю сумісного з RFC, CSV-аналізатора у світі. Я витрачав місяці на вирішення цієї проблеми, розмовляючи з багатьма розумними людьми і намагаючись багато, якщо різні реалізації, включаючи 3 повних переписки основного механізму аналізатора.

tl; dr - Мораль розповіді, PCRE поодинці піддається розбору будь-якого, крім самих простих і суворих регулярних граматик (тобто типу III). Хоча це корисно для токенізації термінальних та нетермінальних рядків.


1
Так, це був і мій досвід. Будь-яка спроба повністю інкапсулювати більше, ніж дуже простий шаблон CSV, стикається з цими речами, і тоді ви наштовхуєтесь як на проблеми ефективності, так і на проблеми складності масового регулярного вираження. Ви подивилися на бібліотеку node-csv ? Здається, це підтверджує і ця теорія. Кожна нетривіальна реалізація використовує внутрішній аналізатор.
Спенсер Ратбун

@SpencerRathbun Так. Я впевнений, що раніше переглянув джерело node-csv. Схоже, використовується типова машина токенізації символів для обробки. Аналізатор jquery-csv працює на тій же фундаментальній концепції, за винятком того, що я використовую регулярний термін для термінальної / нетермінальної токенізації. Замість того, щоб оцінювати та об'єднувати їх на основі принципу «чар-чар-чар», регулярний генерування може порівнювати декілька нетермінальних символів одночасно та повертати їх як групу (тобто рядок). Це мінімізує непотрібне конкатенацію та «повинно» підвищити ефективність.
Еван Плейс

20

Regex може розбирати будь-яку звичайну мову і не може розбирати фантазії, такі як рекурсивні граматики. Але CSV, здається, досить регулярний, настільки розбірливий з регулярним виразом.

Давайте попрацюємо з визначення : дозволені послідовність, альтернатива форми вибору ( |) та повторення (зірка Kleene, the *).

  • Значення без котирування є регулярним: [^,]*# будь-яка знака, а не кома
  • Значення, яке цитується, є регулярним: "([^\"]|\\\\|\\")*"# послідовність нічого, окрім цитати, "або цитата, що \"уникнула, або втечі\\
    • Деякі форми можуть включати пропущені цитати з цитатами, що додає варіант ("")*"до виразу вище.
  • Дозволене значення є регулярним: <неоцінене значення> |<ціноване значення>
  • Один рядок CSV є регулярним: <значення> (,<значення>)*
  • Послідовність розділених рядків \nтакож очевидно регулярна.

Я не ретельно перевіряв кожен із цих виразів і ніколи не визначав групи улову. Я також замовчується деяких тонкощів, як варіанти символів , які можуть бути використані замість ,, "або роздільники рядків: вони не порушують регулярність, ви просто отримаєте декілька кілька різних мов.

Якщо ви можете помітити проблему в цьому доказі, будь ласка, прокоментуйте! :)

Але, незважаючи на це, практичний аналіз файлів CSV за допомогою чистих регулярних виразів може бути проблематичним. Потрібно знати, який із варіантів подається в аналізатор, а стандарту для цього немає. Ви можете спробувати кілька парсерів проти кожного рядка, поки один не вдається, або якось поділити коментарі форми формату. Але це може зажадати інших засобів, крім регулярних виразів, щоб зробити це ефективно, або взагалі.


4
Абсолютно +1 для практичного моменту. Я впевнений, що десь глибоко є приклад (надуманого) значення, яке б порушило версію котируваного значення, я просто не знаю, що це таке. "Весело" з кількома парсерами було б "ці дві роботи, але дайте різні відповіді"

1
Очевидно, вам знадобляться різні зворотні знаки для зворотних косих котирувань порівняно з подвоєними цитатами, що уникнули. Зворотний вираз для першого типу csv поля повинен бути чимось подібним [^,"]*|"(\\(\\|")|[^\\"])*", а другий - чимось подібним [^,"]*|"(""|[^"])*". (Обережно, як я не перевіряв жодного з них!)
штурм

Полюючи на щось, що може бути стандартом, є випадок, який пропускають - значення із доданим роздільником запису. Це також робить практичний аналіз ще більш цікавим, коли існує декілька різних способів вирішити це

Хороша відповідь, але якщо я запускаю perl -pi -e 's/"([^\"]|\\\\|\\")*"/yay/'і вкладаю, "I have here an item,\" that is a test\""то результат - "так, це тест \" ". Зважаючи на те, що ваш регулярний вираз є помилковим.
Спенсер Ратбун

@SpencerRathbun: коли у мене буде більше часу, я фактично випробую регулярні вирази і, ймовірно, навіть вставлю якийсь код підтвердження концепції, який проходить тести. Вибачте, робочий день триває.
9000 р.

5

Проста відповідь - напевно, ні.

Перша проблема - це відсутність стандарту. Хоча можна описати їх csv в строго визначеному вигляді, не можна сподіватися отримати строго визначені файли csv. "Будьте консервативними у тому, що робите, будьте ліберальні у тому, що приймаєте від інших", - Джон Джон Пошта

Якщо припустити, що у людини є прийнятний стандарт, виникає питання про втечу персонажів, і чи потрібно їх збалансувати.

Рядок у багатьох форматах CSV визначається як string value 1,string value 2. Однак, якщо цей рядок містить кому, він є зараз "string, value 1",string value 2. Якщо вона містить цитату, вона стає "string, ""value 1""",string value 2.

На даний момент я вважаю, що це неможливо. Проблема полягає у тому, що вам потрібно визначити, скільки цитат ви прочитали, і якщо кома знаходиться всередині або поза режимом подвійного цитування значення. Врівноваження круглих дужок - неможлива проблема регулярного вираження. Деякі розширені системи регулярного вираження (PCRE) можуть з цим впоратися, але тоді це не регулярний вираз.

Https://stackoverflow.com/questions/8629763/csv-parsing-with-a-context-free-grammar може бути корисним.


Змінено:

Я переглядав формати для втечі символів і не знайшов жодного, який потребував би довільного підрахунку - так, мабуть, це не проблема.

Однак є питання, що є символом втечі та роздільником запису (для початку). http://www.csvreader.com/csv_format.php добре читається на різних форматах у дикій природі.

  • Правила для цитованого рядка (якщо це один рядок, що цитується, або рядок з подвійним цитуванням) відрізняються.
    • 'This, is a value' проти "This, is a value"
  • Правила втечі персонажів
    • "This ""is a value""" проти "This \"is a value\""
  • Обробка вбудованого роздільника записів ({rd})
    • (сира вставка) "This {rd}is a value"vs (втекла) "This \{rd}is a value"vs (перекладено)"This {0x1C}is a value"

Ключовим тут є те, що можна мати рядок, який завжди матиме кілька дійсних інтерпретацій.

Пов'язане запитання (для крайових випадків) "чи можливе прийняття недійсного рядка?"

Я все ще сильно сумніваюся, що існує регулярний вираз, який може відповідати кожному дійсному CSV, створеному якоюсь програмою, і відхиляти кожен csv, який неможливо проаналізувати.


1
Цитати всередині лапок не повинні бути врівноваженими. Замість цього, має бути парна кількість лапок перед вкладеної цитатою, яка, очевидно , регулярна: ("")*". Якщо котирування всередині значення є позабалансовими, це вже не наша справа.
9000

Це моє становище, зіткнувшись з цими жахливими виправданнями для "передачі даних" у минулому. Єдине, що належним чином поводилося з ними, було парсером, чистий регулярний вираз пробивався кожні кілька тижнів.
Спенсер Ратбун

2

Спочатку визначте граматику для свого CSV (чи розділені польові обмежувачі поля чи якось закодовані, якщо вони відображаються в тексті?), А потім можна визначити, чи воно підлягає відриву з регулярним виразом. Граматика по-перше: парсер друга: http://www.boyet.com/articles/csvparser.html Слід зазначити, що цей метод використовує токенізатор - але я не можу створити регулярний вираз POSIX, який би відповідав усім крайовим випадкам. Якщо ви використовуєте формати CSV нерегулярні та без контексту ... тоді ваша відповідь у вашому запитанні. Хороший огляд тут: http://nikic.github.com/2012/06/15/The-true-power-of-regular-expressions.html


2

Цей регулярний генепп може токенізувати нормальний CSV, як описано в RFC:

/("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/

Пояснення:

  • ("(?:[^"]|"")*"|[^,"\n\r]*) - поле CSV, цитується чи ні
    • "(?:[^"]|"")*" - котируване поле;
      • [^"]|""- кожен символ або немає ", або "врятується як""
    • [^,"\n\r]* - поле без котирування, яке може не містити , " \n \r
  • (,|\r?\n|\r)- наступний роздільник, ,або новий рядок
    • \r?\n|\r - новий рядок, один із \r\n \n \r

Цілий файл CSV можна зіставити та перевірити, використовуючи цей регулярний вираз. Потім потрібно зафіксувати цитовані поля та розділити їх на рядки на основі роздільників.

Ось код для аналізатора CSV в Javascript, заснований на регулярному вираженні:

var csv_tokens_rx = /("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/y;
var csv_unescape_quote_rx = /""/g;
function csv_parse(s) {
    if (s && s.slice(-1) != '\n')
        s += '\n';
    var ok;
    var rows = [];
    var row = [];
    csv_tokens_rx.lastIndex = 0;
    while (true) {
        ok = csv_tokens_rx.lastIndex == s.length;
        var m = s.match(csv_tokens_rx);
        if (!m)
            break;
        var v = m[1], d = m[2];
        if (v[0] == '"') {
            v = v.slice(1, -1);
            v = v.replace(csv_unescape_quote_rx, '"');
        }
        if (d == ',' || v)
            row.push(v);
        if (d != ',') {
            rows.push(row)
            row = [];
        }
    }
    return ok ? rows : null;
}

Чи допоможе ця відповідь вирішити свою аргументацію, вирішувати вам; Мені просто приємно мати невеликий, простий і правильний аналізатор CSV.

На мою думку, lexпрограма є більш-менш великим регулярним виразом, і вони можуть мати значно складніші формати, такі як мова програмування С.

З посиланням на визначення RFC 4180 :

  1. розрив лінії (CRLF) - Зворотний вираз є більш гнучким, що дозволяє CRLF, LF або CR.
  2. Останній запис у файлі може мати або не мати розрив закінчення рядка. Зворотний перелік, як це потрібно, вимагає остаточного розриву рядка, але аналізатор коригує це.
  3. Можливо, необов'язковий рядок заголовка - це не впливає на аналізатор.
  4. Кожен рядок повинен містити однакову кількість полів у всьому файлі - не примусові
    пробіли вважаються частиною поля і не повинні їх ігнорувати - нормально
    Останнє поле в записі не повинно супроводжуватися комою - не застосовується
  5. Кожне поле може бути, а може і не бути укладеним у подвійні лапки ... - добре
  6. Поля, що містять розриви рядків (CRLF), подвійні лапки та коми, повинні бути укладені у подвійні лапки - добре
  7. подвійну цитату, що з’являється всередині поля, потрібно уникнути, передуючи їй ще однією подвійною цитатою - добре

Сам регулярний вираз задовольняє більшості вимог RFC 4180. Я не згоден з іншими, але легко налаштувати аналізатор для їх реалізації.


1
це більше схоже на саморекламу, ніж вирішення заданого питання, див. Як відповісти
gnat

1
@gnat, я відредагував свою відповідь, щоб дати більше пояснень, перевірити регулярний вирівнювання щодо RFC 4180 і зробити його менш саморекламою. Я вважаю, що ця відповідь має значення, оскільки містить перевірений регулярний вираз, який може змістити найпоширенішу форму CSV, яку використовують Excel та інші електронні таблиці. Я думаю, що це вирішує питання. Маленький аналізатор CSV демонструє, що легко аналізувати CSV, використовуючи цей регулярний вираз.
Сем Уоткінс

Не бажаючи надмірно рекламувати себе, ось мої повні маленькі бібліотеки csv та tsv, якими я користуюсь як частина невеликого додатка для електронних таблиць (аркуші Google для мене занадто важкі). Це відкритий код / ​​публічне надходження / CC0-код, як і всі речі, які я публікую. Я сподіваюся, що це може бути корисним для когось іншого. sam.aiki.info/code/js
Сем Уоткінс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.