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


93

Ho il seguente tipo di stringa

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

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

Non riesco a capire la giusta espressione regolare per la divisione ...

string.split(/,/)

mi darà

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

ma il risultato dovrebbe essere:

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

Esiste una soluzione cross-browser?


Sono sempre virgolette singole? C'è mai una virgoletta singola all'interno di una stringa quotata? In tal caso, come viene eseguito l'escape (barra rovesciata, raddoppiata)?
Phrogz

Cosa succede se i caratteri delle virgolette sono completamente intercambiabili tra virgolette singole e doppie come in JavaScript e nel codice HTML / XML? In tal caso, ciò richiede un'operazione di analisi più estesa di CSV.
Austincheney

effettivamente sì, potrebbe esserci una singola virgoletta all'interno, uscire con il backslash andrebbe bene.
Hans

Un valore può essere una stringa tra virgolette doppie?
ridgerunner

1
Papa Parse fa un ottimo lavoro. Analisi di un file CSV locale con JavaScript e Papa Parse: joyofdata.de/blog/…
Raffael

Risposte:


214

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:

  1. Una stringa con virgolette doppie. (può contenere virgolette singole senza caratteri di escape.)
  2. Una singola stringa tra virgolette. (può contenere virgolette doppie senza caratteri di escape.)
  3. Una stringa non quotata. (NON può contenere virgolette, virgole o barre rovesciate.)
  4. 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.


1
@Evan Plaice - Grazie per le belle parole. Sicuramente puoi usare qualsiasi separatore. Basta sostituire ogni virgola nella mia regex con il separatore di scelta (ma il separatore non può essere uno spazio bianco). Saluti.
ridgerunner

2
@Evan Plaice - Sei libero di usare una qualsiasi delle mie regex per qualsiasi scopo desideri. Una nota di riconoscimento sarebbe carina ma non necessaria. Buona fortuna con il tuo plug-in. Saluti!
ridgerunner

1
Fantastico, ecco il progetto code.google.com/p/jquery-csv . Alla fine, voglio aggiungere un formato di estensione a CSV chiamato SSV (Structured Separated Values) che è semplicemente CSV con metadati (cioè, delimitatore, separatore, fine riga, ecc.) Inclusi.
Evan Plaice

1
Grazie mille per questa fantastica implementazione: l'ho usata come base per un modulo Node.js ( csv-iterator ).
mirkokiefer

3
Applaudo il dettaglio e chiarisco la tua risposta, ma va notato da qualche parte che la tua definizione di CSV non si adatta alla RFC 4180, che è la cosa più vicina a uno standard per CSV e che posso dire aneddoticamente è comunemente usata. In particolare questo sarebbe il modo normale per "sfuggire" a un carattere di virgolette doppie all'interno di un campo stringa: "field one", "field two", "a ""final"" field containing two double quote marks"non ho testato la risposta di Trevor Dixon su questa pagina, ma è una risposta che affronta la definizione RFC 4180 di CSV.
DG.

53

Soluzione RFC 4180

Ciò non risolve la stringa nella domanda poiché il suo formato non è conforme a RFC 4180; la codifica accettabile è l'escape di virgolette doppie con virgolette doppie. La soluzione seguente funziona correttamente con i file CSV d / l dai fogli di calcolo di Google.

AGGIORNAMENTO (3/2017)

L'analisi di una singola riga sarebbe sbagliata. Secondo RFC 4180 i campi possono contenere CRLF che causerà la rottura del file CSV da parte di qualsiasi lettore di riga. 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));

VECCHIA RISPOSTA

(Soluzione a linea singola)

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 per divertimento, ecco come crei CSV dall'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);


1
questo ha fatto il lavoro per me, non l'altro
WtFudgE

7

Grammatica PEG (.js) che gestisce esempi 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 .


6

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


1
Questo dovrebbe essere classificato molto più in alto, non provare mai a lanciare il tuo parser CSV, non funzionerà correttamente , specialmente quando usi le espressioni regolari. Papaparse è fantastico : usalo !
cbley

6

Mi è piaciuta la risposta di FakeRainBrigand, tuttavia contiene alcuni problemi: non è in grado di gestire gli spazi tra virgolette e virgole e non supporta 2 virgole consecutive. Ho provato a modificare la sua risposta ma la mia modifica è stata rifiutata dai revisori che apparentemente 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('|'));

4

La gente sembrava essere contraria alle RegEx per questo. Perché?

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

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

3
Hmm, la tua regexp ha alcuni problemi: non può gestire gli spazi tra virgolette e virgole e non supporta 2 virgole consecutive. Ho aggiornato la tua risposta con un codice che risolve entrambi i problemi e ho creato un nuovo violino: jsfiddle.net/xTezm/43
HammerNL

Per qualche motivo la mia modifica al tuo codice è stata rifiutata perché "deviava dall'intenzione originale del post". Molto strano!? Ho appena preso il tuo codice e ho risolto due problemi con esso. Come cambia lo scopo del post !? Comunque ... ho semplicemente aggiunto una nuova risposta a questa domanda.
HammerNL

Bella domanda nella tua risposta, @FakeRainBrigand. Io per primo tutti per regex, e per questo motivo, riconosco che è lo strumento sbagliato per il lavoro.
niry

2
@niry il mio codice qui è orribile. Prometto di essere migliorato negli ultimi 6 anni :-p
Brigante

4

Aggiungendo un altro alla lista, perché trovo tutto quanto sopra non abbastanza "KISS".

