¿Cómo analizar datos CSV?

14 minutos de lectura

Avatar de usuario de Pierre-Gilles Levallois
Pierre-Gilles Levallois

¿Dónde puedo encontrar código JavaScript para analizar datos CSV?

  • Aquí hay una función de JavaScript que analiza los datos CSV, teniendo en cuenta las comas que se encuentran dentro de las comillas.

    – curran

    03/04/2014 a las 23:25

  • papa analisis es otra opción con muchas funciones (multiproceso, soporte de fila de encabezado, delimitador de detección automática y más)

    – Hinrich

    24 de agosto de 2015 a las 19:13

Avatar de usuario de Kirtan
kirtan

Puedes usar el CSVToArray() función mencionada en esta entrada de blog.

<script type="text/javascript">
    // ref: http://stackoverflow.com/a/1293163/2343
    // This will parse a delimited string into an array of
    // arrays. The default delimiter is the comma, but this
    // can be overriden in the second argument.
    function CSVToArray( strData, strDelimiter ){
        // Check to see if the delimiter is defined. If not,
        // then default to comma.
        strDelimiter = (strDelimiter || ",");

        // Create a regular expression to parse the CSV values.
        var objPattern = new RegExp(
            (
                // Delimiters.
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

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

                // Standard fields.
                "([^\"\\" + strDelimiter + "\\r\\n]*))"
            ),
            "gi"
            );


        // Create an array to hold our data. Give the array
        // a default empty first row.
        var arrData = [[]];

        // Create an array to hold our individual pattern
        // matching groups.
        var arrMatches = null;


        // Keep looping over the regular expression matches
        // until we can no longer find a match.
        while (arrMatches = objPattern.exec( strData )){

            // Get the delimiter that was found.
            var strMatchedDelimiter = arrMatches[ 1 ];

            // Check to see if the given delimiter has a length
            // (is not the start of string) and if it matches
            // field delimiter. If id does not, then we know
            // that this delimiter is a row delimiter.
            if (
                strMatchedDelimiter.length &&
                strMatchedDelimiter !== strDelimiter
                ){

                // Since we have reached a new row of data,
                // add an empty row to our data array.
                arrData.push( [] );

            }

            var strMatchedValue;

            // Now that we have our delimiter out of the way,
            // let's check to see which kind of value we
            // captured (quoted or unquoted).
            if (arrMatches[ 2 ]){

                // We found a quoted value. When we capture
                // this value, unescape any double quotes.
                strMatchedValue = arrMatches[ 2 ].replace(
                    new RegExp( "\"\"", "g" ),
                    "\""
                    );

            } else {

                // We found a non-quoted value.
                strMatchedValue = arrMatches[ 3 ];

            }


            // Now that we have our value string, let's add
            // it to the data array.
            arrData[ arrData.length - 1 ].push( strMatchedValue );
        }

        // Return the parsed data.
        return( arrData );
    }

</script>

  • Da undefined por campos vacíos eso es citado. Ejemplo: CSVToArray("4,,6") me da [["4","","6"]]pero CSVToArray("4,\"\",6") me da [["4",undefined,"6"]].

    – Pang

    14 de noviembre de 2012 a las 4:36

  • He tenido problemas con esto en Firefox y el script no responde. Sin embargo, parecía afectar solo a unos pocos usuarios, por lo que no pude encontrar la causa.

    – JDandChips

    18 de marzo de 2013 a las 11:51

  • Hay un error en la expresión regular: "([^\"\\" should be "([^\\". Otherwise a double quote anywhere in an unquoted value will prematurely end it. Found this the hard way…

    – Walter Tross

    Nov 30, 2015 at 21:52

  • For anyone looking for a reduced version of the above method, with the regex fix described above applied: gist.github.com/Jezternz/c8e9fafc2c114e079829974e3764db75

    – Josh Mc

    Sep 23, 2018 at 1:39


  • Borrowed from @JoshMc (thanks!) and added header capability and more robust character escaping. See gist.github.com/plbowers/7560ae793613ee839151624182133159

    – Peter Bowers

    Dec 29, 2018 at 12:18


