[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 개 이상의 값으로 구성되며 여러 값은 쉼표로 구분됩니다. 각 값은 다음으로 구성 될 수 있습니다.

  1. 큰 따옴표로 묶인 문자열. (이스케이프 처리되지 않은 작은 따옴표를 포함 할 수 있습니다.)
  2. 작은 따옴표로 묶인 문자열. (이스케이프 처리되지 않은 큰 따옴표를 포함 할 수 있습니다.)
  3. 인용되지 않은 문자열입니다. (따옴표, 쉼표 또는 백 슬래시를 포함 할 수 없습니다.)
  4. 빈 값. (모두 공백 값은 비어있는 것으로 간주됩니다.)

규칙 / 참고 :

  • 인용 된 값에는 쉼표가 포함될 수 있습니다.
  • 인용 된 값은 이스케이프 된 모든 것을 포함 할 수 있습니다 '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정규 표현식은 (바이 분할 않는 세 가지가 있습니다 |)

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