Javascript代码来解析CSV数据


215

有人对我在哪里可以找到一些JavaScript代码来解析CSV数据有想法吗?


3
看看这个答案在这里,它具有很好的答案: stackoverflow.com/questions/8493195/...
Dobes VanderMeer的

14
除了安迪(Andy)的答案,以下大多数答案都是错误的。任何使用模式匹配或拆分的答案注定会失败-它们将不支持转义序列。为此,您需要一个有限状态机。
greg.kindel

3
使用JavaScript和Papa Parse解析本地CSV文件:joyofdata.de/blog/…–
Raffael

4
Papa Parse是具有许多功能(多线程,头行支持,自动检测定界符等)的另一个选项
Hinrich

1
PapaParse的另一个投票,我将它与AngularJS一起使用,效果很好。
德米特里·布斯拉耶夫

Answers:


257

您可以使用此博客条目中提到的CSVToArray()函数。

<script type="text/javascript">
    // ref: http://stackoverflow.com/a/1293163/2343
    // This will parse a delimited string into an array of
    // arrays. The default delimiter is the comma, but this
    // can be overriden in the second argument.
    function CSVToArray( strData, strDelimiter ){
        // Check to see if the delimiter is defined. If not,
        // then default to comma.
        strDelimiter = (strDelimiter || ",");

        // Create a regular expression to parse the CSV values.
        var objPattern = new RegExp(
            (
                // Delimiters.
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

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

                // Standard fields.
                "([^\"\\" + strDelimiter + "\\r\\n]*))"
            ),
            "gi"
            );


        // Create an array to hold our data. Give the array
        // a default empty first row.
        var arrData = [[]];

        // Create an array to hold our individual pattern
        // matching groups.
        var arrMatches = null;


        // Keep looping over the regular expression matches
        // until we can no longer find a match.
        while (arrMatches = objPattern.exec( strData )){

            // Get the delimiter that was found.
            var strMatchedDelimiter = arrMatches[ 1 ];

            // Check to see if the given delimiter has a length
            // (is not the start of string) and if it matches
            // field delimiter. If id does not, then we know
            // that this delimiter is a row delimiter.
            if (
                strMatchedDelimiter.length &&
                strMatchedDelimiter !== strDelimiter
                ){

                // Since we have reached a new row of data,
                // add an empty row to our data array.
                arrData.push( [] );

            }

            var strMatchedValue;

            // Now that we have our delimiter out of the way,
            // let's check to see which kind of value we
            // captured (quoted or unquoted).
            if (arrMatches[ 2 ]){

                // We found a quoted value. When we capture
                // this value, unescape any double quotes.
                strMatchedValue = arrMatches[ 2 ].replace(
                    new RegExp( "\"\"", "g" ),
                    "\""
                    );

            } else {

                // We found a non-quoted value.
                strMatchedValue = arrMatches[ 3 ];

            }


            // Now that we have our value string, let's add
            // it to the data array.
            arrData[ arrData.length - 1 ].push( strMatchedValue );
        }

        // Return the parsed data.
        return( arrData );
    }

</script>

1
这可以处理嵌入式逗号,引号和换行符,例如:var csv ='id,value \ n1,James \ n02,“ Jimmy Smith,Esq。” \ n003,“ James”“ Jimmy”“ Smith,III” \ n0004,“ James \ nSmith \ nWuz Here”“ var array = CSVToArray(csv,”,“);
原型

4
它给出undefined了被引用的空字段。示例:给我,但给我。CSVToArray("4,,6")[["4","","6"]]CSVToArray("4,\"\",6")[["4",undefined,"6"]]

3
我在firefox中遇到了这个问题,脚本变得无法响应。不过,它似乎只影响了少数用户,因此找不到原因
JDandChips

8
正则表达式中有一个错误:"([^\"\\"应该是"([^\\"。否则,如果在双引号中使用未加引号的值,则会提前结束它。发现了这个困难的方法...
Walter Tross 2015年

5
对于正在寻找上述方法的简化版本且使用上述正则表达式修复的任何人:gist.github.com/Jezternz/c8e9fafc2c114e079829974e3764db75
Josh Mc

147

jQuery的CSV

