如何parsingCSVstring,其中包含数据中的逗号?

我有以下types的string

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

我想分割string到每个逗号的数组,但只有单引号外的逗号。

我不能找出正确的正则expression式分裂…

 string.split(/,/) 

会给我

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

但结果应该是:

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

有没有跨浏览器的解决scheme?

放弃

2014-12-01更新:下面的答案只适用于一个非常具体的CSV格式。 正如DG在注释中正确指出的那样,该解决scheme不符合RFC 4180的CSV定义,也不适合MS Excel格式。 该解决scheme简单地演示了如何parsing包含stringtypes混合的一个(非标准)CSVinput行,其中string可能包含转义引号和逗号。

非标准的CSV解决scheme

正如austincheney正确指出的那样,如果您希望正确处理可能包含转义字符的引用string,则您确实需要从头到尾parsingstring。 另外,OP没有明确定义“CSVstring”究竟是什么。 首先,我们必须定义什么构成有效的CSVstring及其个人价值。

鉴于:“CSVstring”的定义

为了讨论的目的,“CSVstring”由零个或多个值组成,其中多个值由逗号分隔。 每个值可以包括:

  1. 双引号string (可能包含未转义的单引号。)
  2. 单引号string。 (可能包含未转义的双引号。)
  3. 一个不带引号的string。 (不得包含引号,逗号或反斜线。)
  4. 一个空值。 (所有空白值都被认为是空的。)

规则/注意事项:

  • 引用的值可能包含逗号。
  • 引用的值可能包含任何东西,例如'that\'s cool'
  • 包含引号,逗号或反斜杠的值必须加引号。
  • 包含前导或尾随空白的值必须加引号。
  • 反斜杠将从单引号值中的所有“ \'中删除。
  • 反斜杠将从双引号值中的\"中删除。
  • 未加引号的string被剪裁为任何前导和尾随空格。
  • 逗号分隔符可能有相邻的空格(忽略)。

找:

将有效的CSVstring(如上定义)转换为string值数组的JavaScript函数。

解:

这个解决scheme使用的正则expression式很复杂。 和(恕我直言) 所有不平凡的正则expression式应该以自由空间模式呈现,有很多评论和缩进。 不幸的是,JavaScript不允许自由空间模式。 因此,这个解决scheme所实现的正则expression式首先以原生的正则expression式语法(用Python的方便expression: r'''...'''原始多行string语法)呈现。

首先,这里是一个正则expression式,用于validationCVSstring是否符合上述要求:

正则expression式来validation“CSVstring”:

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

如果一个string与上面的正则expression式相匹配,那么该string是一个有效的CSVstring(根据前面提到的规则),可以使用下面的正则expression式进行parsing。 然后使用以下正则expression式来匹配CSVstring中的一个值。 它被重复应用,直到找不到更多的匹配(并且所有的值已被parsing)。

正则expression式来parsing有效的CSVstring中的一个值:

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

请注意,这个正则expression式不匹配有一个特殊情况值 – 当该值为空时的最后一个值。 这个特殊的“空的最后一个值”的情况是由下面的js函数testing和处理的。

JavaScript函数来parsingCSVstring:

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

示例input和输出:

在以下示例中,大括号用于分隔{result strings} 。 (这是为了帮助可视化前后空格和零长度的string。)

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

补充笔记:

这个解决scheme要求CSVstring是“有效的”。 例如,未加引号的值可能不包含反斜线或引号,例如下面的CSVstring是无效的:

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

这不是一个限制,因为任何子string都可以表示为单引号或双引号。 还要注意,这个解决scheme仅代表一个可能的定义:“逗号分隔值”。

编辑:2014-05-19:新增免责声明。 编辑:2014-12-01:将免责声明移至顶部。

RFC 4180解决scheme

这并不能解决问题中的string,因为它的格式不符合RFC 4180; 可接受的编码是双引号转义双引号。 下面的解决scheme可以正确处理来自Google电子表格的CSV文件d / l。

更新(3/2017)