Evan Plaice's user avatar
Evan Plaice

jQuery-CSV

It’s a jQuery plugin designed to work as an end-to-end solution for parsing CSV into JavaScript data. It handles every single edge case presented in RFC 4180, as well as some that pop up for Excel/Google spreadsheet exports (i.e., mostly involving null values) that the specification is missing.

Example:

track,artist,album,year

Dangerous,’Busta Rhymes’,’When Disaster Strikes’,1997

// Calling this
music = $.csv.toArrays(csv)

// Outputs...
[
  ["track", "artist", "album", "year"],
  ["Dangerous", "Busta Rhymes", "When Disaster Strikes", "1997"]
]consola.log(música[1][2]) // Salidas: 'Cuando ocurre un desastre'

Actualizar:

Ah, sí, probablemente también debería mencionar que es completamente configurable.

music = $.csv.toArrays(csv, {
  delimiter: "'", // Sets a custom value delimiter character
  separator: ';', // Sets a custom field separator character
});

Actualización 2:

Ahora también funciona con jQuery en Node.js. Por lo tanto, tiene la opción de realizar un análisis del lado del cliente o del lado del servidor con la misma biblioteca.

Actualización 3:

Desde el cierre de Google Code, jquery-csv se migró a GitHub.

Descargo de responsabilidad: también soy el autor de jQuery-CSV.

  • ¿Por qué es jQuery csv? ¿Por qué depende de jQuery? He realizado un escaneo rápido a través de la fuente… no parece que estés usando jQuery

    – paulslater19

    10 de mayo de 2012 a las 8:20

  • @ paulslater19 El complemento no depende de jquery. Más bien, sigue las pautas comunes de desarrollo de jQuery. Todos los métodos incluidos son estáticos y residen en su propio espacio de nombres (es decir, $.csv). Para usarlos sin jQuery, simplemente cree un objeto $ global al que se vinculará el complemento durante la inicialización.

    – Evan solla

    19 de noviembre de 2012 a las 20:48

  • es csv en el código de la solución consulte el .csv filename? estoy interesado en una buena herramienta JS/JQuery para analizar un archivo csv

    – hipopótamo rebotando

    22 de noviembre de 2012 a las 19:59

  • @bouncingHippo En el ejemplo, solo se refiere a una cadena de datos csv, pero la lib se puede usar para abrir archivos csv localmente en el navegador usando la API de archivos HTML5. Aquí hay un ejemplo de esto en acción. jquery-csv.googlecode.com/git/examples/file-handling.html.

    – Evan solla

    23 de noviembre de 2012 a las 22:09

  • Dado que no depende de jQuery, sería mejor eliminar la dependencia global “$” y permitir que los usuarios pasen cualquier referencia de objeto que deseen. Quizás por defecto a jQuery si está disponible. Hay otras bibliotecas que usan “$” y pueden ser utilizadas por equipos de desarrollo con servidores proxy mínimos de esas bibliotecas.

    – RobG

    8 de julio de 2018 a las 6:47


Avatar de usuario de Trevor Dixon
trevor dixon

Aquí hay un analizador CSV extremadamente simple que maneja campos entre comillas con comas, líneas nuevas y comillas dobles con escape. No hay división o expresión regular. Escanea la cadena de entrada de 1 a 2 caracteres a la vez y crea una matriz.

Pruébalo en http://jsfiddle.net/vHKYH/.

