¿Cómo puedo analizar una cadena CSV con JavaScript, que contiene comas en los datos?

93

Tengo el siguiente tipo de cuerda

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

Quiero dividir la cadena en una matriz en cada coma, pero solo las comas fuera de las comillas simples.

No puedo encontrar la expresión regular correcta para la división ...

string.split(/,/)

me darás

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

pero el resultado debería ser:

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

¿Existe una solución para varios navegadores?

Hans
fuente
¿Son siempre comillas simples? ¿Alguna vez hay una comilla simple dentro de una cadena entre comillas? Si es así, ¿cómo se escapa (barra invertida, duplicada)?
Phrogz
¿Qué pasa si los caracteres de comillas son completamente intercambiables entre caracteres de comillas simples y dobles como en JavaScript y código HTML / XML? Si es así, esto requiere una operación de análisis más extensa que CSV.
Austincheney
de hecho, sí, podría haber una sola cita dentro, escapar con barra invertida estaría bien.
Hans
¿Puede un valor ser una cadena entre comillas dobles?
ridgerunner
1
Papa Parse hace un buen trabajo. Análisis de un archivo CSV local con JavaScript y Papa Parse: joyofdata.de/blog/…
Raffael

Respuestas:

215

Descargo de responsabilidad

Actualización de 2014-12-01: la respuesta a continuación solo funciona para un formato muy específico de CSV. Como señaló correctamente DG en los comentarios , esta solución no se ajusta a la definición de CSV de RFC 4180 y tampoco se ajusta al formato de Microsoft Excel. Esta solución simplemente demuestra cómo se puede analizar una línea de entrada CSV (no estándar) que contiene una combinación de tipos de cadenas, donde las cadenas pueden contener comillas y comillas de escape.

Una solución CSV no estándar

Como señala correctamente austincheney , realmente necesita analizar la cadena de principio a fin si desea manejar adecuadamente las cadenas entre comillas que pueden contener caracteres de escape. Además, el OP no define claramente qué es realmente una "cadena CSV". Primero debemos definir qué constituye una cadena CSV válida y sus valores individuales.

Dado: Definición de "Cadena CSV"

A los efectos de esta discusión, una "cadena CSV" consta de cero o más valores, donde varios valores están separados por una coma. Cada valor puede constar de:

  1. Una cadena entre comillas dobles (puede contener comillas simples sin escape).
  2. Una sola cadena entre comillas (puede contener comillas dobles sin escape).
  3. Una cadena sin comillas ( no puede contener comillas, comas o barras invertidas).
  4. Un valor vacío. (Un valor de todos los espacios en blanco se considera vacío).

Reglas / Notas:

  • Los valores entre comillas pueden contener comas.
  • Los valores entre comillas pueden contener cualquier cosa de escape, por ejemplo 'that\'s cool'.
  • Los valores que contienen comillas, comas o barras invertidas deben estar entrecomillados.
  • Los valores que contienen espacios en blanco al principio o al final deben estar entre comillas.
  • La barra invertida se elimina de todos: \'en valores entre comillas simples.
  • La barra invertida se elimina de todos: \"en valores entre comillas dobles.
  • Las cadenas no entrecomilladas se recortan de los espacios iniciales y finales.
  • El separador de coma puede tener espacios en blanco adyacentes (que se ignoran).

Encontrar:

Una función de JavaScript que convierte una cadena CSV válida (como se define arriba) en una matriz de valores de cadena.

Solución:

Las expresiones regulares que utiliza esta solución son complejas. Y (en mi humilde opinión) todas las expresiones regulares no triviales deben presentarse en modo de espacio libre con muchos comentarios y sangría. Desafortunadamente, JavaScript no permite el modo de espacio libre. Por lo tanto, las expresiones regulares implementadas por esta solución se presentan primero en la sintaxis de expresiones regulares nativas (expresadas utilizando la práctica r'''...'''sintaxis de cadena de múltiples líneas sin procesar de Python ).

Primero, aquí hay una expresión regular que valida que una cadena CVS cumple con los requisitos anteriores:

Expresión regular para validar una "cadena 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.
"""

Si una cadena coincide con la expresión regular anterior, entonces esa cadena es una cadena CSV válida (de acuerdo con las reglas indicadas anteriormente) y se puede analizar utilizando la siguiente expresión regular. La siguiente expresión regular se usa luego para hacer coincidir un valor de la cadena CSV. Se aplica repetidamente hasta que no se encuentran más coincidencias (y se han analizado todos los valores).

Expresión regular para analizar un valor de una cadena 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.
"""

