Як я можу проаналізувати рядок CSV за допомогою JavaScript, який містить кому в даних?


93

У мене є такий тип рядка

var string = "'string, duppi, du', 23, lala"

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

Я не можу зрозуміти правильний регулярний вираз для розбиття ...

string.split(/,/)

дасть мені

["'string", " duppi", " du'", " 23", " lala"]

але результат повинен бути:

["string, duppi, du", "23", "lala"]

Чи існує крос-браузерне рішення?


Це завжди одинарні лапки? Чи є коли-небудь одинарні лапки всередині рядка із цитуваннями? Якщо так, то як це вдається уникнути (зворотна коса риса, подвоєння)?
Phrogz

Що робити, якщо символи лапок повністю взаємозамінні між подвійними та одинарними лапками, як у JavaScript та коді HTML / XML? Якщо так, то для цього потрібна більш обширна операція синтаксичного аналізу, ніж CSV.
austincheney

насправді так, всередині може бути одна цитата, втеча з зворотною рискою буде добре.
Ганс,

Чи може значення бути рядком із подвійними лапками?
ridgerunner

1
Папа Парс відмінно справляється з роботою. Розбір локального CSV файлу з JavaScript і Papa Parse: joyofdata.de/blog / ...
Раффаель

Відповіді:


214

Застереження

2014-12-01 Оновлення: Відповідь нижче працює лише для одного дуже конкретного формату CSV. Як правильно зазначив DG у коментарях , це рішення не відповідає визначенню CSV RFC 4180, а також не відповідає формату Microsoft Excel. Це рішення просто демонструє, як можна проаналізувати один (нестандартний) рядок вводу CSV, що містить поєднання типів рядків, де рядки можуть містити екрановані лапки та коми.

Нестандартне рішення CSV

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

Дано: Визначення "рядка CSV"

Для цілей цього обговорення "рядок CSV" складається з нуля або більше значень, де кілька значень відокремлюються комою. Кожне значення може складатися з:

  1. Рядок із подвійними лапками (може містити одинарні лапки, що не є).
  2. Рядок із одинарними лапками (може містити подвійні лапки).
  3. Рядок без цитат ( не може містити лапки, коми чи зворотні скісні риски).
  4. Порожнє значення. (Значення всього пробілу вважається порожнім.)

Правила / Примітки:

  • Значення, вказані в лапках, можуть містити коми.
  • Значення, що вказані в лапках, можуть містити що-небудь, наприклад, наприклад '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: Переміщено застереження до початку.

1
@Evan Plaice - Дякую за приємні слова. Звичайно, ви можете використовувати будь-який сепаратор. Просто замініть кожну кому в моєму регулярному виразі на вибраний роздільник (але роздільник не може бути пробілом). Ура.
ridgerunner

2
@Evan Plaice - Ви можете використовувати будь-який з моїх регулярних виразів для будь-яких цілей, які бажаєте. Пам’ятка про визнання була б непоганою, але необов’язковою. Успіху з вашим плагіном. На здоров’я!
ridgerunner

1
Класно, ось код проекту.google.com/ p/jquery-csv . Зрештою, я хочу додати формат розширення до CSV, який називається SSV (Structured Separated Values), який є просто CSV із включеними метаданими (тобто роздільником, роздільником, закінченням рядка тощо).
Еван Плейс,

1
Велике спасибі за цю чудову реалізацію - я використав її як основу для модуля Node.js ( csv-iterator ).
mirkokiefer

3
Я вітаю подробиці та роз'яснення вашої відповіді, але десь слід зауважити, що ваше визначення CSV не відповідає RFC 4180, що є найближчим, що є стандартним для CSV, і, що я можу сказати анекдотично, зазвичай використовується. Зокрема, це був би звичайний спосіб "уникнути" подвійних лапок у рядковому полі: "field one", "field two", "a ""final"" field containing two double quote marks"я не перевіряв відповідь Тревора Діксона на цій сторінці, але це відповідь, яка стосується визначення CSV RFC 4180.
ДГ.

