Comment puis-je analyser une chaîne CSV avec JavaScript, qui contient une virgule dans les données?

93

J'ai le type de chaîne suivant

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

Je veux diviser la chaîne en un tableau sur chaque virgule, mais uniquement les virgules en dehors des guillemets simples.

Je n'arrive pas à trouver la bonne expression régulière pour le fractionnement ...

string.split(/,/)

me donnera

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

mais le résultat devrait être:

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

Existe-t-il une solution multi-navigateurs?

Hans
la source
Est-ce toujours des guillemets simples? Y a-t-il déjà un guillemet simple dans une chaîne entre guillemets? Si tel est le cas, comment est-il échappé (contre-oblique, doublé)?
Phrogz
Que faire si les guillemets sont complètement interchangeables entre les guillemets doubles et simples comme dans le code JavaScript et HTML / XML? Si tel est le cas, cela nécessite une opération d'analyse plus approfondie que CSV.
austincheney
en fait oui, il pourrait y avoir un guillemet simple à l'intérieur, échapper avec une barre oblique inverse serait bien.
Hans
Une valeur peut-elle être une chaîne entre guillemets?
ridgerunner
1
Papa Parse fait du bon travail. Analyse d'un fichier CSV local avec JavaScript et Papa Parse: joyofdata.de/blog/…
Raffael

Réponses:

214

Avertissement

Mise à jour du 01/12/2014: La réponse ci-dessous ne fonctionne que pour un format très spécifique de CSV. Comme l'a correctement souligné DG dans les commentaires , cette solution ne correspond pas à la définition RFC 4180 du CSV et elle ne correspond pas non plus au format Microsoft Excel. Cette solution montre simplement comment analyser une ligne d'entrée CSV (non standard) qui contient un mélange de types de chaînes, où les chaînes peuvent contenir des guillemets et des virgules.

Une solution CSV non standard

Comme le souligne à juste titre Austincheney , vous devez vraiment analyser la chaîne du début à la fin si vous souhaitez gérer correctement les chaînes entre guillemets qui peuvent contenir des caractères échappés. De plus, l'OP ne définit pas clairement ce qu'est réellement une "chaîne CSV". Nous devons d'abord définir ce qui constitue une chaîne CSV valide et ses valeurs individuelles.

Donné: Définition de "chaîne CSV"

Pour les besoins de cette discussion, une "chaîne CSV" se compose de zéro ou plusieurs valeurs, où plusieurs valeurs sont séparées par une virgule. Chaque valeur peut être constituée de:

  1. Une chaîne entre guillemets (peut contenir des guillemets simples sans échappement).
  2. Une chaîne entre guillemets simples (peut contenir des guillemets doubles non échappés).
  3. Une chaîne non entre guillemets ( ne peut pas contenir de guillemets, de virgules ou de barres obliques inverses).
  4. Une valeur vide. (Une valeur entièrement vide est considérée comme vide.)

Règles / Notes:

  • Les valeurs citées peuvent contenir des virgules.
  • Les valeurs citées peuvent contenir tout élément échappé, par exemple 'that\'s cool'.
  • Les valeurs contenant des guillemets, des virgules ou des barres obliques inverses doivent être entre guillemets.
  • Les valeurs contenant des espaces de début ou de fin doivent être entre guillemets.
  • La barre oblique inverse est supprimée de tout: \'dans les valeurs entre guillemets simples.
  • La barre oblique inverse est supprimée de tout: \"dans les valeurs entre guillemets.
  • Les chaînes non entre guillemets sont supprimées de tous les espaces de début et de fin.
  • Le séparateur par virgule peut avoir un espace blanc adjacent (qui est ignoré).

Trouver:

Une fonction JavaScript qui convertit une chaîne CSV valide (telle que définie ci-dessus) en un tableau de valeurs de chaîne.

Solution:

Les expressions régulières utilisées par cette solution sont complexes. Et (IMHO) toutes les expressions régulières non triviales doivent être présentées en mode espacement libre avec beaucoup de commentaires et d'indentation. Malheureusement, JavaScript n'autorise pas le mode d'espacement libre. Ainsi, les expressions régulières implémentées par cette solution sont d'abord présentées dans la syntaxe des expressions régulières natives (exprimées en utilisant le pratique de Pythonr'''...''' la syntaxe de chaîne brute multi-lignes ).

Voici d'abord une expression régulière qui valide qu'une chaîne CVS répond aux exigences ci-dessus:

Expression régulière pour valider une "chaîne 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 une chaîne correspond à l'expression régulière ci-dessus, alors cette chaîne est une chaîne CSV valide (selon les règles précédemment énoncées) et peut être analysée à l'aide de l'expression régulière suivante. L'expression régulière suivante est ensuite utilisée pour faire correspondre une valeur de la chaîne CSV. Il est appliqué à plusieurs reprises jusqu'à ce qu'il n'y ait plus de correspondance (et que toutes les valeurs aient été analysées).