Tenga en cuenta que hay un valor de caso especial con el que esta expresión regular no coincide: el último valor cuando ese valor está vacío. Este caso especial de "último valor vacío" es probado y manejado por la función de JavaScript que sigue.

Función de JavaScript para analizar la cadena 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;
};

Ejemplo de entrada y salida:

En los siguientes ejemplos, se utilizan llaves para delimitar el {result strings}. (Esto es para ayudar a visualizar espacios iniciales / finales y cadenas de longitud cero).

// Test 1: Test string from original question.
var test = "'string, duppi, du', 23, lala";
var a = CSVtoArray(test);
/* Array has three elements:
    a[0] = {string, duppi, du}
    a[1] = {23}
    a[2] = {lala} */
// Test 2: Empty CSV string.
var test = "";
var a = CSVtoArray(test);
/* Array has zero elements: */
// Test 3: CSV string with two empty values.
var test = ",";
var a = CSVtoArray(test);
/* Array has two 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 has three 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 has three 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 has eight elements:
    a[0] = {one}
    a[1] = {two}
    a[2] = {}
    a[3] = { four}
    a[4] = {}
    a[5] = {six }
    a[6] = { seven }
    a[7] = {} */

Notas adicionales:

Esta solución requiere que la cadena CSV sea "válida". Por ejemplo, los valores sin comillas pueden no contener barras invertidas ni comillas, por ejemplo, la siguiente cadena CSV no es válida:

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

Esto no es realmente una limitación porque cualquier subcadena puede representarse como un valor entre comillas simple o doble. Tenga en cuenta también que esta solución representa sólo una posible definición de "valores separados por comas".

Editar historial

  • 2014-05-19: Descargo de responsabilidad agregado.
  • 2014-12-01: Se movió el descargo de responsabilidad al principio.
ridgerunner
fuente
1
@Evan Plaice - Gracias por las bonitas palabras. Seguro que puedes usar cualquier separador. Simplemente reemplace cada coma en mi expresión regular con el separador de su elección (pero el separador no puede ser un espacio en blanco). Salud.
ridgerunner
2
@Evan Plaice: puedes usar cualquiera de mis expresiones regulares para cualquier propósito que desees. Una nota de reconocimiento sería agradable pero no necesaria. Buena suerte con tu complemento. ¡Salud!
ridgerunner
1
Genial, aquí está el proyecto code.google.com/p/jquery-csv . Finalmente, quiero agregar un formato de extensión a CSV llamado SSV (valores separados estructurados) que es simplemente CSV con metadatos (es decir, delimitador, separador, final de línea, etc.) incluidos.
Evan Plaice
1
Muchas gracias por esta gran implementación; la usé como base para un módulo Node.js ( csv-iterator ).
mirkokiefer
3
Aplaudo el detalle y la aclaración de su respuesta, pero debe tenerse en cuenta en alguna parte que su definición de CSV no se ajusta a RFC 4180, que es lo más cercano a un estándar para CSV, y que puedo decir de manera anecdótica que se usa comúnmente. En particular, esta sería la forma normal de "escapar" de un carácter de comillas dobles dentro de un campo de cadena: "field one", "field two", "a ""final"" field containing two double quote marks"no he probado la respuesta de Trevor Dixon en esta página, pero es una respuesta que aborda la definición RFC 4180 de CSV.
DG.
53

Solución RFC 4180

Esto no resuelve la cadena en la pregunta ya que su formato no se ajusta a RFC 4180; la codificación aceptable es escapar de comillas dobles con comillas dobles. La siguiente solución funciona correctamente con archivos CSV d / l de hojas de cálculo de Google.

ACTUALIZACIÓN (3/2017)

Analizar una sola línea sería incorrecto. Según RFC 4180, los campos pueden contener CRLF, lo que hará que cualquier lector de línea rompa el archivo CSV. Aquí hay una versión actualizada que analiza la cadena 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));

ANTIGUA RESPUESTA

(Solución de una sola línea)

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

Y para divertirse, así es como crea CSV a partir de la matriz:

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
fuente
1
este hizo el trabajo por mí, no el otro
WtFudgE
7

