Disclaimer
Aggiornamento 2014-12-01: la risposta di seguito funziona solo per un formato molto specifico di CSV. Come correttamente sottolineato da DG nei commenti, questa soluzione NON si adatta alla definizione RFC 4180 di CSV e NON si adatta al formato MS Excel. Questa soluzione dimostra semplicemente come si può analizzare una riga di input CSV (non standard) che contiene un mix di tipi di stringa, in cui le stringhe possono contenere virgolette e virgolette con escape.
Una soluzione CSV non standard
Come sottolinea correttamente austincheney, è davvero necessario analizzare la stringa dall'inizio alla fine se si desidera gestire correttamente le stringhe tra virgolette che possono contenere caratteri di escape. Inoltre, l'OP non definisce chiaramente cosa sia realmente una "stringa CSV". Per prima cosa dobbiamo definire cosa costituisce una stringa CSV valida e i suoi valori individuali.
Dato: "Stringa CSV" Definizione
Ai fini di questa discussione, una "stringa CSV" è costituita da zero o più valori, dove più valori sono separati da una virgola. Ogni valore può essere costituito da:
- Una stringa con virgolette doppie. (può contenere virgolette singole senza caratteri di escape.)
- Una singola stringa tra virgolette. (può contenere virgolette doppie senza caratteri di escape.)
- Una stringa non quotata. (NON può contenere virgolette, virgole o barre rovesciate.)
- Un valore vuoto. (Un valore di tutti gli spazi è considerato vuoto.)
Regole / Note:
- I valori tra virgolette possono contenere virgole.
- I valori tra virgolette possono contenere caratteri di escape, ad es
'that\'s cool' .
- I valori contenenti virgolette, virgole o barre rovesciate devono essere citati.
- I valori contenenti spazi iniziali o finali devono essere citati.
- La barra rovesciata viene rimossa da tutto:
\'nei singoli valori tra virgolette.
- La barra rovesciata viene rimossa da tutto:
\"tra virgolette doppie.
- Le stringhe senza virgolette vengono tagliate dagli spazi iniziali e finali.
- Il separatore virgola può avere spazi bianchi adiacenti (che vengono ignorati).
Trova:
Una funzione JavaScript che converte una stringa CSV valida (come definita sopra) in un array di valori stringa.
Soluzione:
Le espressioni regolari utilizzate da questa soluzione sono complesse. E (IMHO) tutte le regex non banali dovrebbero essere presentate in modalità a spaziatura libera con molti commenti e rientri. Sfortunatamente, JavaScript non consente la modalità di spaziatura libera. Pertanto, le espressioni regolari implementate da questa soluzione vengono prima presentate nella sintassi regex nativa (espressa utilizzando il pratico Python:r'''...''' raw-multi-line-string).
Innanzitutto ecco un'espressione regolare che convalida che una stringa CVS soddisfi i requisiti di cui sopra:
Regex per convalidare una "stringa 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 una stringa corrisponde all'espressione regolare precedente, allora quella stringa è una stringa CSV valida (secondo le regole precedentemente dichiarate) e può essere analizzata utilizzando la seguente regex. La seguente regex viene quindi utilizzata per abbinare un valore dalla stringa CSV. Viene applicato ripetutamente fino a quando non vengono trovate più corrispondenze (e tutti i valori sono stati analizzati).
Regex per analizzare un valore da una stringa CSV valida:
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.
"""
Nota che c'è un valore di caso speciale che questa regex non corrisponde: l'ultimo valore quando quel valore è vuoto. Questo speciale caso "vuoto ultimo valore" viene testato e gestito dalla funzione js che segue.
Funzione JavaScript per analizzare la stringa 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;
};
Esempio di input e output:
Negli esempi seguenti, le parentesi graffe vengono utilizzate per delimitare il file {result strings}. (Questo serve per visualizzare gli spazi iniziali / finali e le stringhe di lunghezza 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] = {} */
Note aggiuntive:
Questa soluzione richiede che la stringa CSV sia "valida". Ad esempio, i valori non quotati potrebbero non contenere barre rovesciate o virgolette, ad esempio la seguente stringa CSV NON è valida:
var invalid1 = "one, that's me!, escaped \, comma"
Questa non è realmente una limitazione perché qualsiasi sottostringa può essere rappresentata come un valore tra virgolette singole o doppie. Si noti inoltre che questa soluzione rappresenta solo una possibile definizione di: "Valori separati da virgola".
Modifica: 2014-05-19: aggiunto disclaimer.
Modifica: 2014-12-01: disclaimer spostato all'inizio.