109 votes

Comment puis-je parse un fichier CSV chaîne avec du Javascript?

J'ai le type suivant de la chaîne

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

Je veux diviser la chaîne en un tableau sur chaque virgule, mais seulement les virgules à l'extérieur des guillemets simples.

Je ne peux pas trouver la bonne regex pour le split...

string.split(/,/)

va me donner

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

mais le résultat devrait être:

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

est-il de la croix-navigateur solution?

229voto

ridgerunner Points 14773

Comme austincheney souligne à juste titre, vous avez vraiment besoin pour analyser la chaîne du début à la fin si vous souhaitez gérer correctement cité des chaînes de caractères qui peut contenir des caractères d'échappement. Aussi, l'OP ne définit pas clairement ce qu'est un "CSV chaîne" est vraiment. Nous devons d'abord définir ce qui constitue un valide CSV chaîne et de ses valeurs.

Donnée: "CSV" Chaîne de Définition

Pour les fins de cette discussion, un "CSV" chaîne se compose de zéro ou plusieurs valeurs, où plusieurs valeurs sont séparées par une virgule. Chaque valeur peut consister en:

  1. Une double chaîne de caractères entre guillemets. (peut contenir sans échappement des apostrophes.)
  2. Une seule chaîne de caractères entre guillemets. (peut contenir sans échappement double guillemets.)
  3. Un non-chaîne entre guillemets. (ne peut PAS contenir des citations, des virgules ou des barres obliques inverses.)
  4. Une valeur vide. (Une tous les espaces de la valeur est considérée comme vide.)

Règles/Remarques:

  • Des valeurs à la cote peuvent contenir des virgules.
  • Des valeurs à la cote peut contenir des échappés-quoi que ce soit, par exemple, 'that\'s cool'.
  • Les valeurs contenant des citations, des virgules ou des barres obliques inverses doivent être indiqués.
  • Les valeurs contenant des espaces avant ni après doivent être indiqués.
  • La barre oblique inverse est retiré de tous: \' dans une seule des valeurs à la cote.
  • La barre oblique inverse est retiré de tous: \" en double des valeurs à la cote.
  • Non chaînes entre guillemets sont garnis de toute attaque et de fuite des espaces.
  • La virgule de séparation peut avoir les espaces adjacents (qui est ignoré).

Trouver:

Une fonction JavaScript qui convertit valide CSV chaîne de caractères (tel que défini ci-dessus) dans un tableau de valeurs de chaîne.

Solution:

Les expressions régulières utilisées par cette solution sont complexes. Et (à mon humble avis) de tous les non-trivial regexes devraient être présentés en libre-mode d'espacement avec beaucoup de commentaires et de l'indentation. Malheureusement, JavaScript ne permet pas de libre-mode d'espacement. Ainsi, les expressions régulières mises en œuvre par cette solution sont d'abord présentés en natif syntaxe regex (exprimé à l'aide de Python est très pratique: r'''...''' raw multi-ligne-syntaxe de la chaîne).

Premier ici est une expression régulière qui permet de vérifier que les CVS chaîne répond aux exigences ci-dessus:

Regex pour valider un "CSV chaîne":

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 à ce qui précède regex, alors que la chaîne est valide CSV chaîne (selon les règles précédemment indiqué) et peuvent être analysées à l'aide de la suite de regex. La regex suivante est ensuite utilisé pour faire correspondre une valeur à partir d'un fichier CSV chaîne. Il est appliqué à plusieurs reprises jusqu'à ce que plus aucun des correspondances sont trouvées (et toutes les valeurs ont été analysés).

Regex pour analyser une valeur à partir du valide CSV chaîne:

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 un cas particulier de la valeur que cette expression ne correspond pas à la dernière valeur lorsque celle-ci est vide. Ce spécial "vide dernière valeur" cas est testé pour et traitées par la fonction js qui suit.

Fonction JavaScript pour analyser CSV chaîne:

// 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, les accolades sont utilisées pour délimiter l' {result strings}. (C'est pour aider à visualiser le leading/trailing spaces 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 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] = {} */

Notes supplémentaires:

Cette solution nécessite que le CSV chaîne de l'être "valide". Par exemple, les valeurs non cotées ne peuvent pas contenir des barres obliques inverses ou des citations, par exemple, à la suite de CSV chaîne n'est PAS valide:

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

Ce n'est pas vraiment une limitation, car tout sous-chaîne peut être représentée sous la forme d'un simple ou double cité de la valeur. Notez également que cette solution ne représente qu'une définition possible pour: les "Valeurs Séparées par des Virgules".

Avertissement

2014-05-19 mise à Jour: La réponse ci-dessus ne fonctionne que pour un modèle très précis de CSV. Comme souligné à juste titre par la DG dans les commentaires, cette solution ne correspond PAS à la norme RFC 4180 définition de CSV et il ne rentre PAS en format MS Excel. Cette solution illustre simplement comment on peut analyser une ligne de l'entrée contient un mélange de types de chaînes, où les chaînes peuvent contenir échappé des citations et des virgules.

Edit: 2014-05-19: Ajout de l'avertissement.

7voto

Trevor Dixon Points 6384

PEG(.js) de la grammaire qui traite de la RFC 4180 exemples à 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 '"'; }
  / [^"]

Test à http://jsfiddle.net/knvzk/10 ou http://pegjs.majda.cz/online.

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

3voto

Phrogz Points 112337

Si vous pouvez avoir votre devis délimiteur être double-guillemets, c'est une duplication de Code JavaScript pour Analyser des Données au format CSV.

Vous pouvez traduire tous les simples guillemets à double-guillemets première:

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

...ou vous pouvez modifier la regex dans cette question de reconnaître des guillemets simples au lieu de double-guillemets:

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

Toutefois, cela suppose certaines balises qui n'est pas clair à partir de votre question. Veuillez préciser ce que toutes les possibilités de balisage peuvent être, par mon commentaire sur votre question.

2voto

austincheney Points 714

Ma réponse suppose votre entrée est un reflet de code/contenu à partir de sources web où les guillemets doubles et simples sont entièrement interchangeables à condition de se produire comme un non-échappé jeu correspondant.

Vous ne pouvez pas utiliser les regex pour cela. En fait, vous avez à écrire une micro analyseur d'analyser la chaîne que vous voulez diviser. Je vais, pour le bien de cette réponse, l'appel de la cité pièces de vos chaînes de sous-chaînes. Vous devez spécifiquement à pied à travers la chaîne. Considérons 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 d'où une sous-chaîne de caractères commence ou se termine par la simple analyse de la contribution d'un modèle de caractère. Au lieu de cela, vous devez écrire la logique de prendre des décisions quant à savoir si une citation de caractères est utilisé une citation de caractères, est lui-même non cotées, et que le caractère de devis n'est pas à la suite d'une évasion.

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

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

1voto

CanSpice Points 14749

Selon ce 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 serait-il appeler de la sorte:

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

Cette jsfiddle type de travaux, mais il semble que certains des éléments de l'espace devant eux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X