Come posso analizzare una stringa CSV con Javascript, che contiene una virgola nei dati?

Ho il seguente tipo di stringa

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

Voglio dividere la stringa in una matrice su ogni virgola, ma solo le virgole al di fuori delle virgolette singole.

Non riesco a capire la regex giusta per lo split

 string.split(/,/) 

mi darà

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

ma il risultato dovrebbe essere:

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

c’è una soluzione cross browser?

disconoscimento

Aggiornamento 2014-12-01: la risposta sotto funziona solo per un formato molto specifico di CSV. Come correttamente indicato dalla DG nei commenti, questa soluzione NON si adatta alla definizione RFC 4180 di CSV e inoltre NON si adatta al formato MS Excel. Questa soluzione dimostra semplicemente come è ansible analizzare una riga di input CSV (non standard) che contiene un mix di tipi di stringhe, in cui le stringhe possono contenere virgolette e virgolette di escape.

Una soluzione CSV non standard

Come fa giustamente notare 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 una “stringa CSV”. Per prima cosa dobbiamo definire cosa costituisce una stringa CSV valida e i suoi singoli valori.

Dato: definizione “stringa CSV”

Ai fini di questa discussione, una “stringa CSV” consiste di zero o più valori, in cui più valori sono separati da una virgola. Ogni valore può essere costituito da:

  1. Una stringa doppia citata. (può contenere virgolette singole senza escape).
  2. Una singola stringa quotata. (può contenere virgolette doppie senza caratteri di escape).
  3. Una stringa non quotata. (potrebbe NON contenere virgolette, virgole o barre rovesciate).
  4. Un valore vuoto (Un valore di tutti gli spazi bianchi è considerato vuoto).

Regole / Note:

  • I valori citati possono contenere virgole.
  • I valori quotati possono contenere caratteri di escape, ad esempio 'that\'s cool' .
  • I valori contenenti virgolette, virgole o barre retroverse devono essere citati.
  • I valori contenenti spazi iniziali o finali devono essere indicati.
  • La barra retroversa viene rimossa da tutti: \' in singoli valori quotati.
  • La barra rovesciata viene rimossa da tutti: \" in valori con doppie virgolette.
  • Le stringhe non quotate sono tagliate di qualsiasi spazio iniziale e finale.
  • Il separatore di virgola può avere uno spazio bianco adiacente (che viene ignorato).

Trova:

Una funzione JavaScript che converte una stringa CSV valida (come definita sopra) in una matrice 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à di 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 syntax regex nativa (espressa utilizzando la syntax raw-multi-line-string di Python: r'''...''' raw-multi-line-string).

Prima ecco un’espressione regolare che convalida che una stringa CVS soddisfa 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 alla regex precedente, quella stringa è una stringa CSV valida (in base alle regole precedentemente dichiarate) e può essere analizzata usando la regex seguente. La seguente espressione regolare viene quindi utilizzata per associare un valore della stringa CSV. Viene applicato ripetutamente fino a quando non vengono trovate altre corrispondenze (e tutti i valori sono stati analizzati).

