[javascript] 데이터에 쉼표가 포함 된 JavaScript로 CSV 문자열을 구문 분석하려면 어떻게해야합니까?
다음 유형의 문자열이 있습니다.
var string = "'string, duppi, du', 23, lala"
각 쉼표에서 문자열을 배열로 나누고 싶지만 작은 따옴표 밖에있는 쉼표 만 있습니다.
분할에 적합한 정규식을 찾을 수 없습니다.
string.split(/,/)
나에게 줄 것이다
["'string", " duppi", " du'", " 23", " lala"]
그러나 결과는 다음과 같아야합니다.
["string, duppi, du", "23", "lala"]
크로스 브라우저 솔루션이 있습니까?
답변
부인 성명
2014-12-01 업데이트 : 아래 답변은 매우 구체적인 CSV 형식에만 적용됩니다. 의견에서 DG가 올바르게 지적했듯이이 솔루션은 CSV의 RFC 4180 정의에 맞지 않으며 MS Excel 형식에도 맞지 않습니다. 이 솔루션은 문자열에 이스케이프 된 따옴표와 쉼표가 포함될 수있는 문자열 유형의 혼합을 포함하는 하나의 (비표준) CSV 입력 행을 구문 분석하는 방법을 보여줍니다.
비표준 CSV 솔루션
austincheney가 올바르게 지적했듯이, 이스케이프 된 문자를 포함 할 수있는 따옴표로 묶인 문자열을 올바르게 처리하려면 문자열을 처음부터 끝까지 구문 분석해야합니다. 또한 OP는 “CSV 문자열”이 실제로 무엇인지 명확하게 정의하지 않습니다. 먼저 유효한 CSV 문자열과 개별 값을 구성하는 항목을 정의해야합니다.
주어진 : “CSV 문자열”정의
이 설명을 위해 “CSV 문자열”은 0 개 이상의 값으로 구성되며 여러 값은 쉼표로 구분됩니다. 각 값은 다음으로 구성 될 수 있습니다.
- 큰 따옴표로 묶인 문자열. (이스케이프 처리되지 않은 작은 따옴표를 포함 할 수 있습니다.)
- 작은 따옴표로 묶인 문자열. (이스케이프 처리되지 않은 큰 따옴표를 포함 할 수 있습니다.)
- 인용되지 않은 문자열입니다. (따옴표, 쉼표 또는 백 슬래시를 포함 할 수 없습니다.)
- 빈 값. (모두 공백 값은 비어있는 것으로 간주됩니다.)
규칙 / 참고 :
- 인용 된 값에는 쉼표가 포함될 수 있습니다.
- 인용 된 값은 이스케이프 된 모든 것을 포함 할 수 있습니다
'that\'s cool'
. - 따옴표, 쉼표 또는 백 슬래시가 포함 된 값은 따옴표로 묶어야합니다.
- 선행 또는 후행 공백이 포함 된 값은 따옴표로 묶어야합니다.
- 백 슬래시는 모두에서 제거됩니다 :
\'
작은 따옴표로 묶인 값. - 백 슬래시는 모두에서 제거됩니다 :
\"
큰 따옴표로 묶인 값. - 인용되지 않은 문자열은 선행 및 후행 공백이 제거됩니다.
- 쉼표 구분 기호에 인접한 공백이있을 수 있습니다 (무시 됨).
찾기:
유효한 CSV 문자열 (위에 정의 된대로)을 문자열 값의 배열로 변환하는 JavaScript 함수입니다.
해결책:
이 솔루션에서 사용하는 정규식은 복잡합니다. 그리고 (IMHO) 모든 중요하지 않은 정규식은 많은 주석과 들여 쓰기와 함께 자유 간격 모드로 표시되어야합니다. 안타깝게도 JavaScript는 자유 간격 모드를 허용하지 않습니다. 따라서이 솔루션에 의해 구현 된 정규식은 먼저 기본 정규식 구문으로 표시됩니다 (Python의 편리한 : r'''...'''
raw-multi-line-string 구문을 사용하여 표현됨 ).
먼저 여기에 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 문자열에서 하나의 값을 일치시킵니다. 더 이상 일치하는 항목이없고 모든 값이 구문 분석 될 때까지 반복적으로 적용됩니다.
유효한 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.
"""
이 정규식이 일치하지 않는 특별한 경우 값이 하나 있습니다. 해당 값이 비어있을 때 가장 마지막 값입니다. 이 특별한 “빈 마지막 값” 케이스는 뒤에 오는 js 함수에 의해 테스트되고 처리됩니다.
CSV 문자열을 구문 분석하는 JavaScript 함수 :
// 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;
};
입력 및 출력 예 :
다음 예에서는 중괄호를 사용하여 {result strings}
. (이것은 선행 / 후행 공백과 길이가 0 인 문자열을 시각화하는 데 도움이됩니다.)
// 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] = {} */
추가 참고 사항 :
이 솔루션을 사용하려면 CSV 문자열이 “유효”해야합니다. 예를 들어 따옴표가없는 값에는 백 슬래시 또는 따옴표가 포함될 수 없습니다. 예를 들어 다음 CSV 문자열은 유효하지 않습니다.
var invalid1 = "one, that's me!, escaped \, comma"
하위 문자열이 작은 따옴표 또는 큰 따옴표 값으로 표현 될 수 있기 때문에 이것은 실제로 제한이 아닙니다. 또한이 솔루션은 “쉼표로 구분 된 값”에 대한 하나의 가능한 정의 만 나타냅니다.
편집 : 2014-05-19 : 면책 조항 추가.
편집 : 2014-12-01 : 면책 조항을 맨 위로 이동했습니다.
답변
RFC 4180 솔루션
형식이 RFC 4180을 따르지 않기 때문에 문제의 문자열은 해결되지 않습니다. 허용되는 인코딩은 큰 따옴표를 사용하여 큰 따옴표를 이스케이프하는 것입니다. 아래 솔루션은 Google 스프레드 시트의 CSV 파일 d / l에서 올바르게 작동합니다.
업데이트 (2017 년 3 월)
한 줄을 구문 분석하는 것은 잘못되었습니다. 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);
답변
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 또는 https://pegjs.org/online 에서 테스트 하십시오 .
https://gist.github.com/3362830 에서 생성 된 파서를 다운로드합니다 .
답변
Google 스프레드 시트의 셀을 웹 앱으로 복사하려는 매우 구체적인 사용 사례가있었습니다. 셀에는 큰 따옴표와 개행 문자가 포함될 수 있습니다. 복사 및 붙여 넣기를 사용하면 셀이 탭 문자로 구분되고 홀수 데이터가있는 셀은 큰 따옴표로 묶입니다. 이 주요 솔루션, regexp를 사용하는 링크 된 기사, Jquery-CSV 및 CSVToArray를 시도했습니다. http://papaparse.com/ 바로 사용할 수있는 유일한 방법입니다. 기본 자동 감지 옵션이있는 Google 스프레드 시트를 사용하면 복사 및 붙여 넣기가 원활합니다.
답변
나는 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";
var parsed = string.splitCSV();
alert(parsed.join('|'));
답변
목록에 하나 더 추가하면 위의 모든 것이 충분히 “KISS”가 아니라는 것을 알기 때문입니다.
이것은 정규식을 사용하여 쉼표 또는 줄 바꿈을 찾고 인용 된 항목을 건너 뜁니다. 바라건대 이것은 누비들이 스스로 읽을 수있는 내용입니다. splitFinder
정규 표현식은 (바이 분할 않는 세 가지가 있습니다 |
)
,
-쉼표를 찾습니다.\r?\n
-새 줄을 찾습니다 (수출자가 좋은 경우 캐리지 리턴 포함)"(\\"|[^"])*?"
-쉼표와 줄 바꿈은 중요하지 않기 때문에 따옴표로 묶인 것은 건너 뜁니다.\\"
인용 된 항목에 이스케이프 된 인용문 이 있으면 끝 인용문을 찾기 전에 캡처됩니다.
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);