Gramática PEG (.js) que maneja ejemplos de RFC 4180 en 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 '"'; }
  / [^"]

Pruebe en http://jsfiddle.net/knvzk/10 o https://pegjs.org/online .

Descargue el analizador generado en https://gist.github.com/3362830 .

Trevor Dixon
fuente
6

Tuve un caso de uso muy específico en el que quería copiar celdas de Google Sheets en mi aplicación web. Las celdas pueden incluir comillas dobles y caracteres de nueva línea. Al usar copiar y pegar, las celdas se delimitan con caracteres de tabulación y las celdas con datos impares se citan dos veces. Probé esta solución principal, el artículo vinculado usando regexp y Jquery-CSV y CSVToArray. http://papaparse.com/ Es el único que funcionó fuera de la caja. Copiar y pegar es perfecto con Google Sheets con opciones predeterminadas de detección automática.

bjcullinan
fuente
1
Esto debería tener una clasificación mucho más alta, nunca intente ejecutar su propio analizador CSV, no funcionará correctamente , especialmente cuando se usan expresiones regulares. Papaparse es increíble , ¡ úsala !
cbley
6

Me gustó la respuesta de FakeRainBrigand, sin embargo, contiene algunos problemas: no puede manejar espacios en blanco entre una cita y una coma, y ​​no admite 2 comas consecutivas. Intenté editar su respuesta, pero mi edición fue rechazada por revisores que aparentemente no entendieron mi código. Aquí está mi versión del código de FakeRainBrigand. También hay un violín: 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
fuente
4

La gente parecía estar en contra de RegEx por esto. ¿Por qué?

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

Aquí está el código. También hice un violín .

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('|'));
Bandido
fuente
3
Hmm, su expresión regular tiene algunos problemas: no puede manejar espacios en blanco entre una cita y una coma, y ​​no admite 2 comas consecutivas. Actualicé
HammerNL
Por alguna razón, mi edición de su código fue rechazada porque "se desviaría de la intención original de la publicación". ¿¡Muy extraño!? Solo tomé su código y solucioné dos problemas con él. ¿¡Cómo cambia eso la intención de la publicación !? De todos modos ... simplemente agregué una nueva respuesta a esta pregunta.
HammerNL
Buena pregunta en tu respuesta, @FakeRainBrigand. Yo, por mi parte, todos para regex, y por eso, reconozco que es la herramienta incorrecta para el trabajo.
niry
2
@niry, mi código aquí es horrible. Prometo que he mejorado en los últimos 6 años :-p
Brigand
4

Añadiendo uno más a la lista, porque encuentro que todo lo anterior no es lo suficientemente "KISS".

Este usa expresiones regulares para encontrar comas o líneas nuevas mientras se salta los elementos citados. Con suerte, esto es algo que los novatos pueden leer por sí mismos. La splitFinderexpresión regular tiene tres cosas que hace (dividida por a |):

  1. , - encuentra comas
  2. \r?\n - encuentra nuevas líneas (potencialmente con retorno de carro si el exportador fue amable)
  3. "(\\"|[^"])*?"- omite cualquier cosa entre comillas, porque las comas y las nuevas líneas no importan allí. Si hay una cotización escapada \\"en el artículo cotizado, se capturará antes de que se pueda encontrar una cotización final.

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
fuente
Si leo mi archivo a través de fileReader y mi resultado: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 ¿cómo puedo analizar en función de las columnas que especifico?
bluePearl
Después de obtener la matriz 2d, elimine el primer índice (esos son los nombres de sus accesorios), luego itere sobre el resto de la matriz, creando objetos con cada uno de los valores como una propiedad. Se verá así:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed
3

Si puede hacer que su delimitador de comillas sea comillas dobles, entonces este es un duplicado del código JavaScript de ejemplo para analizar datos CSV .

Primero puede traducir todas las comillas simples a comillas dobles:

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

... o puede editar la expresión regular en esa pregunta para reconocer comillas simples en lugar de comillas dobles:

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

Sin embargo, esto asume cierto marcado que no está claro en su pregunta. Aclare cuáles pueden ser todas las diversas posibilidades de marcado, según mi comentario sobre su pregunta.

Phrogz
fuente
2

Mi respuesta asume que su entrada es un reflejo de código / contenido de fuentes web donde los caracteres de comillas simples y dobles son completamente intercambiables siempre que ocurran como un conjunto de coincidencias sin escape.

No puede usar expresiones regulares para esto. De hecho, debe escribir un micro analizador para analizar la cadena que desea dividir. Por el bien de esta respuesta, llamaré a las partes citadas de sus cadenas como subcadenas. Necesitas caminar específicamente a través de la cuerda. Considere el siguiente 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.";

En este caso, no tiene ni idea de dónde comienza o termina una subcadena simplemente analizando la entrada para un patrón de caracteres. En su lugar, debe escribir lógica para tomar decisiones sobre si un carácter de comillas se utiliza como carácter de comillas, si no está entre comillas y si el carácter de comillas no sigue un escape.

No voy a escribir ese nivel de complejidad de código para usted, pero puede ver algo que escribí recientemente que tiene el patrón que necesita. Este código no tiene nada que ver con las comas, pero por lo demás es un micro-analizador lo suficientemente válido para que lo siga escribiendo su propio código. Mire la función asifix de la siguiente aplicación:

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

Austincheney
fuente
2

Para complementar esta respuesta

Si necesita analizar comillas escapadas con otra cita, ejemplo:

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

Puedes 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
fuente
Descubrí que esto todavía falla al analizar"jjj "" kkk""","123"
niry
2

Mientras lee el archivo CSV en una cadena, contiene valores nulos entre las cadenas, así que inténtelo con \ 0 línea por línea. Esto funciona para mi.

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

También me he enfrentado al mismo tipo de problema cuando tuve que analizar un archivo CSV.

El archivo contiene una dirección de columna que contiene el ','.

Después de analizar ese archivo CSV a JSON, obtengo una asignación no coincidente de las claves mientras lo convierto en un archivo JSON.

Solía Node.js para analizar el archivo y bibliotecas como análisis sintáctico bebé y csvtojson .

Ejemplo de archivo -

address,pincode
foo,baar , 123456

Mientras analizaba directamente sin usar baby parse en JSON, obtenía:

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

Entonces escribí un código que elimina la coma (,) con cualquier otro delimitador con 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;
    },'')
}

