Como posso analisar uma string CSV com JavaScript, que contém vírgulas nos dados?

93

Eu tenho o seguinte tipo de string

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

Quero dividir a string em uma matriz em cada vírgula, mas apenas as vírgulas fora das aspas simples.

Não consigo descobrir a expressão regular correta para a divisão ...

string.split(/,/)

vai me dar

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

mas o resultado deve ser:

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

Existe uma solução para vários navegadores?

Hans
fonte
É sempre aspas simples? Existe sempre uma aspa simples dentro de uma string entre aspas? Em caso afirmativo, como ele é escapado (barra invertida, dobrado)?
Phrogz
E se os caracteres de aspas forem completamente intercambiáveis ​​entre aspas duplas e simples, como no código JavaScript e HTML / XML? Nesse caso, isso requer uma operação de análise mais ampla do que o CSV.
austincheney
na verdade, sim, pode haver uma aspa simples, escapando com barra invertida seria suficiente.
Hans
Um valor pode ser uma string entre aspas duplas?
ridgerunner
1
Papa Parse faz um bom trabalho. Analisando um arquivo CSV local com JavaScript e Papa Parse: joyofdata.de/blog/…
Raffael

Respostas:

214

aviso Legal

Atualização de 01-12-2014: A resposta abaixo funciona apenas para um formato muito específico de CSV. Como corretamente apontado pelo DG nos comentários, esta solução NÃO se encaixa na definição de CSV da RFC 4180 e também NÃO se encaixa no formato MS Excel. Esta solução simplesmente demonstra como é possível analisar uma linha CSV (não padrão) de entrada que contém uma mistura de tipos de string, em que as strings podem conter aspas e vírgulas com escape.

Uma solução CSV não padrão

Como austincheney aponta corretamente, você realmente precisa analisar a string do início ao fim se quiser lidar adequadamente com strings entre aspas que podem conter caracteres de escape. Além disso, o OP não define claramente o que é realmente uma "string CSV". Primeiro, devemos definir o que constitui uma string CSV válida e seus valores individuais.

Dado: Definição de "string CSV"

Para o propósito desta discussão, uma "string CSV" consiste em zero ou mais valores, onde vários valores são separados por uma vírgula. Cada valor pode consistir em:

  1. Uma string entre aspas duplas. (pode conter aspas simples sem escape).
  2. Uma única string entre aspas. (pode conter aspas duplas sem escape).
  3. Uma string não entre aspas. (NÃO pode conter aspas, vírgulas ou barras invertidas.)
  4. Um valor vazio. (Um valor com todos os espaços em branco é considerado vazio.)

Regras / Notas:

  • Os valores citados podem conter vírgulas.
  • Os valores citados podem conter qualquer coisa com escape, por exemplo 'that\'s cool'.
  • Os valores que contêm aspas, vírgulas ou barras invertidas devem ser colocados entre aspas.
  • Valores contendo espaços em branco à esquerda ou à direita devem ser colocados entre aspas.
  • A barra invertida é removida de todos: \'em valores entre aspas simples.
  • A barra invertida é removida de todos: \"em valores entre aspas duplas.
  • Strings não citadas são eliminadas de quaisquer espaços à esquerda e à direita.
  • O separador de vírgula pode ter espaços em branco adjacentes (que são ignorados).

Encontrar:

Uma função JavaScript que converte uma string CSV válida (conforme definido acima) em uma matriz de valores de string.

Solução:

As expressões regulares usadas por esta solução são complexas. E (IMHO) todos os regexes não triviais devem ser apresentados em modo de espaçamento livre com muitos comentários e recuos. Infelizmente, o JavaScript não permite o modo de espaçamento livre. Assim, as expressões regulares implementadas por esta solução são apresentadas primeiro na sintaxe regex nativa (expressa usando a prática do Python: r'''...'''sintaxe de string de várias linhas bruta).

Primeiro, aqui está uma expressão regular que valida se uma string CVS atende aos requisitos acima:

Regex para validar uma "string 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.
"""

Se uma string corresponder à regex acima, então essa string é uma string CSV válida (de acordo com as regras declaradas anteriormente) e pode ser analisada usando a seguinte regex. O seguinte regex é então usado para corresponder a um valor da string CSV. É aplicado repetidamente até que não sejam encontradas mais correspondências (e todos os valores tenham sido analisados).

Regex para analisar um valor de uma string CSV válida:

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

Observe que há um valor de caso especial que esta regex não corresponde - o último valor quando esse valor está vazio. Este caso especial de "último valor vazio" é testado e tratado pela função js que se segue.

Função JavaScript para analisar string 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;
};

Exemplo de entrada e saída:

Nos exemplos a seguir, as chaves são usadas para delimitar o {result strings}. (Isso ajuda a visualizar espaços iniciais / finais e strings de comprimento zero.)

// 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] = {} */

Notas Adicionais:

Esta solução requer que a string CSV seja "válida". Por exemplo, valores sem aspas não podem conter barras invertidas ou aspas, por exemplo, a seguinte string CSV NÃO é válida:

var invalid1 = "one, that's me!, escaped \, comma"

Isso não é realmente uma limitação porque qualquer sub-string pode ser representada como um valor entre aspas simples ou duplas. Observe também que esta solução representa apenas uma definição possível para: "Valores separados por vírgula".

Edit: 2014-05-19: isenção de responsabilidade adicionada. Edit: 2014-12-01: isenção de responsabilidade movida para o topo.

Ridgerunner
fonte
1
@Evan Plaice - Obrigado pelas palavras bonitas. Claro que você pode usar qualquer separador. Basta substituir todas as vírgulas em minha regex pelo separador de escolha (mas o separador não pode ser um espaço em branco). Felicidades.
ridgerunner de
2
@Evan Plaice - você está convidado a usar qualquer uma das minhas regexes para qualquer finalidade que desejar. Uma nota de reconhecimento seria bom, mas não necessária. Boa sorte com seu plug-in. Felicidades!
ridgerunner de
1
Legal, aqui está o projeto code.google.com/p/jquery-csv . Eventualmente, quero adicionar um formato de extensão ao CSV chamado SSV (Structured Separated Values), que é simplesmente CSV com metadados (ou seja, delimitador, separador, final de linha, etc.) incluídos.
Evan Plaice
1
Muito obrigado por esta excelente implementação - usei-a como base para um módulo Node.js ( csv-iterator ).
mirkokiefer
3
Eu aplaudo os detalhes e esclareço sua resposta, mas deve ser observado em algum lugar que sua definição de CSV não se encaixa na RFC 4180, que é a última coisa que existe para um padrão para CSV, e que posso dizer que é comumente usada. Em particular, essa seria a maneira normal de "escapar" de um caractere de aspas duplas em um campo de string: "field one", "field two", "a ""final"" field containing two double quote marks"Não testei a resposta de Trevor Dixon nesta página, mas é uma resposta que aborda a definição de CSV da RFC 4180.
DG.
53

Solução RFC 4180

Isso não resolve a string em questão, pois seu formato não está em conformidade com RFC 4180; a codificação aceitável está escapando de aspas duplas. A solução abaixo funciona corretamente com arquivos CSV d / l de planilhas do Google.

ATUALIZAÇÃO (3/2017)

Analisar uma única linha seria errado. De acordo com a RFC 4180, os campos podem conter CRLF, o que fará com que qualquer leitor de linha quebre o arquivo CSV. Esta é uma versão atualizada que analisa a string 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));

RESPOSTA ANTIGA

(Solução de linha única)

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

E para se divertir, veja como você cria CSV a partir do array:

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

niry
fonte
1
este fez o trabalho para mim, não o outro
WtFudgE
7

Gramática PEG (.js) que lida com exemplos RFC 4180 em 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 '"'; }
  / [^"]

Teste em http://jsfiddle.net/knvzk/10 ou https://pegjs.org/online .

Baixe o analisador gerado em https://gist.github.com/3362830 .

Trevor Dixon
fonte
6

Tive um caso de uso muito específico em que queria copiar células do Planilhas Google para meu aplicativo da web. As células podem incluir aspas duplas e caracteres de nova linha. Usando copiar e colar, as células são delimitadas por caracteres de tabulação e células com dados ímpares são colocadas entre aspas. Tentei esta solução principal, o artigo vinculado usando regexp, Jquery-CSV e CSVToArray. http://papaparse.com/ É o único que funcionou fora da caixa. Copiar e colar funciona perfeitamente com o Planilhas Google, com opções de detecção automática padrão.

bjcullinan
fonte
1
Isso deve ser classificado muito mais alto, nunca tente rolar seu próprio analisador CSV, ele não funcionará corretamente - especialmente ao usar regexes. Papaparse é incrível - use-o!
cbley
6

Gostei da resposta do FakeRainBrigand, mas ela contém alguns problemas: Não consegue lidar com espaços em branco entre aspas e vírgulas e não suporta 2 vírgulas consecutivas. Tentei editar sua resposta, mas minha edição foi rejeitada por revisores que aparentemente não entenderam meu código. Aqui está minha versão do código do FakeRainBrigand. Também há um violino: 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
fonte
4

As pessoas pareciam ser contra o RegEx por isso. Por quê?

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

Aqui está o código. Eu também fiz um violino .

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('|'));
Brigand
fonte
3
Hmm, seu regexp tem alguns problemas: ele não pode lidar com espaços em branco entre uma aspa e uma vírgula e não suporta 2 vírgulas consecutivas. Atualizei sua resposta com um código que corrige os dois problemas e fiz um novo violino: jsfiddle.net/xTezm/43
HammerNL
Por alguma razão, minha edição em seu código foi rejeitada porque seria "diferente da intenção original do post". Muito estranho!? Eu apenas peguei seu código e resolvi dois problemas com ele. Como isso muda a intenção do post !? Enfim ... Simplesmente adicionei uma nova resposta a essa pergunta.
HammerNL
Boa pergunta em sua resposta, @FakeRainBrigand. Eu, pelo menos, tudo para regex e, por isso, reconheço que é a ferramenta errada para o trabalho.
niry
2
@niry meu código aqui é horrível. Eu prometo que melhorei nos últimos 6 anos :-p
Brigand
4

Adicionando mais um à lista, porque eu acho que todos os itens acima não são "KISS" o suficiente.

Este usa regex para encontrar vírgulas ou novas linhas enquanto pula os itens citados. Esperançosamente, isso é algo que os noobies podem ler por conta própria. A splitFinderregexp tem três funções (dividida por a |):

  1. , - encontra vírgulas
  2. \r?\n - encontra novas linhas, (potencialmente com retorno de carro se o exportador for bom)
  3. "(\\"|[^"])*?"- pula qualquer coisa entre aspas, porque vírgulas e novas linhas não importam aqui. Se houver uma cotação de escape \\"no item cotado, ela será capturada antes que uma cotação final possa ser encontrada.

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

Seph Reed
fonte
Se eu ler meu arquivo via fileReader e meu resultado: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 como posso analisar com base nas colunas que eu especificar?
bluePearl
Depois de obter o array 2d, remova o primeiro índice (se esses são os nomes de propriedade) e, em seguida, itere sobre o resto do array, criando objetos com cada um dos valores como uma propriedade. Será parecido com este:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed
3

Se você pode ter seu delimitador de aspas duplas, então esta é uma duplicata do código JavaScript para analisar dados CSV .

Você pode traduzir todas as aspas simples para aspas duplas primeiro:

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

... ou você pode editar o regex nessa questão para reconhecer aspas simples em vez de aspas duplas:

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

No entanto, isso pressupõe certa marcação que não está clara em sua pergunta. Por favor, esclareça quais podem ser todas as várias possibilidades de marcação, de acordo com meu comentário sobre sua pergunta.

Phrogz
fonte
2

Minha resposta presume que sua entrada é um reflexo do código / conteúdo de fontes da web onde caracteres de aspas simples e duplas são totalmente intercambiáveis, desde que ocorram como um conjunto de correspondência sem escape.

Você não pode usar regex para isso. Na verdade, você precisa escrever um micro analisador para analisar a string que deseja dividir. Eu irei, por causa desta resposta, chamar as partes citadas de suas strings como sub-strings. Você precisa caminhar especificamente pela corda. Considere o seguinte caso:

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

Nesse caso, você não tem absolutamente nenhuma ideia de onde uma sequência secundária começa ou termina simplesmente analisando a entrada para um padrão de caractere. Em vez disso, você deve escrever uma lógica para tomar decisões sobre se um caractere de aspas é usado como um caractere de aspas, se ele próprio não está cotado e se o caractere de aspas não segue um escape.

Não vou escrever esse nível de complexidade de código para você, mas você pode ver algo que escrevi recentemente que tem o padrão de que você precisa. Este código não tem nada a ver com vírgulas, mas é um micro-analisador válido o suficiente para você seguir escrevendo seu próprio código. Observe a função asifixo do seguinte aplicativo:

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

Austincheney
fonte
2

Para complementar esta resposta

Se você precisar analisar aspas escapadas com outra citação, exemplo:

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

Você pode usar

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
fonte
Descobri que a análise ainda falha"jjj "" kkk""","123"
niry
2