这是一个jquery插件,旨在用作将CSV解析为Javascript数据的端到端解决方案。它处理RFC 4180中出现的每个单边情况,以及为规范丢失的Excel / Google Spreadsheed导出而弹出的一些情况(即主要涉及空值)。

例:

跟踪,艺术家,专辑,年

危险,“ Busta Rhymes”,“灾难发生时”,1997年

// calling this
music = $.csv.toArrays(csv)

// outputs...
[
  ["track","artist","album","year"],
  ["Dangerous","Busta Rhymes","When Disaster Strikes","1997"]
]

console.log(music[1][2]) // outputs: 'When Disaster Strikes'

更新:

哦,是的,我可能还应该提到它是完全可配置的。

music = $.csv.toArrays(csv, {
  delimiter:"'", // sets a custom value delimiter character
  separator:';', // sets a custom field separator character
});

更新2:

现在,它也可以在Node.js上使用jQuery。因此,您可以选择使用相同的lib进行客户端或服务器端解析。

更新3:

自Google Code关闭以来,jquery-csv已迁移到GitHub

免责声明:我也是jQuery-CSV的作者。


29
为什么是jQuery csv?为什么要依赖jQuery?我快速浏览了一下源代码……看起来好像您不是在使用jQuery
paulslater19 2012年

17
@ paulslater19该插件不依赖于jquery。而是遵循通用的jQuery开发准则。包括的所有方法都是静态的,并位于其自己的名称空间(即$ .csv)下。要在没有jQuery的情况下使用它们,只需创建一个全局$对象,插件将在初始化期间绑定到该对象。
Evan Plaice 2012年

2
csv在解决方案代码指的是.csv filename?我对解析CSV文件的优质JS / JQuery工具感兴趣
bouncingHippo 2012年

1
@bouncingHippo在此示例中,它仅引用一串csv数据,但可以使用lib使用HTML5 File API在浏览器中本地打开csv文件。这是动作jquery-csv.googlecode.com/git/examples/file-handling.html中的示例
Evan Plaice 2012年

1
鉴于它不依赖jQuery,最好删除全局的“ $”依赖关系,并让用户传递所需的任何对象引用。如果可用,则默认为jQuery。还有其他使用“ $”的库,开发团队可能会使用这些库中最少的代理。
RobG '18年

40

作为电子表格项目的一部分,我有一个实现

该代码尚未经过全面测试,但是欢迎任何人使用。

正如一些答案指出的那样,如果您实际上有DSVTSV文件,则实现可能会简单得多,因为它们不允许在值中使用记录和字段分隔符。另一方面,CSV实际上可以在字段中包含逗号和换行符,这会破坏大多数正则表达式和基于拆分的方法。