La función devuelta se puede pasar a la biblioteca csvtojson y, por lo tanto, se puede usar el resultado.

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

Ahora puede obtener el resultado como:

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

Sin expresiones regulares, legible y de acuerdo con 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
fuente
1

Según esta publicación de blog , esta función debería hacerlo:

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 llamarías así:

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

Este tipo de jsfiddle funciona, pero parece que algunos de los elementos tienen espacios antes.

CanSpice
fuente
Imagina tener que hacer todo eso en una expresión regular. Esta es la razón por la que las expresiones regulares no son realmente adecuadas para analizar a veces.
CanSpice
Esta solución simplemente no funciona. Dada la cadena de prueba original : "'string, duppi, du', 23, lala", esta función devuelve:["'string"," duppi"," du'"," 23"," lala"]
ridgerunner
@ridgerunner: Tienes razón. Edité la respuesta y el jsfiddle para corregir la función. Básicamente, me cambié "'"a '"'y viceversa.
CanSpice
Eso ayudó, pero ahora la función maneja incorrectamente cadenas CSV entre comillas simples que tienen valores entre comillas dobles. Por ejemplo, invertir los tipos de citas de la cadena de prueba original de esta manera: '"string, duppi, du", 23, lala'da como resultado:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner
@CanSpice, su comentario me inspiró a probar con RegEx. No tiene tantas funciones, pero podrían agregarse fácilmente. (Mi respuesta está en esta página, si está interesado.)
Brigand
0

¡Expresiones regulares al rescate! Estas pocas líneas de código manejan los campos entrecomillados correctamente con comas, comillas y nuevas líneas incrustadas según el estándar 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 se indique en otra parte, no necesita una máquina de estado finito. La expresión regular maneja RFC 4180 correctamente gracias a la búsqueda hacia atrás positiva, la búsqueda hacia atrás negativa y la búsqueda hacia adelante positiva.

Clonar / descargar código en https://github.com/peterthoeny/parse-csv-js

Peter Thoeny
fuente
0

Aparte de la excelente y completa respuesta de ridgerunner , pensé en una solución muy simple para cuando su backend ejecuta PHP.

Añadir este archivo PHP para backend de su dominio (por ejemplo: 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")));
?>

Ahora agregue esta función a su kit de herramientas de JavaScript (creo que debería revisarse un poco para hacer un navegador cruzado).

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

Le costará una llamada Ajax, pero al menos no duplicará el código ni incluirá ninguna biblioteca externa.

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

Sebas
fuente
0

Puede usar papaparse.js como el siguiente ejemplo:

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

No olvide incluir papaparse.js en la misma carpeta.

Tahseen Alaa
fuente