Expression régulière pour analyser une valeur à partir d'une chaîne CSV valide:

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

Notez qu'il existe une valeur de cas particulier à laquelle cette expression régulière ne correspond pas - la toute dernière valeur lorsque cette valeur est vide. Cette "dernière valeur vide" spéciale est testé et géré par la fonction JavaScript qui suit.

Fonction JavaScript pour analyser la chaîne 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;
};

Exemple d'entrée et de sortie:

Dans les exemples suivants, des accolades sont utilisées pour délimiter le {result strings}. (Ceci permet de visualiser les espaces de début / de fin et les chaînes de longueur nulle.)

// 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] = {} */

Notes complémentaires:

Cette solution nécessite que la chaîne CSV soit "valide". Par exemple, les valeurs sans guillemets peuvent ne pas contenir de barres obliques inverses ou de guillemets, par exemple, la chaîne CSV suivante n'est pas valide:

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

Ce n'est pas vraiment une limitation car toute sous-chaîne peut être représentée sous la forme d'une valeur entre guillemets simples ou doubles. Notez également que cette solution ne représente qu'une seule définition possible pour les «valeurs séparées par des virgules».

Modifier l'historique

  • 2014-05-19: Avertissement ajouté.
  • 01/12/2014: mise en garde déplacée vers le haut.
ridgerunner
la source
1
@Evan Plaice - Merci pour les gentils mots. Bien sûr, vous pouvez utiliser n'importe quel séparateur. Remplacez simplement chaque virgule de ma regex par le séparateur de votre choix (mais le séparateur ne peut pas être un espace). À votre santé.
ridgerunner
2
@Evan Plaice - Vous êtes invités à utiliser n'importe laquelle de mes expressions régulières pour n'importe quel but que vous désirez. Une note de reconnaissance serait bien mais pas nécessaire. Bonne chance avec votre plug-in. À votre santé!
ridgerunner
1
Cool, voici le projet code.google.com/p/jquery-csv . Finalement, je souhaite ajouter un format d'extension au CSV appelé SSV (Structured Separated Values) qui est simplement CSV avec des métadonnées (c'est-à-dire, délimiteur, séparateur, fin de ligne, etc.) inclus.
Evan Plaice
1
Merci beaucoup pour cette excellente implémentation - je l'ai utilisée comme base pour un module Node.js ( csv-iterator ).
mirkokiefer
3
J'applaudis au détail et à la clarification de votre réponse, mais il convient de noter quelque part que votre définition de CSV ne correspond pas à la RFC 4180, qui est la chose la plus proche d'une norme pour CSV, et que je peux dire de manière anecdotique est couramment utilisée. En particulier, ce serait la manière normale d '«échapper» un caractère guillemet double dans un champ de chaîne: "field one", "field two", "a ""final"" field containing two double quote marks"je n'ai pas testé la réponse de Trevor Dixon sur cette page, mais c'est une réponse qui répond à la définition RFC 4180 du CSV.
DG.
53

Solution RFC 4180

Cela ne résout pas la chaîne de la question car son format n'est pas conforme à la RFC 4180; le codage acceptable échappe entre guillemets doubles. La solution ci-dessous fonctionne correctement avec les fichiers CSV d / l des feuilles de calcul Google.

MISE À JOUR (3/2017)

L'analyse d'une seule ligne serait erronée. Selon la RFC 4180, les champs peuvent contenir CRLF, ce qui provoquera la rupture du fichier CSV par tout lecteur de ligne. Voici une version mise à jour qui analyse la chaîne 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));

ANCIENNE RÉPONSE

(Solution sur une seule ligne)

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

Et pour le plaisir, voici comment créer un CSV à partir du tableau:

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
la source
1
celui-ci a fait le travail pour moi, pas l'autre
WtFudgE
7