function parseCSV(str) {
    var arr = [];
    var quote = false;  // 'true' means we're inside a quoted field

    // Iterate over each character, keep track of current row and column (of the returned array)
    for (var row = 0, col = 0, c = 0; c < str.length; c++) {
        var cc = str[c], nc = str[c+1];        // Current character, next character
        arr[row] = arr[row] || [];             // Create a new row if necessary
        arr[row][col] = arr[row][col] || '';   // Create a new column (start with empty string) if necessary

        // If the current character is a quotation mark, and we're inside a
        // quoted field, and the next character is also a quotation mark,
        // add a quotation mark to the current column and skip the next character
        if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }

        // If it's just one quotation mark, begin/end quoted field
        if (cc == '"') { quote = !quote; continue; }

        // If it's a comma and we're not in a quoted field, move on to the next column
        if (cc == ',' && !quote) { ++col; continue; }

        // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
        // and move on to the next row and move to column 0 of that new row
        if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }

        // If it's a newline (LF or CR) and we're not in a quoted field,
        // move on to the next row and move to column 0 of that new row
        if (cc == '\n' && !quote) { ++row; col = 0; continue; }
        if (cc == '\r' && !quote) { ++row; col = 0; continue; }

        // Otherwise, append the current character to the current column
        arr[row][col] += cc;
    }
    return arr;
}

  • Esto parece más limpio y más sencillo. Tuve que analizar un archivo de 4 MB y las otras respuestas se bloquearon en mí en ie8, pero esto lo logró.

    –Charles Clayton

    22 de junio de 2014 a las 15:26


  • Esto también funcionó para mí. Sin embargo, tuve que hacer una modificación para permitir el manejo adecuado de los saltos de línea: if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; } if (cc == '\n' && !quote) { ++row; col = 0; continue; }

    – usuario655063

    08/04/2015 a las 13:00


  • Otro usuario (@sorin-postelnicu) publicó amablemente una función complementaria para convertir el resultado en un objeto de diccionario: jsfiddle.net/8t2po6wh.

    – Trevor Dixon

    20 de octubre de 2017 a las 12:37

  • Sí, cada vez que se necesita velocidad o importan las huellas de memoria, una solución limpia como esta es muy superior. El análisis al estilo de una máquina de estado es mucho más fluido.

    – Tatarizar

    9 de agosto de 2018 a las 5:22

  • Conviértalo en un generador que rinda y obtendrá una forma de manejar datos más grandes: ejemplo

    – Sin fin

    16 de noviembre de 2020 a las 20:29

Avatar de usuario de Andy VanWagoner
Andy VanWagoner

tengo una implementación como parte de un proyecto de hoja de cálculo.

Este código aún no se ha probado a fondo, pero cualquiera puede usarlo.

Sin embargo, como se señaló en algunas de las respuestas, su implementación puede ser mucho más simple si realmente tiene DSV o TSV archivo, ya que no permiten el uso de los separadores de registros y campos en los valores. CSV, por otro lado, en realidad puede tener comas y saltos de línea dentro de un campo, lo que rompe la mayoría de los enfoques basados ​​en expresiones regulares y divididos.