sem regexp, legível, de acordo com 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;
}
Bachor
fonte
2

Ao ler o arquivo CSV em uma string, ele contém valores nulos entre as strings, então tente com \ 0 linha por linha. Funciona para mim.

stringLine = stringLine.replace(/\0/g, "" );
Sharathi RB
fonte
2

Eu também enfrentei o mesmo tipo de problema ao analisar um arquivo CSV.

O arquivo contém um endereço de coluna que contém o ','.

Depois de analisar esse arquivo CSV para JSON, recebo o mapeamento incompatível das chaves ao convertê-lo em um arquivo JSON.

Usei Node.js para analisar o arquivo e bibliotecas como baby parse e csvtojson .

Exemplo de arquivo -

address,pincode
foo,baar , 123456

Enquanto eu estava analisando diretamente sem usar a análise de bebê em JSON, eu estava conseguindo:

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

Então, escrevi um código que remove a vírgula (,) com qualquer outro delimitador em cada campo:

/*
 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;
    },'')
}

A função retornada pode ser passada para a biblioteca csvtojson e, portanto, o resultado pode ser usado.

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 */
  })

Agora você pode obter a saída como:

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

De acordo com esta postagem do blog , esta função deve fazer isso:

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

Você o chamaria assim:

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

Esse tipo de quebra-cabeça funciona, mas parece que alguns dos elementos têm espaços antes deles.