Grammaire PEG (.js) qui gère les exemples RFC 4180 à l' adresse 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 '"'; }
  / [^"]

Testez sur http://jsfiddle.net/knvzk/10 ou https://pegjs.org/online .

Téléchargez l'analyseur généré à l' adresse https://gist.github.com/3362830 .

Trevor Dixon
la source
6

J'ai eu un cas d'utilisation très spécifique où je voulais copier des cellules de Google Sheets dans mon application Web. Les cellules peuvent inclure des guillemets et des caractères de nouvelle ligne. En utilisant le copier-coller, les cellules sont délimitées par des caractères de tabulation et les cellules avec des données impaires sont entre guillemets. J'ai essayé cette solution principale, l'article lié utilisant regexp et Jquery-CSV et CSVToArray. http://papaparse.com/ Est le seul qui a fonctionné hors de la boîte. Le copier-coller est transparent avec Google Sheets avec des options de détection automatique par défaut.

bjcullinan
la source
1
Cela devrait être classé beaucoup plus haut, n'essayez jamais de lancer votre propre analyseur CSV, cela ne fonctionnera pas correctement - en particulier lors de l'utilisation de regexes. Papaparse est génial - utilisez-le!
cbley
6

J'ai aimé la réponse de FakeRainBrigand, mais elle contient quelques problèmes: il ne peut pas gérer les espaces entre un guillemet et une virgule, et ne prend pas en charge 2 virgules consécutives. J'ai essayé de modifier sa réponse, mais ma modification a été rejetée par des critiques qui ne comprenaient apparemment pas mon code. Voici ma version du code de FakeRainBrigand. Il y a aussi un violon: 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
la source
4

Les gens semblaient être contre RegEx pour cela. Pourquoi?

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

Voici le code. J'ai aussi fait un violon .

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('|'));
Brigand
la source
3
Hmm, votre expression régulière a quelques problèmes: elle ne peut pas gérer les espaces entre un guillemet et une virgule, et ne prend pas en charge 2 virgules consécutives. J'ai mis à jour votre réponse avec un code qui corrige les deux problèmes et créé un nouveau violon: jsfiddle.net/xTezm/43
HammerNL
Pour une raison quelconque, ma modification de votre code a été rejetée car elle "s'écarterait de l'intention originale du message". Très étrange!? J'ai juste pris votre code et corrigé deux problèmes avec. En quoi cela change-t-il l'intention du message !? Quoi qu'il en soit ... J'ai simplement ajouté une nouvelle réponse à cette question.
HammerNL
Bonne question dans votre réponse, @FakeRainBrigand. Pour ma part, je suis pour regex, et à cause de cela, je reconnais que ce n'est pas le bon outil pour le travail.
niry
2
@niry mon code ici est horrible. Je promets que je me suis amélioré au cours des 6 dernières années :-p
Brigand
4

Ajouter un de plus à la liste, car je trouve que tout ce qui précède n'est pas assez "KISS".

