如何用JavaScript解析一个CSV字符串,它在数据中包含逗号

How can I parse a CSV string with JavaScript, which contains comma in data?

本文关键字:字符串 数据 包含逗 CSV 一个 JavaScript 何用      更新时间:2023-09-26

我有以下类型的字符串

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

我想在每个逗号上将字符串分割成一个数组,但只有单引号外的逗号。

I can't figure out for the split…

string.split(/,/)

会给我

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

,但结果应该是:

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

是否有跨浏览器的解决方案?

免责声明

2014-12-01更新:下面的答案只适用于一种非常特定的CSV格式。正如DG在评论中正确指出的那样,这个解决方案不适合RFC 4180对CSV的定义,也不适合MS Excel格式。这个解决方案简单地演示了如何解析一个(非标准的)CSV输入行,其中包含字符串类型的混合,其中字符串可能包含转义的引号和逗号。

非标准CSV解决方案

正如austincheney正确指出的那样,如果您希望正确处理可能包含转义字符的引号字符串,您确实需要从头到尾解析字符串。此外,OP没有明确定义什么是"CSV字符串"。真的是。首先,我们必须定义什么构成了一个有效的CSV字符串和它的各个值。

给定:"CSV字符串"定义

出于本讨论的目的,一个"CSV字符串"由零个或多个值组成,其中多个值用逗号分隔。每个值可以包含:

  1. 双引号字符串。(可能包含未转义的单引号)
  2. 单引号字符串。(可能包含未转义的双引号)
  3. 不带引号的字符串。(不可包含引号、逗号或反斜杠)
  4. 空值。

规则/注:

  • 引号可以包含逗号。
  • 引号可以包含转义的任何内容,例如'that''s cool'
  • 包含引号、逗号或反斜杠的值必须引号。
  • 包含空格的值必须加引号。
  • 从所有:''的单引号中删除反斜杠。
  • 从所有:'"的双引号值中删除反斜杠。
  • 未加引号的字符串将删除任何前导和尾随空格。
  • 逗号分隔符可以有相邻的空格(忽略)。

:

将有效的CSV字符串(如上所定义)转换为字符串值数组的JavaScript函数。

解决方案:

此解决方案使用的正则表达式比较复杂。而且(恕我冒昧)所有重要的正则表达式都应该以自由间距模式显示,并带有大量注释和缩进。不幸的是,JavaScript不允许自由空格模式。因此,由该解决方案实现的正则表达式首先以原生正则表达式语法呈现(使用Python方便的:r'''...''' raw-多行字符串语法表示)。

首先是一个正则表达式,它验证CVS字符串是否满足上述要求:

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

如果一个字符串匹配上面的正则表达式,那么这个字符串就是一个有效的CSV字符串(根据前面所述的规则),可以使用下面的正则表达式进行解析。然后使用下面的正则表达式匹配CSV字符串中的一个值。它被重复应用,直到没有找到更多的匹配(并且所有的值都被解析)。

Regex从有效的CSV字符串中解析一个值:

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

请注意,有一个特殊情况值是这个正则表达式不匹配的——当该值为空时的最后一个值。这个特殊的"empty last value"情况由后面的js函数测试和处理。

输入输出示例:

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

    // 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;
    };
    console.log('Test 1: Test string from original question.');
    console.log(CSVtoArray("'string, duppi, du', 23, lala"));
    console.log('Test 2: Empty CSV string.');
    console.log(CSVtoArray(""));
    console.log('Test 3: CSV string with two empty values.');
    console.log(CSVtoArray(","));
    console.log('Test 4: Double quoted CSV string having single quoted values.');
    console.log(CSVtoArray("'one','two with escaped '' single quote', 'three, with, commas'"));
    console.log('Test 5: Single quoted CSV string having double quoted values.');
    console.log(CSVtoArray('"one","two with escaped '" double quote", "three, with, commas"'));
    console.log('Test 6: CSV string with whitespace in and around empty and non-empty values.');
    console.log(CSVtoArray("   one  ,  'two'  ,  , ' four' ,, 'six ', ' seven ' ,  "));
    console.log('Test 7: Not valid');
    console.log(CSVtoArray("one, that's me!, escaped ', comma"));

其他说明:

此解决方案要求CSV字符串为";valid"。例如,未加引号的值可能不包含反斜杠或引号,例如以下CSV字符串是无效的:

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

这实际上并不是一个限制,因为任何子字符串都可以用单引号或双引号表示。还请注意,此解决方案仅表示"逗号分隔值"的一种可能定义。

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

RFC 4180解决方案

这并不能解决问题中的字符串,因为它的格式不符合RFC 4180;可接受的编码是用双引号转义双引号。下面的解决方案可以正确地使用来自google电子表格的CSV文件d/l。

更新(3/2017)

解析单行是错误的。根据RFC 4180,字段可能包含CRLF,这会导致任何行读取器破坏CSV文件。以下是解析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));