Questo usa regex per trovare virgole o nuove righe mentre salta gli elementi citati. Si spera che questo sia qualcosa che i noobies possano leggere da soli. La splitFinderregexp ha tre cose che fa (divisa per a |):

  1. , - trova le virgole
  2. \r?\n - trova nuove righe, (potenzialmente con ritorno a capo se l'esportatore è stato gentile)
  3. "(\\"|[^"])*?"- salta qualsiasi cosa racchiusa tra virgolette, perché le virgole e le nuove righe non contano. Se nell'articolo \\"citato è presente una citazione con escape , verrà acquisita prima che sia possibile trovare una citazione finale.

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


Se leggo il mio file tramite fileReader e il mio risultato: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 come posso analizzare in base alle colonne specificate?
bluePearl

Dopo aver ottenuto l'array 2d, rimuovi il primo indice (sono i nomi delle tue prop), quindi itera sul resto dell'array, creando oggetti con ciascuno dei valori come proprietà. Sarà simile a questo:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed

3

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

Puoi prima tradurre tutte le virgolette singole in virgolette doppie:

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

... oppure puoi modificare la regex in quella domanda per riconoscere virgolette singole invece di virgolette doppie:

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


2

La mia risposta presume che il tuo input sia un riflesso del codice / contenuto da fonti web in cui i caratteri virgolette singole e doppie sono completamente intercambiabili a condizione che si presentino come un insieme di corrispondenza senza escape.

Non puoi usare regex per questo. In realtà devi scrivere un micro parser per analizzare la stringa che desideri dividere. Per amore di questa risposta chiamerò le parti citate delle tue stringhe come sottostringhe. Devi camminare specificamente sulla corda. 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 inizi o finisca 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 virgolette venga usato come un carattere di virgolette, sia esso stesso non quotato e che il carattere di virgolette non stia seguendo una fuga.

Non scriverò quel livello di complessità del codice per te, ma puoi guardare qualcosa che ho scritto di recente che ha lo schema di cui hai bisogno. Questo codice non ha nulla a che fare con le virgole, ma per il resto è un micro-parser abbastanza valido da poter seguire nella scrittura del proprio codice. Guarda nella funzione asifix della seguente applicazione:

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


2

Per completare questa risposta

Se devi analizzare le virgolette con escape con un'altra citazione, ad 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 trovato che questo non riesce ancora l'analisi"jjj "" kkk""","123"
Niry

2

nessuna regexp, leggibile, secondo 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;
}

2

Durante la lettura del file CSV in una stringa, contiene valori nulli tra le stringhe, quindi provalo con \ 0 riga per riga. Per me funziona.

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

2

Ho anche affrontato lo stesso tipo di problema quando ho dovuto analizzare un file CSV.

Il file contiene un indirizzo di colonna che contiene ",".

Dopo aver analizzato il file CSV in JSON, ottengo una mappatura non corrispondente delle chiavi durante la conversione in un file JSON.

Ho usato Node.js per analizzare il file e le librerie come baby parse e csvtojson .

Esempio di file -

address,pincode
foo,baar , 123456

Mentre stavo analizzando direttamente senza usare baby parse in JSON, stavo ottenendo:

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

Quindi ho scritto del codice che rimuove la virgola (,) con qualsiasi altro delimitatore 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;
    },'')
}

La funzione restituita può essere passata alla libreria csvtojson e quindi il risultato può essere utilizzato.

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

Ora puoi ottenere l'output come:

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

1

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 elementi abbiano spazi prima di loro.


Immagina di dover fare tutto questo in una regex. Questo è il motivo per cui a volte le regex non sono adatte per l'analisi.
CanSpice

Questa soluzione semplicemente non funziona. Data la stringa di prova originale "'string, duppi, du', 23, lala"["'string"," duppi"," du'"," 23"," lala"]
:,

@ridgerunner: Hai ragione. Ho modificato la risposta e il jsfiddle per correggere la funzione. Fondamentalmente, sono passato "'"a '"'e viceversa.
CanSpice

Ciò ha aiutato, ma ora la funzione gestisce in modo errato stringhe CSV con virgolette singole con valori tra virgolette doppie. ad esempio, invertendo i tipi di virgolette della stringa di prova originale in questo modo: si '"string, duppi, du", 23, lala'ottiene:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner

@CanSpice, il tuo commento mi ha ispirato a provare con RegEx. Non ha molte funzionalità, ma potrebbero essere facilmente aggiunte. (La mia risposta è su questa pagina, se sei interessato.)
Brigante

0

Espressioni regolari in soccorso! Queste poche righe di codice gestiscono i campi correttamente citati con virgole, virgolette e nuove righe incorporate in base allo standard 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 meno che non sia specificato altrove, non è necessaria una macchina a stati finiti. L'espressione regolare gestisce correttamente l'RFC 4180 grazie a lookbehind positivo, lookbehind negativo e lookahead positivo.

Clona / scarica il codice su https://github.com/peterthoeny/parse-csv-js


0

A parte l'eccellente e completa risposta di ridgerunner , ho pensato a una soluzione molto semplice per quando il tuo backend esegue PHP.

Aggiungere questo file PHP per backend del dominio (ad esempio: 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")));
?>

Ora aggiungi questa funzione al tuo toolkit JavaScript (dovrebbe essere rivisto un po 'per renderlo crossbrowser, credo).

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

Ti costerà una chiamata Ajax, ma almeno non duplicherai il codice né includerai alcuna libreria esterna.

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


0

Puoi usare papaparse.js come nell'esempio seguente:

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

Non dimenticare di includere papaparse.js nella stessa cartella.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.