J'ai le type de chaîne suivant
var string = "'string, duppi, du', 23, lala"
Je veux diviser la chaîne dans un tableau sur chaque virgule, mais seulement les virgules en dehors des guillemets simples.
Je n'arrive pas à trouver la regex qui convient à la scission ...
string.split(/,/)
va me donner
["'string", " duppi", " du'", " 23", " lala"]
mais le résultat devrait être:
["string, duppi, du", "23", "lala"]
existe-t-il une solution multi-navigateurs?
2014-12-01 Mise à jour: La réponse ci-dessous ne fonctionne que pour un format très spécifique de CSV. Comme souligné à juste titre par DG dans les commentaires, cette solution ne correspond PAS à la définition de CSV définie dans la RFC 4180, ni au format MS Excel. Cette solution montre simplement comment on peut analyser une ligne d'entrée CSV (non standard) contenant un mélange de types de chaînes, les chaînes pouvant contenir des guillemets et des guillemets.
Comme l'a souligné correctement austincheney, vous devez vraiment analyser la chaîne du début à la fin si vous souhaitez gérer correctement les chaînes entre guillemets pouvant contenir des caractères d'échappement. En outre, l'OP ne définit pas clairement ce qu'est une "chaîne CSV". Nous devons d’abord définir ce qui constitue une chaîne CSV valide et ses valeurs individuelles.
Aux fins de la présente discussion, une "chaîne CSV" consiste en zéro ou plusieurs valeurs, plusieurs valeurs étant séparées par une virgule. Chaque valeur peut être composée de:
Règles/Notes:
'that\'s cool'
.\'
en valeurs simples.\"
en valeurs entre guillemets.Fonction JavaScript qui convertit une chaîne CSV valide (telle que définie ci-dessus) en un tableau de valeurs de chaîne.
Les expressions régulières utilisées par cette solution sont complexes. Et (IMHO) toutes les expressions rationnelles non triviales doivent être présentées en mode espacement libre avec beaucoup de commentaires et d'indentation. Malheureusement, JavaScript ne permet pas le mode d'espacement libre. Ainsi, les expressions rationnelles implémentées par cette solution sont d'abord présentées dans la syntaxe native de regex (exprimée à l'aide de l'utilitaire de Python: r'''...'''
syntaxe raw-multi-line-string).
Voici d'abord une expression régulière qui valide qu'une chaîne CVS répond aux exigences ci-dessus:
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, cette chaîne est une chaîne CSV valide (conformément aux règles énoncées précédemment) 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 que plus aucune correspondance ne soit trouvée (et toutes les valeurs ont été analysées).
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 casse spéciale à laquelle cette expression rationnelle ne correspond pas - la toute dernière valeur lorsque cette valeur est vide. Ce cas spécial "dernière valeur vide" est testé et traité par la fonction js qui suit.
// 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;
};
Dans les exemples suivants, des accolades sont utilisées pour délimiter le {result strings}
. (Ceci permet de visualiser les espaces de début/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 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] = {} */
Cette solution nécessite que la chaîne CSV soit "valide". Par exemple, les valeurs non entre guillemets peuvent ne pas contenir de barres obliques inverses ni 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 soit comme une valeur simple, soit comme une double guillemet. Notez également que cette solution ne représente qu'une seule définition possible pour: "Valeurs séparées par des virgules".
Edit: 2014-05-19: Ajout de la clause de non-responsabilité. Edit: 2014-12-01: Clause de non responsabilité déplacée vers le haut.
Cela ne résout pas la chaîne dans la question car son format n'est pas conforme à la RFC 4180; l'encodage acceptable échappe à la citation double avec la citation double. La solution ci-dessous fonctionne correctement avec les fichiers CSV d/l de feuilles de calcul Google.
Analyser une seule ligne serait une erreur. Selon la norme RFC 4180, les champs peuvent contenir CRLF, ce qui obligera tout lecteur de ligne à casser le fichier CSV. 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));
(Solution à ligne unique)
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 fichier 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);
Grammaire PEG (.js) prenant en charge les exemples RFC 4180 sur 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 à http://jsfiddle.net/knvzk/1 ou https://pegjs.org/online .
Téléchargez l'analyseur généré à l'adresse https://Gist.github.com/33628 .
Dans certains cas, je souhaitais copier des cellules de Google Sheets dans mon application Web. Les cellules peuvent inclure des guillemets et des caractères de nouvelle ligne. À l’aide de copier-coller, les cellules sont délimitées par des tabulations et les cellules contenant des données impaires sont doubles. J'ai essayé cette solution principale, l'article lié utilisant regexp, Jquery-CSV et CSVToArray. http://papaparse.com/ Est le seul qui a fonctionné hors de la boîte. Copier-coller est transparent avec Google Sheets avec les options de détection automatique par défaut.
En 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 nouvelles lignes tout en ignorant les éléments cités. Espérons que c'est quelque chose que les nobles peuvent lire par eux-mêmes. La regexp splitFinder
a trois fonctions (divisée par un |
):
,
- trouve des virgules\r?\n
- trouve de nouvelles lignes (éventuellement avec retour de transport si l'exportateur était Nice)"(\\"|[^"])*?"
- ignore tout ce qui est entouré de guillemets, car les virgules et les sauts de ligne n'ont pas d'importance. S'il existe une citation échappée \\"
Dans l'élément cité, elle sera capturée avant qu'une citation de fin puisse être trouvée.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);
J'ai aimé la réponse de FakeRainBrigand, cependant, elle contient quelques problèmes: elle ne peut pas gérer les espaces entre une citation et une virgule et ne prend pas en charge deux virgules consécutives. J'ai essayé de modifier sa réponse, mais ma modification a été rejetée par des relecteurs qui, apparemment, ne comprenaient 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('|'));
Si vous pouvez utiliser un délimiteur de guillemets entre guillemets, il s'agit d'un doublon de code JavaScript permettant d'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 rationnelle de cette question pour reconnaître les guillemets simples au lieu des guillemets doubles:
// Quoted fields.
"(?:'([^']*(?:''[^']*)*)'|" +
Cependant, cela suppose un certain balisage qui ne ressort pas clairement de votre question. Veuillez préciser quelles sont les différentes possibilités de balisage, selon mon commentaire sur votre question.
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('|'));
Lors de la lecture de csv à chaîne, il contient une valeur NULL entre les chaînes; essayez-le donc \ Ligne par ligne, cela me convient.
stringLine = stringLine.replace( /\0/g, "" );
Pour compléter cette réponse
Si vous avez besoin d'analyser des citations échappées avec une autre citation, par 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;
}
Ma réponse suppose que votre entrée est le reflet d'un code/contenu provenant de sources Web où les caractères avec guillemets et deux guillemets sont parfaitement interchangeables, à condition qu'ils apparaissent sous la forme d'un ensemble de correspondance non masqué.
Vous ne pouvez pas utiliser regex pour cela. Vous devez en fait écrire un micro-analyseur pour analyser la chaîne que vous souhaitez scinder. Pour les besoins de cette réponse, je vais appeler les parties citées de vos chaînes en tant que sous-chaînes. Vous devez spécifiquement marcher à 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 du début ou de la fin d'une sous-chaîne en analysant simplement l'entrée d'un motif de caractère. Au lieu de cela, vous devez écrire une logique pour décider si un caractère guillemet est utilisé ou non, et que le caractère guillemet ne fait pas suite à un échappement.
Je ne vais pas écrire ce niveau de complexité du code pour vous, mais vous pouvez regarder quelque chose que j'ai écrit récemment 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. Examinez la fonction asifix de l'application suivante:
https://github.com/austincheney/Pretty-Diff/blob/master/fulljsmin.js
J'ai également rencontré le même type de problème lorsque je dois analyser un fichier CSV. Le fichier contient une adresse de colonne qui contient le ','.
Après l'analyse de ce fichier CSV en JSON, le mappage des clés ne correspond pas lors de la conversion en fichier JSON.
J'ai utilisé noeud pour analyser le fichier et la bibliothèque 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 un code qui supprime la virgule (,) avec tout autre déliminateur 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 renvoyée peut être passée dans la bibliothèque csvtojson et le résultat peut donc ê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 */
})
[{
address: 'foo, bar',
pincode: 123456
}]
Vous pouvez utiliser papaparse.js comme dans 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>
N'oubliez pas d'inclure papaparse.js dans le même dossier.
Selon this blog post , 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 comme ceci:
var string = "'string, duppi, du', 23, lala";
var parsed = string.splitCSV();
alert(parsed.join("|"));
This jsfiddle genre de travail, mais il semble que certains des éléments ont des espaces devant eux.
En plus de l'excellente et complète réponse de ridgerunner, j'ai pensé à une solution de contournement très simple pour le moment où votre backend fonctionne en php.
Ajoutez ce fichier php au backend de votre domaine (dites: 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 toolkit javascript (devrait être révisé un peu pour rendre 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));
}
Vous coûtera 1 appel ajax, mais au moins vous ne dupliquerez pas le code ni n'incluerez de bibliothèque externe.