var CSV = {
parse: function(csv, reviver) {
    reviver = reviver || function(r, c, v) { return v; };
    var chars = csv.split(''), c = 0, cc = chars.length, start, end, table = [], row;
    while (c < cc) {
        table.push(row = []);
        while (c < cc && '\r' !== chars[c] && '\n' !== chars[c]) {
            start = end = c;
            if ('"' === chars[c]){
                start = end = ++c;
                while (c < cc) {
                    if ('"' === chars[c]) {
                        if ('"' !== chars[c+1]) { break; }
                        else { chars[++c] = ''; } // unescape ""
                    }
                    end = ++c;
                }
                if ('"' === chars[c]) { ++c; }
                while (c < cc && '\r' !== chars[c] && '\n' !== chars[c] && ',' !== chars[c]) { ++c; }
            } else {
                while (c < cc && '\r' !== chars[c] && '\n' !== chars[c] && ',' !== chars[c]) { end = ++c; }
            }
            row.push(reviver(table.length-1, row.length, chars.slice(start, end).join('')));
            if (',' === chars[c]) { ++c; }
        }
        if ('\r' === chars[c]) { ++c; }
        if ('\n' === chars[c]) { ++c; }
    }
    return table;
},

stringify: function(table, replacer) {
    replacer = replacer || function(r, c, v) { return v; };
    var csv = '', c, cc, r, rr = table.length, cell;
    for (r = 0; r < rr; ++r) {
        if (r) { csv += '\r\n'; }
        for (c = 0, cc = table[r].length; c < cc; ++c) {
            if (c) { csv += ','; }
            cell = replacer(r, c, table[r][c]);
            if (/[,\r\n"]/.test(cell)) { cell = '"' + cell.replace(/"/g, '""') + '"'; }
            csv += (cell || 0 === cell) ? cell : '';
        }
    }
    return csv;
}
};

9
这是我最喜欢的答案之一。这是一个用很少的代码实现的真正的解析器。
Trevor Dixon

1
如果逗号放在行尾,则后面应有一个空单元格。这段代码只是跳到下一行,生成一个undefined单元格。例如,console.log(CSV.parse("first,last,age\r\njohn,doe,"));
skibulk

同样,空单元格应解析为空字符串。这段代码将它们解析为零,这令人困惑,因为单元格实际上可以包含零:console.log(CSV.parse("0,,2,3"));
skibulk

@skibulk您的第二条评论不正确(至少在Chrome浏览器中,您的示例正常运行)。尽管您的第一个评论很容易解决,但仍是有效的-在此之前添加以下内容if ('\r' === chars[c]) { ... }if (end === c-1) { row.push(reviver(table.length-1, row.length, '')); }
coderforlife

35

这是一个非常简单的CSV解析器,用于处理带逗号,换行和转义双引号的带引号的字段。没有拆分或正则表达式。它一次扫描输入字符串1-2个字符并构建一个数组。

http://jsfiddle.net/vHKYH/上进行测试。

function parseCSV(str) {
    var arr = [];
    var quote = false;  // true means we're inside a quoted field

    // iterate over each character, keep track of current row and column (of the returned array)
    for (var row = 0, col = 0, c = 0; c < str.length; c++) {
        var cc = str[c], nc = str[c+1];        // current character, next character
        arr[row] = arr[row] || [];             // create a new row if necessary
        arr[row][col] = arr[row][col] || '';   // create a new column (start with empty string) if necessary

        // If the current character is a quotation mark, and we're inside a
        // quoted field, and the next character is also a quotation mark,
        // add a quotation mark to the current column and skip the next character
        if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }  

        // If it's just one quotation mark, begin/end quoted field
        if (cc == '"') { quote = !quote; continue; }

        // If it's a comma and we're not in a quoted field, move on to the next column
        if (cc == ',' && !quote) { ++col; continue; }

        // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
        // and move on to the next row and move to column 0 of that new row
        if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }

        // If it's a newline (LF or CR) and we're not in a quoted field,
        // move on to the next row and move to column 0 of that new row
        if (cc == '\n' && !quote) { ++row; col = 0; continue; }
        if (cc == '\r' && !quote) { ++row; col = 0; continue; }

        // Otherwise, append the current character to the current column
        arr[row][col] += cc;
    }
    return arr;
}

它很简单,对我
有用

3
这看起来更干净,更直接。我不得不解析一个4mb的文件,其他答案在ie8中崩溃了,但这还是可以解决的。
Charles Clayton 2014年

3
这对我也有用。我必须进行一次修改,以允许正确处理换行符:if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; } if (cc == '\n' && !quote) { ++row; col = 0; continue; }
user655063

1
另一个用户(@ sorin-postelnicu)有用地发布了一个伴随函数,以将结果转换为字典对象:jsfiddle.net/8t2po6wh
Trevor Dixon

1
是的,无论何时需要速度或内存占用至关重要,像这样的干净解决方案都非常出色。状态机风格的解析非常顺畅。
8/9

14