53

Рішення RFC 4180

Це не вирішує рядок у питанні, оскільки його формат не відповідає RFC 4180; прийнятним кодуванням є уникнення подвійних лапок із подвійними лапками. Наведене нижче рішення коректно працює з файлами CSV d / l із електронних таблиць Google.

ОНОВИТИ (3/2017)

Розбір одного рядка буде неправильним. Відповідно до RFC 4180 поля можуть містити CRLF, що змусить будь-який зчитувач рядків зламати файл CSV. Ось оновлена ​​версія, яка аналізує рядок CSV:

'use strict';

function csvToArray(text) {
    let p = '', row = [''], ret = [row], i = 0, r = 0, s = !0, l;
    for (l of text) {
        if ('"' === l) {
            if (s && l === p) row[i] += l;
            s = !s;
        } else if (',' === l && s) l = row[++i] = '';
        else if ('\n' === l && s) {
            if ('\r' === p) row[i] = row[i].slice(0, -1);
            row = ret[++r] = [l = '']; i = 0;
        } else row[i] += l;
        p = l;
    }
    return ret;
};

let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"\r\n"2nd line one","two with escaped """" double quotes""","three, with, commas",four with no quotes,"five with CRLF\r\n"';
console.log(csvToArray(test));

СТАРИЙ ВІДПОВІДЬ

(Однорядкове рішення)

function CSVtoArray(text) {
    let ret = [''], i = 0, p = '', s = true;
    for (let l in text) {
        l = text[l];
        if ('"' === l) {
            s = !s;
            if ('"' === p) {
                ret[i] += '"';
                l = '-';
            } else if ('' === p)
                l = '-';
        } else if (s && ',' === l)
            l = ret[++i] = '';
        else
            ret[i] += l;
        p = l;
    }
    return ret;
}
let test = '"one","two with escaped """" double quotes""","three, with, commas",four with no quotes,five for fun';
console.log(CSVtoArray(test));

І для задоволення, ось як ви створюєте CSV з масиву:

function arrayToCSV(row) {
    for (let i in row) {
        row[i] = row[i].replace(/"/g, '""');
    }
    return '"' + row.join('","') + '"';
}

let row = [
  "one",
  "two with escaped \" double quote",
  "three, with, commas",
  "four with no quotes (now has)",
  "five for fun"
];
let text = arrayToCSV(row);
console.log(text);


1
цей зробив роботу за мене, а не інший
WtFudgE

7

Граматика PEG (.js), яка обробляє приклади RFC 4180 на веб-сайті http://en.wikipedia.org/wiki/Comma-separated_values :

start
  = [\n\r]* first:line rest:([\n\r]+ data:line { return data; })* [\n\r]* { rest.unshift(first); return rest; }

line
  = first:field rest:("," text:field { return text; })*
    & { return !!first || rest.length; } // ignore blank lines
    { rest.unshift(first); return rest; }

field
  = '"' text:char* '"' { return text.join(''); }
  / text:[^\n\r,]* { return text.join(''); }

char
  = '"' '"' { return '"'; }
  / [^"]

Тест за адресою http://jsfiddle.net/knvzk/10 або https://pegjs.org/online .

Завантажте згенерований парсер за адресою https://gist.github.com/3362830 .


6

У мене був дуже конкретний випадок використання, коли я хотів скопіювати комірки з Google Таблиць у свій веб-додаток. Клітинки можуть містити подвійні лапки та символи нового рядка. За допомогою копіювання та вставки комірки розмежовуються символами табуляції, а клітинки з непарними даними подвоюються. Я спробував це основне рішення, пов'язану статтю, використовуючи регулярний вираз, і Jquery-CSV, і CSVToArray. http://papaparse.com/ Єдиний, хто працював нестандартно. Копіювати та вставляти безшовно за допомогою Google Таблиць із параметрами автоматичного визначення за замовчуванням.


1
Це слід оцінювати набагато вище, ніколи не намагайтеся згорнути власний синтаксичний аналізатор CSV, він не працюватиме належним чином - особливо при використанні регулярних виразів. Папапарс чудовий - використовуйте його!
cbley

6

Мені сподобалась відповідь FakeRainBrigand, однак вона містить кілька проблем: він не може обробляти пробіли між лапкою та комою, і не підтримує 2 послідовних коми. Я спробував відредагувати його відповідь, але мою редакцію відхилили рецензенти, які, мабуть, не розуміли мого коду. Ось моя версія коду FakeRainBrigand. Також є скрипка: http://jsfiddle.net/xTezm/46/

String.prototype.splitCSV = function() {
        var matches = this.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
        for (var n = 0; n < matches.length; ++n) {
            matches[n] = matches[n].trim();
            if (matches[n] == ',') matches[n] = '';
        }
        if (this[0] == ',') matches.unshift("");
        return matches;
}

var string = ',"string, duppi, du" , 23 ,,, "string, duppi, du",dup,"", , lala';
var parsed = string.splitCSV();
alert(parsed.join('|'));

4

Люди за це, здавалося, були проти RegEx. Чому?

(\s*'[^']+'|\s*[^,]+)(?=,|$)

Ось код. Я теж зробив скрипку .

String.prototype.splitCSV = function(sep) {
  var regex = /(\s*'[^']+'|\s*[^,]+)(?=,|$)/g;
  return matches = this.match(regex);    
}

var string = "'string, duppi, du', 23, 'string, duppi, du', lala";
var parsed = string.splitCSV();
alert(parsed.join('|'));

3
Хм, у вашого регулярного виразу є деякі проблеми: він не може обробляти пробіли між лапкою та комою і не підтримує 2 поспіль коми. Я оновив вашу відповідь кодом, який виправляє обидві проблеми, і зробив нову скрипку: jsfiddle.net/xTezm/43
HammerNL

З якоїсь причини моє редагування вашого коду було відхилено, оскільки воно "відхилятиметься від початкового задуму публікації". Дуже дивно!? Я просто взяв ваш код і вирішив з ним дві проблеми. Як це змінює намір посади !? У будь-якому разі ... Я просто додав нову відповідь на це запитання.
HammerNL

Гарне запитання у вашій відповіді, @FakeRainBrigand. Я один за все для регулярного виразу, і тому я визнаю, що це неправильний інструмент для роботи.
niry

2
@niry мій код тут жахливий. Я обіцяю, що я покращився за останні 6 років :-p
Brigand

4

Додавання ще одного до списку, тому що я вважаю, що все вищезазначене недостатньо "ПОЦІЛУЮЧЕ".

Цей використовує регулярний вираз, щоб знайти або коми, або нові рядки, пропускаючи цитовані елементи. Сподіваємось, це те, що нубі можуть прочитати самостійно. У splitFinderрегулярному виразі є три речі, які він робить (розділений на a |):

  1. , - знаходить коми
  2. \r?\n - знаходить нові рядки, (можливо, з поверненням каретки, якщо експортер був приємний)
  3. "(\\"|[^"])*?"- пропускає будь-що, оточене лапками, тому що там коми та нові рядки не мають значення. Якщо \\"в цитованому елементі є екранована цитата , вона буде захоплена до того, як буде знайдена кінцева цитата.

const splitFinder = /,|\r?\n|"(\\"|[^"])*?"/g;

function csvTo2dArray(parseMe) {
  let currentRow = [];
  const rowsOut = [currentRow];
  let lastIndex = splitFinder.lastIndex = 0;
  
  // add text from lastIndex to before a found newline or comma
  const pushCell = (endIndex) => {
    endIndex = endIndex || parseMe.length;
    const addMe = parseMe.substring(lastIndex, endIndex);
    // remove quotes around the item
    currentRow.push(addMe.replace(/^"|"$/g, ""));
    lastIndex = splitFinder.lastIndex;
  }


  let regexResp;
  // for each regexp match (either comma, newline, or quoted item)
  while (regexResp = splitFinder.exec(parseMe)) {
    const split = regexResp[0];

    // if it's not a quote capture, add an item to the current row
    // (quote captures will be pushed by the newline or comma following)
    if (split.startsWith(`"`) === false) {
      const splitStartIndex = splitFinder.lastIndex - split.length;
      pushCell(splitStartIndex);

      // then start a new row if newline
      const isNewLine = /^\r?\n$/.test(split);
      if (isNewLine) { rowsOut.push(currentRow = []); }
    }
  }
  // make sure to add the trailing text (no commas or newlines after)
  pushCell();
  return rowsOut;
}

const rawCsv = `a,b,c\n"test\r\n","comma, test","\r\n",",",\nsecond,row,ends,with,empty\n"quote\"test"`
const rows = csvTo2dArray(rawCsv);
console.log(rows);


Якщо я читаю свій файл через файлReader та мій результат: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 як я можу проаналізувати на основі вказаних стовпців?
bluePearl

Після того, як ви отримаєте 2d-масив, видаліть перший індекс (це ваші імена опису), а потім повторіть решту масиву, створюючи об'єкти з кожним із значень як властивість. Це буде виглядати так:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Сеф Рід

3

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

Ви можете спочатку перевести всі одинарні лапки в подвійні лапки:

string = string.replace( /'/g, '"' );

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

// Quoted fields.
"(?:'([^']*(?:''[^']*)*)'|" +

Однак це передбачає певну розмітку, яка не зрозуміла з вашого запитання. Будь ласка, поясніть, якими можуть бути всі різні можливості розмітки, відповідно до мого коментаря до вашого запитання.


2

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

Ви не можете використовувати регулярний вираз для цього. Ви насправді повинні написати мікро-синтаксичний аналізатор, щоб проаналізувати рядок, який ви хочете розділити. Заради цієї відповіді я називатиму цитовані частини ваших рядків як підрядки. Вам потрібно спеціально пройтися по струні. Розглянемо наступний випадок:

var a = "some sample string with \"double quotes\" and 'single quotes' and some craziness like this: \\\" or \\'",
    b = "sample of code from JavaScript with a regex containing a comma /\,/ that should probably be ignored.";

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

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

https://github.com/austincheney/Pretty-Diff/blob/master/fulljsmin.js


2

Доповнюючи цю відповідь

Якщо вам потрібно проаналізувати цитати, що захищені з іншою цитатою, приклад:

"some ""value"" that is on xlsx file",123

Можна використовувати

function parse(text) {
  const csvExp = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|"([^""]*(?:"[\S\s][^""]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;

  const values = [];

  text.replace(csvExp, (m0, m1, m2, m3, m4) => {
    if (m1 !== undefined) {
      values.push(m1.replace(/\\'/g, "'"));
    }
    else if (m2 !== undefined) {
      values.push(m2.replace(/\\"/g, '"'));
    }
    else if (m3 !== undefined) {
      values.push(m3.replace(/""/g, '"'));
    }
    else if (m4 !== undefined) {
      values.push(m4);
    }
    return '';
  });

  if (/,\s*$/.test(text)) {
    values.push('');
  }

  return values;
}

Я виявив, що це все ще не вдається "jjj "" kkk""","123"
проаналізувати

2

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

stringLine = stringLine.replace(/\0/g, "" );

2

Я також стикався з такою ж проблемою, коли мені доводилося аналізувати файл CSV.

Файл містить адресу стовпця, що містить ','.

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

Я використовував Node.js для синтаксичного аналізу файлу та бібліотек, таких як baby parse та csvtojson .

Приклад файлу -

address,pincode
foo,baar , 123456

Поки я аналізував безпосередньо, не використовуючи синтаксичний аналіз дитини в JSON, я отримував:

[{
 address: 'foo',
 pincode: 'baar',
 'field3': '123456'
}]

Тому я написав код, який видаляє кому (,) з будь-яким іншим роздільником у кожному полі:

/*
 csvString(input) = "address, pincode\\nfoo, bar, 123456\\n"
 output = "address, pincode\\nfoo {YOUR DELIMITER} bar, 123455\\n"
*/
const removeComma = function(csvString){
    let delimiter = '|'
    let Baby = require('babyparse')
    let arrRow = Baby.parse(csvString).data;
    /*
      arrRow = [
      [ 'address', 'pincode' ],
      [ 'foo, bar', '123456']
      ]
    */
    return arrRow.map((singleRow, index) => {
        //the data will include
        /*
        singleRow = [ 'address', 'pincode' ]
        */
        return singleRow.map(singleField => {
            //for removing the comma in the feild
            return singleField.split(',').join(delimiter)
        })
    }).reduce((acc, value, key) => {
        acc = acc +(Array.isArray(value) ?
         value.reduce((acc1, val)=> {
            acc1 = acc1+ val + ','
            return acc1
        }, '') : '') + '\n';
        return acc;
    },'')
}

Повернута функція може бути передана в бібліотеку csvtojson і, отже, результат може бути використаний.

const csv = require('csvtojson')

let csvString = "address, pincode\\nfoo, bar, 123456\\n"
let jsonArray = []
modifiedCsvString = removeComma(csvString)
csv()
  .fromString(modifiedCsvString)
  .on('json', json => jsonArray.push(json))
  .on('end', () => {
    /* do any thing with the json Array */
  })

Тепер ви можете отримати результат, як:

[{
  address: 'foo, bar',
  pincode: 123456
}]

2

Немає регулярних виразів, читабельних і відповідно до https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules :

function csv2arr(str: string) {
    let line = ["",];
    const ret = [line,];
    let quote = false;

    for (let i = 0; i < str.length; i++) {
        const cur = str[i];
        const next = str[i + 1];

        if (!quote) {
            const cellIsEmpty = line[line.length - 1].length === 0;
            if (cur === '"' && cellIsEmpty) quote = true;
            else if (cur === ",") line.push("");
            else if (cur === "\r" && next === "\n") { line = ["",]; ret.push(line); i++; }
            else if (cur === "\n" || cur === "\r") { line = ["",]; ret.push(line); }
            else line[line.length - 1] += cur;
        } else {
            if (cur === '"' && next === '"') { line[line.length - 1] += cur; i++; }
            else if (cur === '"') quote = false;
            else line[line.length - 1] += cur;
        }
    }
    return ret;
}

1

Відповідно до цього допису в блозі , ця функція повинна це робити:

String.prototype.splitCSV = function(sep) {
  for (var foo = this.split(sep = sep || ","), x = foo.length - 1, tl; x >= 0; x--) {
    if (foo[x].replace(/'\s+$/, "'").charAt(foo[x].length - 1) == "'") {
      if ((tl = foo[x].replace(/^\s+'/, "'")).length > 1 && tl.charAt(0) == "'") {
        foo[x] = foo[x].replace(/^\s*'|'\s*$/g, '').replace(/''/g, "'");
      } else if (x) {
        foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep));
      } else foo = foo.shift().split(sep).concat(foo);
    } else foo[x].replace(/''/g, "'");
  } return foo;
};

Ви б назвали це так:

var string = "'string, duppi, du', 23, lala";
var parsed = string.splitCSV();
alert(parsed.join("|"));

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


Уявіть, що потрібно робити все це в регулярному виразі. Ось чому регулярні вирази насправді іноді не підходять для синтаксичного аналізу.
CanSpice

Це рішення просто не працює. Враховуючи оригінальний тестовий рядок:, "'string, duppi, du', 23, lala"ця функція повертає:["'string"," duppi"," du'"," 23"," lala"]
ridgerunner

@ridgerunner: Правильно ти маєш. Я відредагував відповідь та jsfiddle, щоб виправити функцію. В основному, я перейшов "'"на '"'і навпаки.
CanSpice

Це допомогло, але тепер функція неправильно обробляє рядки CSV з одинарними цитатами, що мають значення з подвійними лапками. наприклад, скасування типів котирувань оригінального тестового рядка таким чином: '"string, duppi, du", 23, lala'результати в:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner

@CanSpice, твій коментар надихнув мене спробувати RegEx. Він має не так багато функцій, але їх можна було б легко додати. (Моя відповідь на цій сторінці, якщо вам цікаво.)
Brigand

0

Регулярні вислови на допомогу! Ці кілька рядків коду обробляють правильно вказані поля з вбудованими комами, лапками та новими рядками на основі стандарту RFC 4180.

function parseCsv(data, fieldSep, newLine) {
    fieldSep = fieldSep || ',';
    newLine = newLine || '\n';
    var nSep = '\x1D';
    var qSep = '\x1E';
    var cSep = '\x1F';
    var nSepRe = new RegExp(nSep, 'g');
    var qSepRe = new RegExp(qSep, 'g');
    var cSepRe = new RegExp(cSep, 'g');
    var fieldRe = new RegExp('(?<=(^|[' + fieldSep + '\\n]))"(|[\\s\\S]+?(?<![^"]"))"(?=($|[' + fieldSep + '\\n]))', 'g');
    var grid = [];
    data.replace(/\r/g, '').replace(/\n+$/, '').replace(fieldRe, function(match, p1, p2) {
        return p2.replace(/\n/g, nSep).replace(/""/g, qSep).replace(/,/g, cSep);
    }).split(/\n/).forEach(function(line) {
        var row = line.split(fieldSep).map(function(cell) {
            return cell.replace(nSepRe, newLine).replace(qSepRe, '"').replace(cSepRe, ',');
        });
        grid.push(row);
    });
    return grid;
}

const csv = 'A1,B1,C1\n"A ""2""","B, 2","C\n2"';
const separator = ',';      // field separator, default: ','
const newline = ' <br /> '; // newline representation in case a field contains newlines, default: '\n' 
var grid = parseCsv(csv, separator, newline);
// expected: [ [ 'A1', 'B1', 'C1' ], [ 'A "2"', 'B, 2', 'C <br /> 2' ] ]

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

Клонуйте / завантажте код на https://github.com/peterthoeny/parse-csv-js


0

Окрім чудової та повної відповіді від ridgerunner , я придумав дуже просте обхідне рішення, коли ваш сервер працює на PHP.

Додайте цей файл PHP бакенда домену (наприклад: csv.php)

<?php
    session_start(); // Optional
    header("content-type: text/xml");
    header("charset=UTF-8");
    // Set the delimiter and the End of Line character of your CSV content:
    echo json_encode(array_map('str_getcsv', str_getcsv($_POST["csv"], "\n")));
?>

Тепер додайте цю функцію до свого набору інструментів JavaScript (я повинен трохи переглянути, щоб зробити перехресний браузер, я вважаю).

function csvToArray(csv) {
    var oXhr = new XMLHttpRequest;
    oXhr.addEventListener("readystatechange",
        function () {
            if (this.readyState == 4 && this.status == 200) {
                console.log(this.responseText);
                console.log(JSON.parse(this.responseText));
            }
        }
    );
    oXhr.open("POST","path/to/csv.php",true);
    oXhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
    oXhr.send("csv=" + encodeURIComponent(csv));
}

Це буде коштувати вам одного дзвінка Ajax, але принаймні ви не дублюєте код і не включаєте жодної зовнішньої бібліотеки.

Посилання: http://php.net/manual/en/function.str-getcsv.php


0

Ви можете використовувати papaparse.js, як приклад нижче:

<!DOCTYPE html>
<html lang="en">

    <head>
        <title>CSV</title>
    </head>

    <body>
        <input type="file" id="files" multiple="">
        <button onclick="csvGetter()">CSV Getter</button>
        <h3>The Result will be in the Console.</h3>

        <script src="papaparse.min.js"></script>

        <script>
            function csvGetter() {

                var file = document.getElementById('files').files[0];
                Papa.parse(file, {
                    complete: function(results) {
                        console.log(results.data);
                    }
                });
            }
          </script>
    </body>

</html>

Не забудьте включити papaparse.js в ту саму папку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.