var CSV = {
    parse: function(csv, reviver) {
        reviver = reviver || function(r, c, v) { return v; };
        var chars = csv.split(''), c = 0, cc = chars.length, start, end, table = [], row;
        while (c < cc) {
            table.push(row = []);
            while (c < cc && '\r' !== chars[c] && '\n' !== chars[c]) {
                start = end = c;
                if ('"' === chars[c]){
                    start = end = ++c;
                    while (c < cc) {
                        if ('"' === chars[c]) {
                            if ('"' !== chars[c+1]) {
                                break;
                            }
                            else {
                                chars[++c] = ''; // unescape ""
                            }
                        }
                        end = ++c;
                    }
                    if ('"' === chars[c]) {
                        ++c;
                    }
                    while (c < cc && '\r' !== chars[c] && '\n' !== chars[c] && ',' !== chars[c]) {
                        ++c;
                    }
                } else {
                    while (c < cc && '\r' !== chars[c] && '\n' !== chars[c] && ',' !== chars[c]) {
                        end = ++c;
                    }
                }
                row.push(reviver(table.length-1, row.length, chars.slice(start, end).join('')));
                if (',' === chars[c]) {
                    ++c;
                }
            }
            if ('\r' === chars[c]) {
                ++c;
            }
            if ('\n' === chars[c]) {
                ++c;
            }
        }
        return table;
    },

    stringify: function(table, replacer) {
        replacer = replacer || function(r, c, v) { return v; };
        var csv = '', c, cc, r, rr = table.length, cell;
        for (r = 0; r < rr; ++r) {
            if (r) {
                csv += '\r\n';
            }
            for (c = 0, cc = table[r].length; c < cc; ++c) {
                if (c) {
                    csv += ',';
                }
                cell = replacer(r, c, table[r][c]);
                if (/[,\r\n"]/.test(cell)) {
                    cell=""" + cell.replace(/"/g, '""') + '"';
                }
                csv += (cell || 0 === cell) ? cell : '';
            }
        }
        return csv;
    }
};

avatar de usuario de dt192
dt192

csvToArray v1.3

Una función compacta (645 bytes), pero compatible para convertir una cadena CSV en una matriz 2D, conforme al estándar RFC4180.

https://code.google.com/archive/p/csv-to-array/downloads

Uso común: jQuery

 $.ajax({
        url: "test.csv",
        dataType: 'text',
        cache: false
 }).done(function(csvAsString){
        csvAsArray=csvAsString.csvToArray();
 });

Uso común: JavaScript

csvAsArray = csvAsString.csvToArray();

Anular separador de campo

csvAsArray = csvAsString.csvToArray("|");

Anular separador de registro

csvAsArray = csvAsString.csvToArray("", "#");

Anular omitir encabezado

csvAsArray = csvAsString.csvToArray("", "", 1);

anular todo

csvAsArray = csvAsString.csvToArray("|", "#", 1);

  • Esto suena interesante, pero no puedo encontrar el código ahora. ¿Puedes publicarlo de nuevo?

    –Sam Watkins

    3 de abril de 2018 a las 3:37

  • He actualizado la publicación principal con un enlace actual. Muchas gracias.

    – dt192

    4 de abril de 2018 a las 9:25


  • Está en el archivo de Google Code, pero ¿tal vez actualizarlo a una nueva ubicación?

    -Peter Mortensen

    1 de septiembre de 2020 a las 13:36

  • Es posible que los ejemplos en esta respuesta no funcionen, ya que he visto que se ha cambiado el código fuente. La versión modificada de los ejemplos anteriores para csvToArray v2.1 debería ser así: Anular separador de campo csvAsArray = csvAsString.csvToArray({fSep: "|"}); Anular separador de registro csvAsArray = csvAsString.csvToArray({rSep: "#"}); Anular omitir encabezado csvAsArray = csvAsString.csvToArray({head: true}); anular todo csvAsArray = csvAsString.csvToArray({fSep: "|", rSep: "#", head: true});

    –Faisal Khan

    1 ago a las 14:11

Avatar de usuario de Trevor Dixon
trevor dixon

Aquí está mi gramática PEG (.js) que parece funcionar bien en RFC 4180 (es decir, maneja los ejemplos 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 '"'; }
  / [^"]

Pruébalo en http://jsfiddle.net/knvzk/10 o http://pegjs.majda.cz/online. Descargue el analizador generado en https://gist.github.com/3362830.

  • Esto suena interesante, pero no puedo encontrar el código ahora. ¿Puedes publicarlo de nuevo?

    –Sam Watkins

    3 de abril de 2018 a las 3:37

  • He actualizado la publicación principal con un enlace actual. Muchas gracias.

    – dt192

    4 de abril de 2018 a las 9:25


  • Está en el archivo de Google Code, pero ¿tal vez actualizarlo a una nueva ubicación?

    -Peter Mortensen

    1 de septiembre de 2020 a las 13:36

  • Es posible que los ejemplos en esta respuesta no funcionen, ya que he visto que se ha cambiado el código fuente. La versión modificada de los ejemplos anteriores para csvToArray v2.1 debería ser así: Anular separador de campo csvAsArray = csvAsString.csvToArray({fSep: "|"}); Anular separador de registro csvAsArray = csvAsString.csvToArray({rSep: "#"}); Anular omitir encabezado csvAsArray = csvAsString.csvToArray({head: true}); anular todo csvAsArray = csvAsString.csvToArray({fSep: "|", rSep: "#", head: true});

    –Faisal Khan

    1 ago a las 14:11

Aquí hay otra solución. Esto usa:

  • una expresión regular global gruesa para dividir la cadena CSV (que incluye comillas alrededor y comas finales)
  • Expresión regular de grano fino para limpiar las comillas circundantes y las comas finales.
  • además, tiene corrección de tipo diferenciando cadenas, números, valores booleanos y valores nulos

Para la siguiente cadena de entrada:

"This is\, a value",Hello,4,-123,3.1415,'This is also\, possible',true,

El código genera:

[
  "This is, a value",
  "Hello",
  4,
  -123,
  3.1415,
  "This is also, possible",
  true,
  null
]

Aquí está mi implementación de parseCSVLine() en un fragmento de código ejecutable:

function parseCSVLine(text) {
  return text.match( /\s*(\"[^"]*\"|'[^']*'|[^,]*)\s*(,|$)/g ).map( function (text) {
    let m;
    if (m = text.match(/^\s*,?$/)) return null; // null value
    if (m = text.match(/^\s*\"([^"]*)\"\s*,?$/)) return m[1]; // Double Quoted Text
    if (m = text.match(/^\s*'([^']*)'\s*,?$/)) return m[1]; // Single Quoted Text
    if (m = text.match(/^\s*(true|false)\s*,?$/)) return m[1] === "true"; // Boolean
    if (m = text.match(/^\s*((?:\+|\-)?\d+)\s*,?$/)) return parseInt(m[1]); // Integer Number
    if (m = text.match(/^\s*((?:\+|\-)?\d*\.\d*)\s*,?$/)) return parseFloat(m[1]); // Floating Number
    if (m = text.match(/^\s*(.*?)\s*,?$/)) return m[1]; // Unquoted Text
    return text;
  } );
}

let data = `"This is\, a value",Hello,4,-123,3.1415,'This is also\, possible',true,`;
let obj = parseCSVLine(data);
console.log( JSON.stringify( obj, undefined, 2 ) );

  • ¡Esto es muy bueno! ¿Es esto parte de un paquete npm en alguna parte?

    – cstrat

    16 de junio de 2021 a las 6:36

  • Hice un cambio en la primera expresión regular: text.match( /\s*(\".*?\"|'.*?'|[^,]+|)\s*(,|$)/g ) Tuve que agregar el último | al primer grupo de captura para permitir una celda vacía en un CSV.

    – cstrat

    18 de junio de 2021 a las 9:47

  • Ahora me di cuenta rápidamente de que esto creaba otro caso límite para mí donde coincide superfluamente con una cadena vacía al final. Intenté agregar una anticipación negativa para no contar un vacío al final: text.match(/\s*(".*?"|'.*?'|[^,]+|(?!$))\s*(,|$)/g) Esto creó otro problema en el que no puedo tener una última celda vacía. Podría volver a la solución original y simplemente filtrar las celdas vacías adicionales en la última columna.

    – cstrat

    18 de junio de 2021 a las 9:59

  • ¿Cómo hacer múltiples entradas con cada entrada con columnas de varias líneas?

    – B”H Bi’ezras — Boruj Hashem

    3 de junio a las 8:11

  • @cstrat Hice un cambio en el último partido de [^,]+ a [^,]* para que ahora coincida con una celda vacía y la devuelva como nula. He actualizado el ejemplo para reflejarlo. @BoruchHashem He reemplazado el (\".*?\") con (\"[^"]*\") para que ahora pueda coincidir con cadenas de comillas dobles de varias líneas. Hice un cambio similar para las cadenas entre comillas simples.

    – Stephen Quan

    hace 2 días

¿Ha sido útil esta solución?