Celui-ci utilise regex pour trouver des virgules ou des retours à la ligne tout en sautant les éléments cités. J'espère que c'est quelque chose que les noobies peuvent lire par eux-mêmes. L' splitFinderexpression rationnelle a trois fonctions (divisée par un |):

  1. , - trouve des virgules
  2. \r?\n - trouve de nouvelles lignes, (potentiellement avec retour chariot si l'exportateur était gentil)
  3. "(\\"|[^"])*?"- ignore tout ce qui est entouré de guillemets, car les virgules et les retours à la ligne n'ont pas d'importance. S'il y a un devis échappé\\" dans l'élément cité, il sera capturé avant qu'un devis final puisse être trouvé.

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
la source
Si je lis mon fichier via FileReader et mon résultat: Id, Name, Age 1, John Smith, 65 2, Jane Doe, 30 comment puis-je analyser en fonction des colonnes que je spécifie?
bluePearl
Après avoir obtenu le tableau 2d, supprimez le premier index (ce sont vos noms d'accessoires), puis parcourez le reste du tableau, en créant des objets avec chacune des valeurs comme propriété. Cela ressemblera à ceci:[{Id: 1, Name: "John Smith", Age: 65}, {Id: 2, Name: "Jane Doe", Age: 30}]
Seph Reed
3

Si vous pouvez faire en sorte que votre délimiteur de devis soit des guillemets, il s'agit d'un double de l' exemple de code JavaScript pour analyser les données CSV .

Vous pouvez d'abord traduire tous les guillemets simples en guillemets doubles:

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

... ou vous pouvez modifier l'expression régulière dans cette question pour reconnaître les guillemets simples au lieu des guillemets doubles:

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

Cependant, cela suppose un certain balisage qui n'est pas clair dans votre question. Veuillez préciser quelles peuvent être toutes les différentes possibilités de balisage, conformément à mon commentaire sur votre question.

Phrogz
la source
2

Ma réponse suppose que votre entrée reflète le code / contenu de sources Web où les guillemets simples et doubles sont entièrement interchangeables à condition qu'ils se produisent comme un ensemble de correspondance non échappé.

Vous ne pouvez pas utiliser regex pour cela. Vous devez en fait écrire un micro analyseur pour analyser la chaîne que vous souhaitez diviser. Dans l'intérêt de cette réponse, je vais appeler les parties entre guillemets de vos chaînes en tant que sous-chaînes. Vous devez spécifiquement marcher sur la chaîne. Prenons le cas suivant:

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.";

Dans ce cas, vous n'avez absolument aucune idée de l'endroit où une sous-chaîne commence ou se termine en analysant simplement l'entrée pour un modèle de caractère. Au lieu de cela, vous devez écrire une logique pour décider si un caractère guillemet est utilisé comme caractère guillemet, est lui-même sans guillemets et que le caractère guillemet ne suit pas un échappement.

Je ne vais pas écrire ce niveau de complexité de code pour vous, mais vous pouvez regarder quelque chose que j'ai récemment écrit et qui a le modèle dont vous avez besoin. Ce code n'a rien à voir avec des virgules, mais est par ailleurs un micro-analyseur suffisamment valide pour que vous puissiez suivre l'écriture de votre propre code. Regardez dans la fonction asifix de l'application suivante:

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

Austincheney
la source
2

Pour compléter cette réponse

Si vous avez besoin d'analyser les guillemets échappés avec un autre guillemet, exemple:

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

Vous pouvez utiliser

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
la source
J'ai constaté que l'analyse échoue toujours"jjj "" kkk""","123"
niry
2

Lors de la lecture du fichier CSV dans une chaîne, il contient des valeurs nulles entre les chaînes, essayez-le avec \ 0 ligne par ligne. Ça marche pour moi.

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

J'ai également rencontré le même type de problème lorsque j'ai dû analyser un fichier CSV.

Le fichier contient une adresse de colonne qui contient le ','.

Après avoir analysé ce fichier CSV en JSON, j'obtiens un mappage incompatible des clés lors de la conversion en fichier JSON.

J'ai utilisé Node.js pour analyser le fichier et les bibliothèques comme baby parse et csvtojson .

Exemple de fichier -

address,pincode
foo,baar , 123456

Alors que j'analysais directement sans utiliser baby parse dans JSON, j'obtenais:

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

J'ai donc écrit du code qui supprime la virgule (,) avec tout autre délimiteur avec chaque champ:

/*
 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 fonction retournée peut être passée dans la bibliothèque csvtojson et ainsi le résultat peut être utilisé.

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

Vous pouvez maintenant obtenir la sortie comme:

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

Aucune expression régulière, lisible, et selon 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
la source
1

Selon cet article de blog , cette fonction devrait le faire:

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

Vous l'appelleriez ainsi:

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

Ce type de jsfiddle fonctionne, mais il semble que certains éléments aient des espaces devant eux.

CanSpice
la source
Imaginez avoir à faire tout cela dans une regex. C'est pourquoi les expressions régulières ne sont parfois pas vraiment adaptées à l'analyse syntaxique.
CanSpice
Cette solution ne fonctionne tout simplement pas. Compte tenu de la chaîne de test d'origine "'string, duppi, du', 23, lala"["'string"," duppi"," du'"," 23"," lala"]
:,
@ridgerunner: Vous avez raison. J'ai édité la réponse et le jsfiddle pour corriger la fonction. En fait, je suis passé "'"à '"'et vice-versa.
CanSpice
Cela a aidé, mais maintenant, la fonction gère incorrectement les chaînes CSV entre guillemets simples ayant des valeurs entre guillemets doubles. Par exemple, inverser les types de guillemets de la chaîne de test d'origine comme ceci: '"string, duppi, du", 23, lala'donne:['"string',' duppi'.' du"',' 23',' lala']
ridgerunner
@CanSpice, votre commentaire m'a inspiré à essayer RegEx. Il n'a pas autant de fonctionnalités, mais elles pourraient être facilement ajoutées. (Ma réponse est sur cette page, si vous êtes intéressé.)
Brigand
0

Les expressions régulières à la rescousse! Ces quelques lignes de code gèrent les champs correctement entre guillemets avec des virgules incorporées, des guillemets et des retours à la ligne basés sur la norme 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' ] ]

Sauf indication contraire, vous n'avez pas besoin d'une machine à états finis. L'expression régulière gère correctement la RFC 4180 grâce à une analyse positive, une analyse négative et une anticipation positive.

Cloner / télécharger le code sur https://github.com/peterthoeny/parse-csv-js

Peter Thoeny
la source
0

Mis à part l'excellente et complète réponse de ridgerunner , j'ai pensé à une solution de contournement très simple lorsque votre backend exécute PHP.

Ajouter ce fichier PHP back - end de votre domaine ( par exemple de: 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")));
?>

Maintenant, ajoutez cette fonction à votre boîte à outils JavaScript (devrait être révisée un peu pour faire un crossbrowser je crois).

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

Cela vous coûtera un appel Ajax, mais au moins vous ne dupliquerez pas de code ni n'incluerez aucune bibliothèque externe.

Réf: http://php.net/manual/en/function.str-getcsv.php

Sebas
la source
0

Vous pouvez utiliser papaparse.js comme l'exemple ci-dessous:

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

N'oubliez pas d'inclure papaparse.js dans le même dossier.

Tahseen Alaa
la source