CanSpice
fonte
Imagine ter que fazer tudo isso em um regex. É por isso que regexes não são realmente adequados para análise às vezes.
CanSpice
Esta solução simplesmente não funciona. Dada a string de teste original "'string, duppi, du', 23, lala"["'string"," duppi"," du'"," 23"," lala"]
:,
@ridgerunner: Certo. Editei a resposta e o jsfiddle para corrigir a função. Basicamente, mudei "'"para '"'e vice-versa.
CanSpice
Isso ajudou, mas agora a função lida incorretamente com strings CSV entre aspas simples com valores entre aspas duplas. Por exemplo, invertendo os tipos de citação da string de teste original, da seguinte forma: '"string, duppi, du", 23, lala'resulta em:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner
@CanSpice, seu comentário me inspirou a tentar o RegEx. Não tem tantos recursos, mas eles podem ser facilmente adicionados. (Minha resposta está nesta página, se você estiver interessado.)
Brigand
0

Expressões regulares para o resgate! Essas poucas linhas de código lidam com campos devidamente citados com vírgulas, aspas e novas linhas incorporadas com base no padrão 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' ] ]

A menos que declarado em outro lugar, você não precisa de uma máquina de estado finito. A expressão regular trata a RFC 4180 de maneira adequada graças a lookbehind positivo, lookbehind negativo e lookahead positivo.

Clone / faça download do código em https://github.com/peterthoeny/parse-csv-js

Peter Thoeny
fonte
0

Além da excelente e completa resposta do ridgerunner , pensei em uma solução alternativa muito simples para quando seu back-end executa PHP.

Adicionar este arquivo PHP para backend do seu domínio (digamos: 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")));
?>

Agora adicione esta função ao seu kit de ferramentas JavaScript (deve ser revisado um pouco para fazer crossbrowser, eu acredito).

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

Custará uma chamada Ajax, mas pelo menos você não duplicará o código nem incluirá qualquer biblioteca externa.

Ref: http://php.net/manual/en/function.str-getcsv.php

Sebas
fonte
0

Você pode usar papaparse.js como o exemplo abaixo:

<!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>

Não se esqueça de incluir papaparse.js na mesma pasta.

Tahseen Alaa
fonte