parsing单行将是错误的。 根据RFC 4180的字段可能包含CRLF,这将导致任何线路阅读器中断CSV文件。 以下是parsingCSVstring的更新版本:

 'use strict'; function csvToArray(text) { let p = '', row = [''], ret = [row], i = 0, r = 0, s = !0, l; for (l in text) { l = text[l]; 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)); 

http://en.wikipedia.org/wiki/Comma-separated_values处理RFC 4180示例的PEG(.js)语法:

 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 '"'; } / [^"] 

http://jsfiddle.net/knvzk/10或http://pegjs.majda.cz/online进行testing。

https://gist.github.com/3362830下载生成的parsing器。

我有一个非常具体的用例,我想将Google表格中的单元格复制到我的networking应用程序中。 单元格可以包含双引号和换行符。 使用复制和粘贴,单元格由制表符分隔,具有奇数数据的单元格用双引号括起来。 我试过这个主要的解决scheme,链接的文章使用正则expression式,和Jquery-CSV和CSVToArray。 http://papaparse.com/是唯一一个开箱即用的工具。 使用默认的自动检测选项,复制和粘贴与Google表格无缝对接。

如果您的报价分隔符是双引号,那么这是JavaScript代码parsingCSV数据的重复。

您可以先将所有的单引号翻译成双引号:

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

…或者你可以在该问题中编辑正则expression式来识别单引号而不是双引号:

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

但是,这假设某些标记不清楚你的问题。 请根据我对您问题的评论,澄清标记的各种可能性。

我的答案假定您的input是来自Web源代码/内容的反映,其中单引号和双引号字符完全可以互换,只要它们作为非转义匹配集出现即可。

你不能使用这个正则expression式。 您实际上必须编写一个微分析器来分析您想要分割的string。 为了这个答案,我会把你的string的引用部分作为子string。 你需要专门穿过string。 考虑以下情况:

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

在这种情况下,通过简单地分析字符模式的input,您完全不知道子string开始或结束的位置。 相反,您必须编写逻辑来决定引号字符是否使用引号字符,本身是否引用引号,引号字符是否不是转义字符。

我不打算为你编写那么复杂的代码,但是你可以看看我最近写的那个有你需要的模式的东西。 这段代码与逗号无关,但是在其他方面,它是一个有效的微分析器,可以让你在编写自己的代码时遵循这个规则。 看看下面的应用程序的asifix函数:

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

人们似乎反对RegEx为此。 为什么?

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

这是代码。 我也做了一个小提琴 。

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

当阅读csvstring它包含空值在string之间,所以尝试它\ 0逐行它工作我。

 stringLine = stringLine.replace( /\0/g, "" ); 

补充这个答案

如果您需要parsing用另一个引号转义的引号,例如:

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

您可以使用

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

我喜欢FakeRainBrigand的答案,但它包含一些问题:它不能处理引号和逗号之间的空格,并且不支持2个连续的逗号。 我试图编辑他的答案,但我的编辑被拒绝的审稿人,显然不明白我的代码。 这是我的FakeRainBrigand的代码版本。 还有一个小提琴: 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('|')); 

当我必须parsingCSV文件时,我也面临同样的问题。 该文件包含一个列地址,其中包含','。
将该CSVparsing为JSON后,将其转换为JSON文件时会得到不匹配的密钥映射。
我使用节点parsing文件和库像婴儿parsing和csvtojson
文件示例 –

 address,pincode foo,baar , 123456 

虽然我直接parsing,而不使用JSON婴儿parsing,我越来越

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

所以我写了一个代码,删除每个字段的任何其他分隔符的逗号(,)

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

根据这个博客文章 ,这个function应该做到这一点:

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

你会这样称呼它:

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

这jsfiddletypes的作品,但它看起来像一些元素之前有空格。

除了来自ridgerunner的优秀和完整的答案,我想到了一个非常简单的解决方法,当你的后端运行PHP。

添加这个PHP文件到你的域的后端(说: 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"))); ?> 

现在把这个函数添加到你的javascript工具箱中(我相信应该修改一下使crossbrowser。)

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

将花费你1个Ajax调用,但至less你不会重复代码,也不包括任何外部库。

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