这是我的PEG(.js)语法,在RFC 4180上似乎没问题(即,它处理了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/10http://pegjs.majda.cz/online上尝试一下。在https://gist.github.com/3362830下载生成的解析器。


2
PEG?建立AST不会为III型语法带来很多内存。它能否处理包含换行符的字段,因为这是“常规语法”解析器中最难解决的情况。无论哪种方式,+ 1都是一种新颖的方法。
Evan Plaice

1
是的,它在字段内处理换行符。
Trevor Dixon

2
很好...仅此一项,它比我见过的所有实现都要好95%。如果要检查是否完全符合RFC,请在此处查看测试(jquery-csv.googlecode.com/git/test/test.html)。
Evan Plaice

6
打的好。+1使我开始使用PEG。我确实喜欢解析器生成器。“为什么要花五天时间手工编程,才能在五年的生命中实现自动化?”
-ANTLR的

14

csvToArray v1.3

紧凑(645字节)但兼容的功能,用于将CSV字符串转换为2D数组,符合RFC4180标准。

https://code.google.com/archive/p/csv-to-array/downloads

常用用法:jQuery

 $.ajax({
        url: "test.csv",
        dataType: 'text',
        cache: false
 }).done(function(csvAsString){
        csvAsArray=csvAsString.csvToArray();
 });

常用用法:Javascript

csvAsArray = csvAsString.csvToArray();

覆盖字段分隔符

csvAsArray = csvAsString.csvToArray("|");

覆盖记录分隔符

csvAsArray = csvAsString.csvToArray("", "#");

覆盖跳过标题

csvAsArray = csvAsString.csvToArray("", "", 1);

覆盖所有

csvAsArray = csvAsString.csvToArray("|", "#", 1);

这听起来很有趣,但是我现在找不到代码。您可以再次发布吗?
山姆·沃特金斯

1
我已经用当前链接更新了主要帖子。非常感谢。
dt192

3

我不确定为什么我不能选择kirtans。为我工作。它似乎在空字段或带有尾随逗号的字段上都失败了...

这似乎可以同时解决这两个问题。

我没有编写解析器代码,只是编写了解析器函数周围的包装器,以使该工作适用于文件。参见归因

    var Strings = {
        /**
         * Wrapped csv line parser
         * @param s string delimited csv string
         * @param sep separator override
         * @attribution : http://www.greywyvern.com/?post=258 (comments closed on blog :( )
         */
        parseCSV : function(s,sep) {
            // http://stackoverflow.com/questions/1155678/javascript-string-newline-character
            var universalNewline = /\r\n|\r|\n/g;
            var a = s.split(universalNewline);
            for(var i in a){
                for (var f = a[i].split(sep = sep || ","), x = f.length - 1, tl; x >= 0; x--) {
                    if (f[x].replace(/"\s+$/, '"').charAt(f[x].length - 1) == '"') {
                        if ((tl = f[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) == '"') {
                            f[x] = f[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"');
                          } else if (x) {
                        f.splice(x - 1, 2, [f[x - 1], f[x]].join(sep));
                      } else f = f.shift().split(sep).concat(f);
                    } else f[x].replace(/""/g, '"');
                  } a[i] = f;
        }
        return a;
        }
    }

1

正则表达式可以解救!这几行代码根据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' ] ]

您不需要解析器生成器,例如lex / yacc。由于使用正向后向查找,负向后向查找和正向提前查找,因此正则表达式可以正确处理RFC 4180。

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


正则表达式是使用有限状态机实现的,因此实际上您确实需要FSM。
Henry Henrinson

@HenryHenrinson:不一定。我要求您查找上述代码的问题。我在生产中使用它。也可以使用正则表达式进行更复杂的解析。您不需要LL解析器即可创建语法树。这是一个博客:如何使用正则表达式解析嵌套结构,twiki.org / cgi
bin /

@HenryHenrinson:哦,是的,请假装我,我们处于暴力协议中:-)
Peter Thoeny

-1

我已经构造了这个JavaScript脚本来将字符串中的CSV解析为数组对象。我发现最好将整个CSV分解为行,字段并进行相应处理。我认为这将使您轻松更改代码以适合您的需求。

希望对您有帮助。谢谢。

    //
    //
    // CSV to object
    //
    //

    const new_line_char = '\n';
    const field_separator_char = ',';

    function parse_csv(csv_str) {

        var result = [];

        let line_end_index_moved = false;
        let line_start_index = 0;
        let line_end_index = 0;
        let csr_index = 0;
        let cursor_val = csv_str[csr_index];
        let found_new_line_char = get_new_line_char(csv_str);
        let in_quote = false;

        // handle \r\n
        if (found_new_line_char == '\r\n') {
            csv_str = csv_str.split(found_new_line_char).join(new_line_char);
        }
        // handle last char is not \n
        if (csv_str[csv_str.length - 1] !== new_line_char) {
            csv_str += new_line_char;
        }

        while (csr_index < csv_str.length) {
            if (cursor_val === '"') {
                in_quote = !in_quote;
            } else if (cursor_val === new_line_char) {
                if (in_quote === false) {
                    if (line_end_index_moved && (line_start_index <= line_end_index)) {
                        result.push(parse_csv_line(csv_str.substring(line_start_index, line_end_index)));
                        line_start_index = csr_index + 1;
                    } // else: just ignore line_end_index has not moved or line has not been sliced for parsing the line
                } // else: just ignore because we are in quote
            }
            csr_index++;
            cursor_val = csv_str[csr_index];
            line_end_index = csr_index;
            line_end_index_moved = true;
        }

        // handle \r\n
        if (found_new_line_char == '\r\n') {
            let new_result = [];
            let curr_row;
            for (var i = 0; i < result.length; i++) {
                curr_row = [];
                for (var j = 0; j < result[i].length; j++) {
                    curr_row.push(result[i][j].split(new_line_char).join('\r\n'));
                }
                new_result.push(curr_row);
            }
            result = new_result;
        }

        return result;
    }

    function parse_csv_line(csv_line_str) {

        var result = [];

        // let field_end_index_moved = false;
        let field_start_index = 0;
        let field_end_index = 0;
        let csr_index = 0;
        let cursor_val = csv_line_str[csr_index];
        let in_quote = false;

        // Pretend that the last char is the separator_char to complete the loop
        csv_line_str += field_separator_char;

        while (csr_index < csv_line_str.length) {
            if (cursor_val === '"') {
                in_quote = !in_quote;
            } else if (cursor_val === field_separator_char) {
                if (in_quote === false) {
                    if (field_start_index <= field_end_index) {
                        result.push(parse_csv_field(csv_line_str.substring(field_start_index, field_end_index)));
                        field_start_index = csr_index + 1;
                    } // else: just ignore field_end_index has not moved or field has not been sliced for parsing the field
                } // else: just ignore because we are in quote
            }
            csr_index++;
            cursor_val = csv_line_str[csr_index];
            field_end_index = csr_index;
            field_end_index_moved = true;
        }

        return result;
    }

    function parse_csv_field(csv_field_str) {
        with_quote = (csv_field_str[0] === '"');

        if (with_quote) {
            csv_field_str = csv_field_str.substring(1, csv_field_str.length - 1); // remove the start and end quotes
            csv_field_str = csv_field_str.split('""').join('"'); // handle double quotes
        }

        return csv_field_str;
    }

    // initial method: check the first newline character only
    function get_new_line_char(csv_str) {
        if (csv_str.indexOf('\r\n') > -1) {
            return '\r\n';
        } else {
            return '\n'
        }
    }

-3

为什么不只使用.split(',')?

http://www.w3schools.com/jsref/jsref_split.asp

var str="How are you doing today?";
var n=str.split(" "); 

2
为什么这个答案不好?它是本机的,将字符串内容放入可行的数组中……
Micah 2012年

20
很多的原因。首先,它不会删除定界值上的双引号。不处理行拆分。不转义用于在定界值中转义的双引号。不允许为空值。等等,等等... CSV格式的灵活性使其非常易于使用,但难以解析。我不会对此投票,只是因为我不对竞争性答案投票。
伊万·普赖斯

1
如果遇到包含换行符的值怎么办?一个简单的split函数会错误地将其解释为条目的末尾,而不是像应有的那样跳过它。解析CSV比仅提供2个拆分例程(一个用于换行,一个用于分隔符)要复杂得多。
伊万·普赖斯

2
(续)也拆分为空值(a,null ,, value)不返回任何内容,而应返回一个空字符串。别误会,如果您100%肯定传入的数据不会破坏解析器,那么split是一个很好的开始,但是创建一个健壮的解析器以处理符合RFC 4801要求的任何数据将变得更加复杂。
伊万·普赖斯

8
埃文,我认为您的JavaScript库很棒。但是,这是另一种观点-我很欣赏这个答案,因为我只是以非常可预测的方式存储一系列数字。对我而言,要确保跨浏览器Javascript的兼容性和可维护性尽可能远地遥遥领先,这比包含一个大型(尽管编写良好且经过测试)的库要重要得多。不同的需求需要不同的方法。如果我需要真正的CSV功能,我一定会致力于使用您的库!:-)
moodboom
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.