Regex per analizzare un valore dalla 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. """ 

Si noti che esiste un valore di caso speciale che questa espressione regolare non corrisponde – l’ultimo valore quando quel valore è vuoto. Questo speciale caso “vuoto ultimo valore” è 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 formsd. 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 formsd 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 {result strings} . (Ciò serve a visualizzare gli spazi iniziali e finali e le stringhe a 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 valore singolo o doppio quotato. Si noti inoltre che questa soluzione rappresenta solo una ansible definizione per: “Valori separati da virgola”.

Modifica: 2014-05-19: Aggiunto disclaimer. Modifica: 2014-12-01: spostato il disclaimer in alto.

Soluzione RFC 4180

Questo non risolve la stringa nella domanda poiché il suo formato non è conforms a RFC 4180; la codifica accettabile sta sfuggendo alla doppia citazione con una doppia citazione. La soluzione qui sotto funziona correttamente con i file CSV d / l dai fogli di lavoro di google.

AGGIORNAMENTO (3/2017)

L’analisi della singola riga sarebbe errata. Secondo RFC 4180, i campi possono contenere CRLF che causerà la rottura del file CSV da qualsiasi lettore di righe. Ecco una versione aggiornata che analizza la stringa 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)); 

Grammatica PEG (.js) che gestisce gli esempi di RFC 4180 su 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 '"'; } / [^"] 

Prova su http://jsfiddle.net/knvzk/10 o https://pegjs.org/online .

Scarica il parser generato su https://gist.github.com/3362830 .

Se è ansible avere il delimitatore di virgolette doppie virgolette, questo è un duplicato del codice JavaScript per analizzare i dati CSV .

Puoi tradurre tutte le virgolette singole in virgolette per prime:

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

… oppure puoi modificare la regex in quella domanda per riconoscere le virgolette singole invece delle virgolette:

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

Tuttavia, questo presuppone un certo markup che non è chiaro dalla tua domanda. Per favore chiarisci quali possono essere tutte le varie possibilità di markup, secondo il mio commento sulla tua domanda.

Ho avuto un caso d’uso molto specifico in cui volevo copiare le celle di Google Sheets nella mia app web. Le celle potrebbero includere virgolette doppie e caratteri di nuova riga. Utilizzando copia e incolla, le celle sono delimitate da caratteri di tabulazione e le celle con dati dispari sono doppie. Ho provato questa soluzione principale, l’articolo collegato usando regexp e Jquery-CSV e CSVToArray. http://papaparse.com/ È l’unico che ha funzionato fuori dalla scatola. Copia e incolla è perfettamente compatibile con Fogli Google con opzioni di rilevamento automatico predefinite.

Mi è piaciuta la risposta di FakeRainBrigand, tuttavia contiene alcuni problemi: non è in grado di gestire spazi bianchi tra virgolette e virgole e non supporta 2 virgole consecutive. Ho provato a modificare la sua risposta, ma la mia modifica è stata respinta dai revisori che a quanto pare non hanno capito il mio codice. Ecco la mia versione del codice di FakeRainBrigand. C’è anche un 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('|')); 

La mia risposta presuppone che il tuo input rifletta il codice / il contenuto proveniente da fonti Web in cui i caratteri a virgolette singole e doppie sono completamente intercambiabili, purché si presentino come un insieme di corrispondenze senza escape.

Non puoi usare regex per questo. In realtà devi scrivere un micro parser per analizzare la stringa che desideri dividere. Per il piacere di questa risposta, chiamerò le parti citate delle tue stringhe come sotto-stringhe. Devi attraversare la corda in modo specifico. Considera il seguente 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."; 

In questo caso non hai assolutamente idea di dove inizia o termina una sottostringa semplicemente analizzando l’input per un pattern di caratteri. Invece devi scrivere la logica per prendere decisioni sul fatto che un carattere di citazione sia usato un carattere di citazione, sia esso stesso non quotato, e che il carattere di citazione non stia seguendo una fuga.

Non scriverò per te quel livello di complessità del codice, ma puoi guardare qualcosa che ho scritto di recente che ha lo schema che ti serve. Questo codice non ha nulla a che fare con le virgole, ma è altrimenti un micro-parser abbastanza valido da seguire nella scrittura del proprio codice. Esaminare la funzione Asifix della seguente applicazione:

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

Le persone sembravano essere contro RegEx per questo. Perché?

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

Ecco il codice. Ho fatto anche un 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('|')); 

Durante la lettura di csv per stringa contiene un valore nullo tra le stringhe, quindi provalo \ 0 Line by line mi funziona.

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

Per completare questa risposta

Se è necessario analizzare le virgolette sfuggite con un’altra citazione, esempio:

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

Puoi usare

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

Ho anche affrontato lo stesso tipo di problema quando devo analizzare un file CSV. Il file contiene una colonna Indirizzo che contiene il ‘,’.
Dopo aver analizzato il file CSV in JSON, ottengo il mapping non corrispondente dei tasti durante la conversione in file JSON.
Ho usato il nodo per analizzare il file e Libreria come baby parse e csvtojson
Esempio di file –

 address,pincode foo,baar , 123456 

Mentre stavo analizzando direttamente senza usare baby parse in JSON, stavo diventando

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

Così ho scritto un codice che rimuove la virgola (,) con ogni altro deliminatore con ogni 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; },'') } 

Secondo questo post del blog , questa funzione dovrebbe farlo:

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

Lo chiameresti così:

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

Questo tipo di jsfiddle funziona, ma sembra che alcuni degli elementi abbiano spazi davanti a loro.

A parte l’eccellente e completa risposta di ridgerunner, ho pensato a una soluzione molto semplice per quando il tuo back-end funziona su PHP.

Aggiungi questo file php al backend del tuo dominio (ad esempio: csv.php )

  

Ora aggiungi questa funzione al tuo javascript toolkit (dovrebbe essere rivisto un po ‘per far credere a crossbrowser).

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

Vi costerà 1 chiamata ajax, ma almeno non duplicherete il codice né includerete alcuna libreria esterna.

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