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:
- Uma string entre aspas duplas. (pode conter aspas simples sem escape).
- Uma única string entre aspas. (pode conter aspas duplas sem escape).
- Uma string não entre aspas. (NÃO pode conter aspas, vírgulas ou barras invertidas.)
- 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.