Как я могу разобрать строку CSV с помощью JavaScript, который содержит запятую в данных?

98

У меня такой тип строки

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

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

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

string.split(/,/)

даст мне

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

но результат должен быть:

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

Есть ли кроссбраузерное решение?

Ганс
источник
Всегда ли это одинарные кавычки? Есть ли когда-нибудь одинарные кавычки внутри строки в кавычках? Если да, то как это экранировать (обратная косая черта, удвоение)?
Phrogz
Что, если символы кавычек полностью взаимозаменяемы между двойными и одинарными кавычками, как в коде JavaScript и HTML / XML? Если это так, то это требует более обширной операции синтаксического анализа, чем CSV.
Остинчени
на самом деле да, внутри могла быть одинарная кавычка, можно было бы избежать обратной косой черты.
Ганс
Может ли значение быть строкой в ​​двойных кавычках?
ridgerunner
1
Папа Парс отлично справляется. Разбор локального CSV-файла с помощью JavaScript и Papa Parse: joyofdata.de/blog/…
Рафаэль

Ответы:

217

Отказ от ответственности

Обновление 2014-12-01: ответ ниже работает только для одного очень конкретного формата CSV. Как правильно указал DG в комментариях , это решение не соответствует определению CSV RFC 4180, а также не соответствует формату Microsoft Excel. Это решение просто демонстрирует, как можно проанализировать одну (нестандартную) входную строку CSV, которая содержит сочетание типов строк, где строки могут содержать экранированные кавычки и запятые.

Нестандартное решение CSV

Как правильно указывает Остинчени , вам действительно нужно проанализировать строку от начала до конца, если вы хотите правильно обрабатывать строки в кавычках, которые могут содержать экранированные символы. Кроме того, OP четко не определяет, что такое «строка CSV» на самом деле. Сначала мы должны определить, что составляет действительную строку CSV и ее отдельные значения.

Дано: "Строка CSV" Определение

Для целей этого обсуждения «строка CSV» состоит из нуля или более значений, где несколько значений разделены запятой. Каждое значение может состоять из:

  1. Строка в двойных кавычках (может содержать неэкранированные одинарные кавычки).
  2. Строка в одинарных кавычках (может содержать неэкранированные двойные кавычки).
  3. Строка без кавычек ( не может содержать кавычек, запятых или обратных косых черт).
  4. Пустое значение. (Все пробелы считаются пустыми.)

Правила / Примечания:

  • Значения в кавычках могут содержать запятые.
  • Значения в кавычках могут содержать что угодно, например 'that\'s cool'.
  • Значения, содержащие кавычки, запятые или обратную косую черту, должны быть заключены в кавычки.
  • Значения, содержащие начальные или конечные пробелы, должны быть заключены в кавычки.
  • Обратная косая черта удаляется со всех: \'в одинарных кавычках.
  • Обратная косая черта удаляется со всех: \"в двойных кавычках.
  • Строки без кавычек удаляются от начальных и конечных пробелов.
  • Разделитель запятой может иметь смежные пробелы (которые игнорируются).

Найти:

Функция JavaScript, которая преобразует действительную строку CSV (как определено выше) в массив строковых значений.

Решение:

Регулярные выражения, используемые в этом решении, сложны. И (IMHO) все нетривиальные регулярные выражения должны быть представлены в режиме свободного интервала с большим количеством комментариев и отступов. К сожалению, JavaScript не поддерживает режим свободного интервала. Таким образом, регулярные выражения, реализованные в этом решении, сначала представлены в собственном синтаксисе регулярных выражений (выраженном с помощью удобного синтаксиса Python для r'''...'''необработанных многострочных строк).

Во-первых, вот регулярное выражение, которое проверяет соответствие строки 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"

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

Редактировать историю

  • 2014-05-19: Добавлен отказ от ответственности.
  • 2014-12-01: Заявление об отказе от ответственности перемещено наверх.
скакун
источник
1
@Evan Plaice - Спасибо за хорошие слова. Конечно, вы можете использовать любой разделитель. Просто замените каждую запятую в моем регулярном выражении выбранным разделителем (но разделитель не может быть пробелом). Ура.
ridgerunner
2
@Evan Plaice - Вы можете использовать любое из моих регулярных выражений для любых целей. Признание было бы неплохо, но не обязательно. Удачи с плагином. Ура!
ridgerunner
1
Круто, вот проект code.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.
ДГ.
54

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 Таблиц в свое веб-приложение. Ячейки могут содержать двойные кавычки и символы новой строки. При использовании копирования и вставки ячейки разделяются символами табуляции, а ячейки с нечетными данными заключаются в двойные кавычки. Я пробовал это основное решение, связанную статью с использованием regexp, Jquery-CSV и CSVToArray. http://papaparse.com/ - единственный, который работал из коробки. Копирование и вставка выполняется без проблем с Google Таблицами с параметрами автоматического обнаружения по умолчанию.

Bjcullinan
источник
1
Это должно быть оценено намного выше, никогда не пытайтесь использовать собственный парсер CSV, он не будет работать правильно - особенно при использовании регулярных выражений. Papaparse потрясающий - используйте его!
cbley 03
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('|'));
HammerNL
источник
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
Хм, у вашего регулярного выражения есть некоторые проблемы: оно не может обрабатывать пробелы между цитатой и запятой и не поддерживает две последовательные запятые. Я обновил ваш ответ кодом, который исправляет обе проблемы, и создал новую скрипку: jsfiddle.net/xTezm/43
HammerNL
По какой-то причине мое изменение вашего кода было отклонено, потому что оно «отклонялось от первоначального замысла сообщения». Очень странно!? Я просто взял ваш код и исправил с ним две проблемы. Как это меняет замысел сообщения !? В любом случае ... Я просто добавил новый ответ на этот вопрос.
HammerNL
Хороший вопрос в вашем ответе, @FakeRainBrigand. Я, со своей стороны, все для регулярных выражений, и поэтому я признаю, что это неправильный инструмент для работы.
niry
2
@niry, мой код ужасен. Обещаю, что за последние 6 лет мне стало лучше :-p
Brigand
4

Добавляю еще один в список, потому что я считаю, что все вышеперечисленное не совсем "ПОЦЕЛУЙ".

Здесь используется регулярное выражение для поиска запятых или новых строк при пропуске цитируемых элементов. Надеюсь, новички смогут прочитать это самостоятельно. У splitFinderрегулярного выражения есть три функции (разделенные на |):

  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);

Сеф Рид
источник
Если я прочитал свой файл через fileReader и получил свой результат: 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.
"(?:'([^']*(?:''[^']*)*)'|" +

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

Phrogz
источник
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.";

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

Я не собираюсь писать для вас такой уровень сложности кода, но вы можете взглянуть на то, что я недавно написал, в котором есть нужный вам шаблон. Этот код не имеет ничего общего с запятыми, но в остальном он является достаточно допустимым микро-парсером, которым вы можете следовать при написании собственного кода. Посмотрите на функцию 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;
}
BrunoLM
источник
Я обнаружил, что это все еще не удается "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
источник
Представьте, что все это нужно делать в регулярном выражении. Вот почему регулярные выражения иногда не подходят для синтаксического анализа.
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 в ту же папку.

Тахсин Алаа
источник