老回答

(单行解)

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

为了好玩,下面是如何从数组创建CSV:

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

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

人们似乎反对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";
console.log( string.splitCSV()  );
.as-console-wrapper { max-height: 100% !important; top: 0; }

我有一个非常具体的用例,我想从谷歌表格复制单元格到我的web应用程序。单元格可以包括双引号和新行字符。使用复制和粘贴,单元格由制表符分隔,奇数数据的单元格用双引号括起来。我尝试了这个主要的解决方案,链接的文章使用regexp, Jquery-CSV和CSVToArray。http://papaparse.com/是唯一一个可以正常运行的。复制和粘贴是无缝的与谷歌表格默认自动检测选项。

处理RFC 4180示例的PEG(.js)语法: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 '"'; }
  / [^"]

测试网址:http://jsfiddle.net/knvzk/10或https://pegjs.org/online

在https://gist.github.com/3362830下载生成的解析器

再添加一个到列表中,因为我发现上面的所有内容都不够"KISS"。

这个使用正则表达式查找逗号或换行符,同时跳过引号项。希望这是新手可以自己阅读的东西。splitFinder regexp有三件事(由|分割):

  1. , -查找逗号
  2. 'r?'n -查找新行,(如果出口商很好,可能会有回车)
  3. "(''"|[^"])*?" -跳过引号包围的任何内容,因为逗号和换行符在那里无关紧要。如果引号中有转义的引号''",它将在找到结束引号之前被捕获。

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

无regexp,可读,并根据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;
}

RFC 4180使用正则表达式的解决方案

正则表达式的拯救!这几行代码正确地处理了基于RFC 4180标准的带有内嵌逗号、引号和换行符的引号字段。

function parseCsv(data, fieldSep, newLine) {
  fieldSep = fieldSep || ',';
  newLine = newLine || ''n';
  const nSep = ''x1D'; const nSepRe = new RegExp(nSep, 'g');
  const qSep = ''x1E'; const qSepRe = new RegExp(qSep, 'g');
  const cSep = ''x1F'; const cSepRe = new RegExp(cSep, 'g');
  const fieldRe = new RegExp('(^|[' + fieldSep + '''n])"([^"]*(?:""[^"]*)*)"(?=($|[' + fieldSep + '''n]))', 'g');
  return data
    .replace(/'r/g, '')
    .replace(/'n+$/, '')
    .replace(fieldRe, (match, p1, p2) => {
      return p1 + p2.replace(/'n/g, nSep).replace(/""/g, qSep).replace(/,/g, cSep)
    })
    .split(/'n/)
    .map(line => {
      return line
        .split(fieldSep)
        .map(cell => cell.replace(nSepRe, newLine).replace(qSepRe, '"').replace(cSepRe, ','))
    });
}
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' 
let grid = parseCsv(csv, separator, newline);
// expected: [ [ 'A1', 'B1', 'C1' ], [ 'A "2"', 'B, 2', 'C <br /> 2' ] ]

指出:

  • 可以配置字段分隔符,例如TSV (tab分隔值)的't
  • 内嵌的换行符可以转换成其他东西,比如<br/>的HTML使用
  • 这个parseCsv函数避免消极/积极的向后看,例如也适用于Safari浏览器。

除非另有说明,否则您不需要有限状态机。正则表达式可以正确地处理RFC 4180,这要归功于使用临时替换/恢复、捕获组和正向向前看的函数式编程方法。

克隆/下载代码https://github.com/peterthoeny/parse-csv-js

了解regex的更多信息: https://twiki.org/cgi-bin/view/Codev/TWikiPresentation2018x10x14Regex

使用回溯的旧答案

(这在Safari浏览器上不起作用)

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' ] ]

如果你可以让你的引号分隔符是双引号,那么这是一个复制的示例JavaScript代码来解析CSV数据

你可以先把所有的单引号翻译成双引号:

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

…或者您可以编辑该问题中的正则表达式以识别单引号而不是双引号:

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

但是,这假定了您的问题中不清楚的某些标记。

请根据我对您的问题的评论澄清所有标记的可能性。

我已经多次使用正则表达式,但每次我都必须重新学习它,这是令人沮丧的:-)

所以这里有一个非正则表达式的解决方案:

function csvRowToArray(row, delimiter = ',', quoteChar = '"'){
    let nStart = 0, nEnd = 0, a=[], nRowLen=row.length, bQuotedValue;
    while (nStart <= nRowLen) {
        bQuotedValue = (row.charAt(nStart) === quoteChar);
        if (bQuotedValue) {
            nStart++;
            nEnd = row.indexOf(quoteChar + delimiter, nStart)
        } else {
            nEnd = row.indexOf(delimiter, nStart)
        }
        if (nEnd < 0) nEnd = nRowLen;
        a.push(row.substring(nStart,nEnd));
        nStart = nEnd + delimiter.length + (bQuotedValue ? 1 : 0)
    }
    return a;
}

工作原理:

  1. row中传入csv字符串。
  2. 当下一个值的起始位置在行内时,执行以下操作:
    • 如果该值已经加了引号,则将nEnd设置为结束引号。
    • 如果value没有被引用,将nEnd设置为下一个分隔符
    • 将值添加到数组中
    • 设置nStartnEnd +分隔线长度
有时候编写自己的小函数比使用库更好。您自己的代码将运行良好,并且占用的空间很小。此外,您可以轻松地调整它以满足自己的需要。

我在解析CSV文件时也遇到过同样的问题。

文件包含一个包含','的列地址。

将CSV文件解析为JSON后,在将其转换为JSON文件时,我得到了键的不匹配映射。

我使用Node.js来解析文件和库,如baby parse和csvtojson。

文件示例-

address,pincode
foo,baar , 123456

当我在JSON中直接解析而不使用婴儿解析时,我得到:

[{
 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;
    },'')
}

返回的函数可以传递到csvtojson库中,因此可以使用结果。

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

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

不能使用正则表达式。实际上,您必须编写一个微型解析器来分析希望分割的字符串。为了回答这个问题,我将把字符串的引用部分称为子字符串。你需要明确地遍历字符串。考虑以下情况:

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

在这种情况下,您完全不知道子字符串的开始或结束位置,只需分析字符模式的输入。相反,您必须编写逻辑来判断引号字符是否被用作引号字符,引号本身是否未被引用,以及引号字符是否跟随转义。

我不打算为你写那么复杂的代码,但你可以看看我最近写的一些你需要的模式。这段代码与逗号无关,但它是一个足够有效的微解析器,您可以在编写自己的代码时遵循它。查看下面应用程序的asifix函数:

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

补充这个答案

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

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

当将CSV文件读取为字符串时,它在字符串之间包含空值,因此尝试逐行使用'0

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

试试这个。

function parseCSV(csv) {
    let quotes = [];
    let token = /(?:(['"`])(['s'S]*?)'1)|([^'t,'r'n]+)'3?|(['r'n])/gm;
    let text = csv.replace(/''?(['"`])'1?/gm, s => s.length != 2 ? s : `_r#${quotes.push(s) - 1}`);
    return [...text.matchAll(token)]
        .map(t => (t[2] || t[3] || t[4])
            .replace(/^_r#'d+$/, "")
            .replace(/_r#'d+/g, q => quotes[q.replace(/'D+/, '')][1]))
        .reduce((a, b) => /^['r'n]$/g.test(b)
            ? a.push([]) && a
            : a[a.length - 1].push(b) && a, [[]])
        .filter(d => d.length);
}

使用npm库csv-string来解析字符串,而不是split: https://www.npmjs.com/package/csv-string

这将处理引号中的逗号和空条目

这是基于nity的答案,但对于分号:

'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 and semicolons";four with no quotes;"five with CRLF'r'n"';
console.log(csvToArray(test));

除了ridgerrunner给出的出色而完整的答案外,我还想到了一个非常简单的解决方案,用于后端运行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工具包中(我相信应该稍微修改一下以实现跨浏览器)。

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

它将花费您一个Ajax调用,但至少您不会重复代码或包含任何外部库。

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

你可以像下面的例子那样使用papaparse.js:

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

不要忘记在同一个文件夹中包含papaparse.js。

根据这篇博文,这个函数应该这样做:

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

这个jsfiddle还行,但是看起来有些元素前面有空格