csv 형식을 정규식으로 정의 할 수 있습니까?


19

동료와 나는 최근에 순수 정규 표현식이 csv 형식을 완전히 캡슐화 할 수 있는지 여부를 주장하여 주어진 이스케이프 문자, 인용 문자 및 구분 문자로 모든 파일을 구문 분석 할 수 있습니다.

정규식은 생성 후 이러한 문자를 변경할 수 없지만 다른 경우에는 실패해서는 안됩니다.

나는 이것이 토크 나이저에게는 불가능하다고 주장했다. 이 작업을 수행 할 수있는 유일한 정규 표현식은 단순한 토큰 화를 넘어서는 매우 복잡한 PCRE 스타일입니다.

나는 다음 라인을 따라 무언가를 찾고있다.

... CSV 형식은 컨텍스트 프리 문법이므로 정규 표현식으로 구문 분석하는 것은 불가능합니다 ...

아니면 내가 틀렸어? POSIX 정규식으로 CSV를 구문 분석 할 수 있습니까?

예를 들어, 이스케이프 문자와 따옴표 문자가 모두 인 경우이 "두 줄은 유효한 csv입니다.

"""this is a test.""",""
"and he said,""What will be, will be."", to which I replied, ""Surely not!""","moving on to the next field here..."

어디에도 중첩이 없기 때문에 CSV가 아닙니다 (IIRC)
래칫 괴물

1
그러나 가장 중요한 경우는 무엇입니까? 어쩌면 내가 생각했던 것보다 더 많은 CSV가 있습니까?
c69

1
@ c69 탈출과 인용 문자는 모두 어떻습니까 ". 다음은 유효합니다 :"""this is a test.""",""
Spencer Rathbun

여기서 정규 표현식을 사용해 보셨습니까 ?
dasblinkenlight

1
에지 사례를 조심해야하지만 정규 표현식은 설명 한대로 CSV를 토큰 화 할 수 있어야합니다. 정규 표현식은 임의의 따옴표 수를 세지 않아도됩니다. 정규 표현식으로 할 수있는 3까지만 세면됩니다. 다른 언급으로, 당신은 ... 당신이되고 토큰 CSV를 기대하는 잘 정의 된 표현을 적어 시도해야
comingstorm

답변:


20

이론적으로 훌륭하고 실제로 끔찍한

하여 CSV 나는에 설명 된대로 규칙을 의미하는 가정거야 RFC 4180 .

기본 CSV 데이터를 일치시키는 것은 쉽지 않습니다.

"data", "more data"

참고 : BTW, 매우 간단하고 체계적인 데이터에 .split ( '/ n'). split ( ' "') 함수를 사용하는 것이 훨씬 효율적입니다. 정규 표현식은 NDFSM (비 결정적 유한)으로 작동합니다. 이스케이프 문자와 같은 엣지 케이스를 추가하기 시작하면 많은 시간을 낭비하는 State Machine).

예를 들어 다음은 내가 찾은 가장 포괄적 인 정규식 일치 문자열입니다.

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

작은 따옴표와 큰 따옴표 값을 합리적으로 처리하지만 값의 줄 바꿈, 이스케이프 따옴표 등은 처리하지 않습니다.

출처 : Stack Overflow-JavaScript로 문자열을 구문 분석하는 방법

일반적인 엣지 케이스가 소개되면 악몽이됩니다 ...

"such as ""escaped""","data"
"values that contain /n newline chars",""
"escaped, commas, like",",these"
"un-delimited data like", this
"","empty values"
"empty trailing values",        // <- this is completely valid
                                // <- trailing newline, may or may not be included

줄 바꿈 값 엣지 케이스만으로도 야생에서 발견되는 RegEx 기반 파서의 99.9999 %를 깨뜨리기에 충분합니다. 유일한 '합리적인'대안은 더 높은 수준의 분석에 사용되는 상태 기계와 쌍을 이루는 기본 제어 / 비 제어 문자 (예 : 터미널 대 비 터미널) 토큰 화에 RegEx 일치를 사용하는 것입니다.

출처 : 광범위한 고통과 고통으로 알려진 경험.

저는 세계에서 유일하게 자바 스크립트 기반의 완전한 RFC 호환 CSV 파서 인 jquery-CSV 의 저자입니다 . 나는이 문제를 해결하고 많은 지능적인 사람들과 이야기하고 핵심 파서 엔진을 3 번 완전히 다시 작성하는 것을 포함하여 다른 구현을 시도하는 데 몇 달을 보냈습니다.

tl; dr-이야기의 도덕, PCRE만으로는 가장 간단하고 엄격한 정규 (I-III) 문법 이외의 것을 파싱하는 데 짜증이납니다. 그럼에도 불구하고 터미널 및 비 터미널 문자열을 토큰 화하는 데 유용합니다.


1
네, 저의 경험이기도합니다. 매우 간단한 CSV 패턴 이상을 완전히 캡슐화하려는 시도는 이러한 것들에 영향을 미치며, 대규모 정규 표현식의 효율성 문제와 복잡성 문제에 부딪치게됩니다. node-csv 라이브러리 를 살펴 보셨습니까 ? 이 이론도 검증하는 것 같습니다. 모든 사소한 구현은 내부적으로 파서를 사용합니다.
Spencer Rathbun

@SpencerRathbun Yep. 이전에 node-csv 소스를 살펴 보았습니다. 처리를 위해 일반적인 문자 토큰 화 상태 시스템을 사용하는 것으로 보입니다. jquery-csv 파서는 터미널 / 비 터미널 토큰 화에 정규식을 사용하는 것을 제외하고는 동일한 기본 개념에서 작동합니다. 문자 단위로 평가하고 연결하는 대신 정규식은 한 번에 여러 개의 비단 말 문자를 일치시켜 그룹 (예 : 문자열)으로 반환 할 수 있습니다. 이는 불필요한 연결을 최소화하고 효율성을 높여야합니다.
Evan Plaice

20

정규 표현식은 정규 언어를 구문 분석 할 수 있으며 재귀 문법과 같은 멋진 것을 구문 분석 할 수 없습니다. 그러나 CSV는 매우 규칙적인 것으로 보이므로 정규식으로 구문 분석 할 수 있습니다.

정의 부터 작업합시다 : 시퀀스, 선택 형식 대안 ( |) 및 반복 (클린 스타, *)이 허용됩니다.

  • 따옴표없는 값은 규칙적입니다 : [^,]*# 쉼표를 제외한 모든 문자
  • 인용 된 값은 규칙적입니다 : "([^\"]|\\\\|\\")*"# 인용 "또는 이스케이프 된 인용 \"또는 이스케이프 된 이스케이프 이외의 순서\\
    • 일부 형식은 따옴표로 따옴표를 이스케이프 처리 ("")*"하여 위 식에 변형 을 추가 할 수 있습니다 .
  • 허용되는 값은 일반입니다 : |<quoted-value> <quoted-value>
  • 단일 CSV 줄은 규칙적입니다 : <value> (,<value>)*
  • 구분 된 일련의 줄 \n도 분명히 규칙적입니다.

나는이 표현들 각각을 세 심하게 테스트하지 않았으며 캐치 그룹을 정의하지 않았다. 나는 또한 대신에 사용할 수있는 문자의 변종과 같은 몇 가지 교칙을 통해 호도 ,, "또는 라인 분리기 :이 방금 여러 약간 다른 언어를 얻을, 규칙 성을 아프게하지 않습니다.

이 증거에서 문제를 발견 할 수 있으면 의견을 말하십시오! :)

그러나 그럼에도 불구하고 순수한 정규식으로 CSV 파일을 실제로 구문 분석하는 것은 문제가 될 수 있습니다. 파서에 어떤 변형이 공급되는지 알아야하며 이에 대한 표준은 없습니다. 성공할 때까지 각 행에 대해 여러 구문 분석기를 시도하거나 형식 양식 주석을 구분할 수 있습니다. 그러나이를 위해서는 정규 표현식 이외의 다른 방법으로도 효율적으로 작업하거나 전혀 수행하지 않아도됩니다.


4
실질적인 포인트는 +1입니다. 어딘가에 깊은 곳은 인용 된 값 버전을 깨뜨릴 수있는 (contrived) 값의 예입니다. 다중 파서가있는 '재미'는 "이 두 가지 작업이지만 다른 답변을 제공합니다"

1
백 슬래시 이스케이프 따옴표와 큰 따옴표 이스케이프 따옴표에는 다른 정규식이 필요합니다. 이전 유형의 csv 필드에 대한 정규식은 다음과 [^,"]*|"(\\(\\|")|[^\\"])*"같아야하며 후자는 다음과 같아야 [^,"]*|"(""|[^"])*"합니다. (내가이 중 하나를 테스트하지 않았으므로 조심하십시오!)
오는 폭풍

사냥 뭔가 동봉 된 레코드 구분 기호와 값 - 표준 수 있습니다, 누락되는 경우가 있습니다. 또한이를 처리하는 여러 가지 다른 방법이있을 때 실제 구문 분석이 훨씬 재미있어집니다.

좋은 대답이지만, 실행 perl -pi -e 's/"([^\"]|\\\\|\\")*"/yay/'하고 파이프 "I have here an item,\" that is a test\""를 연결하면 결과는`yay that is a test \ ""입니다. 정규식에 결함이 있습니다.
스펜서 Rathbun

@SpencerRathbun : 더 많은 시간이 있으면 실제로 정규 표현식을 테스트하고 테스트를 통과 한 개념 증명 코드를 붙여 넣을 수도 있습니다. 죄송합니다. 근무일이 진행됩니다.
9000

5

간단한 대답-아마 아닐 것입니다.

첫 번째 문제는 표준이 없다는 것입니다. 엄격하게 정의 된 방식으로 csv를 설명 할 수는 있지만 엄격하게 정의 된 csv 파일을 얻을 수는 없습니다. "당신이하는 일에 보수적이며, 다른 사람들로부터 받아 들여지는 것에 자유주의하십시오"-Jon Postal

수용 가능한 표준이 있다고 가정하면, 이스케이프 문자에 대한 질문과 균형이 필요한지에 대한 질문이 있습니다.

많은 CSV 형식의 문자열은로 정의됩니다 string value 1,string value 2. 그러나 해당 문자열에 쉼표가 있으면 now "string, value 1",string value 2입니다. 인용 부호가 포함되어 있으면이됩니다 "string, ""value 1""",string value 2.

이 시점에서 나는 불가능하다고 생각합니다. 문제는 읽은 따옴표 수와 쉼표가 큰 따옴표로 묶은 값 모드의 내부 또는 외부에 있는지 확인해야한다는 것입니다. 괄호를 밸런싱하는 것은 불가능한 정규식 문제입니다. 일부 확장 정규 표현식 엔진 (PCRE)이이를 처리 할 수 ​​있지만 정규 표현식이 아닙니다.

/programming/8629763/csv-parsing-with-a-context-free-grammar가 유용 할 수 있습니다 .


수정 :

이스케이프 문자의 형식을 살펴본 결과 임의의 계산이 필요한 것을 찾지 못했습니다. 아마도 문제가 아닙니다.

그러나 이스케이프 문자 및 레코드 구분 기호 (시작)에 대한 문제가 있습니다. http://www.csvreader.com/csv_format.php 는 다양한 형식에 대한 정보를 제공합니다.

  • 따옴표로 묶은 문자열 (작은 따옴표로 묶은 문자열 또는 큰 따옴표로 묶인 문자열 인 경우)에 대한 규칙이 다릅니다.
    • 'This, is a value' vs "This, is a value"
  • 이스케이프 문자 규칙
    • "This ""is a value""" vs "This \"is a value\""
  • 임베디드 레코드 분리 문자 처리 ({rd})
    • (원시 포함) "This {rd}is a value"vs (이스케이프 된) "This \{rd}is a value"vs (번역 된)"This {0x1C}is a value"

여기서 중요한 것은 항상 여러 개의 유효한 해석을 갖는 문자열을 가질 수 있다는 것입니다.

관련 질문 (가장 큰 경우) "잘못된 문자열을 허용 할 수 있습니까?"

일부 응용 프로그램에서 만든 유효한 모든 CSV와 일치하고 구문 분석 할 수없는 모든 CSV를 거부 할 수있는 정규 표현식이 있는지 의심합니다.


1
따옴표 안의 따옴표는 균형을 맞출 필요가 없습니다. 대신 포함 된 따옴표 앞에 짝수의 따옴표가 있어야합니다 ("")*". 값 내부 의 따옴표 가 균형이 맞지 않으면 이미 비즈니스가 아닙니다.
9000

이것은 과거에 "데이터 전송"에 대한 이러한 끔찍한 변명을 겪은 나의 입장이다. 제대로 처리 한 유일한 것은 파서 였고 순수한 정규 표현식은 몇 주마다 끊어졌습니다.
스펜서 Rathbun

2

먼저 CSV에 대한 문법을 ​​정의하고 (필드 구분 기호가 텍스트에 나타나는 경우 어떻게 든 이스케이프되거나 인코딩됩니까?) 정규식으로 구문 분석 할 수 있는지 판별 할 수 있습니다. 문법 우선 : 파서 ​​두 번째 : http://www.boyet.com/articles/csvparser.html 이 방법은 토크 나이저를 사용한다는 점에 유의해야합니다. 그러나 모든 에지와 일치하는 POSIX 정규식을 구성 할 수는 없습니다. CSV 형식을 사용하는 것이 규칙적이지 않고 컨텍스트가없는 경우 ...에 대한 답변이 귀하의 질문에 있습니다. 좋은 개요 : http://nikic.github.com/2012/06/15/The-true-power-of-regular-expressions.html


2

이 정규 표현식은 RFC에 설명 된대로 일반 CSV를 토큰화할 수 있습니다.

/("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/

설명:

  • ("(?:[^"]|"")*"|[^,"\n\r]*) -인용 여부에 관계없이 CSV 필드
    • "(?:[^"]|"")*" -인용 필드;
      • [^"]|""- 각 캐릭터는 하나하지 ", 또는 "로 이스케이프""
    • [^,"\n\r]* -인용되지 않은 필드 (포함되지 않을 수 있음) , " \n \r
  • (,|\r?\n|\r)-다음 구분 기호 ,또는 개행
    • \r?\n|\r -개행 \r\n \n \r

이 정규식을 반복해서 사용하여 전체 CSV 파일을 일치시키고 유효성을 검증 할 수 있습니다. 그런 다음 인용 된 필드를 수정하고 구분 기호를 기준으로 행으로 분할해야합니다.

다음은 정규 표현식을 기반으로 Javascript의 CSV 파서 코드입니다.

var csv_tokens_rx = /("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/y;
var csv_unescape_quote_rx = /""/g;
function csv_parse(s) {
    if (s && s.slice(-1) != '\n')
        s += '\n';
    var ok;
    var rows = [];
    var row = [];
    csv_tokens_rx.lastIndex = 0;
    while (true) {
        ok = csv_tokens_rx.lastIndex == s.length;
        var m = s.match(csv_tokens_rx);
        if (!m)
            break;
        var v = m[1], d = m[2];
        if (v[0] == '"') {
            v = v.slice(1, -1);
            v = v.replace(csv_unescape_quote_rx, '"');
        }
        if (d == ',' || v)
            row.push(v);
        if (d != ',') {
            rows.push(row)
            row = [];
        }
    }
    return ok ? rows : null;
}

이 답변이 논쟁을 해결하는 데 도움이되는지 여부는 귀하가 결정하는 것입니다. 작고 간단하며 올바른 CSV 파서를 보유하게되어 기쁩니다.

제 생각에 lex프로그램은 다소 큰 정규 표현식이며 C 프로그래밍 언어와 같이 훨씬 더 복잡한 형식을 토큰 화 할 수 있습니다.

RFC 4180 정의를 참조하십시오 .

  1. 줄 바꿈 (CRLF)-정규식이보다 유연하여 CRLF, LF 또는 CR을 허용합니다.
  2. 파일의 마지막 레코드는 줄 바꿈이 있거나 없을 수 있습니다-정규 표현식에는 줄 바꿈이 필요하지만 파서는 그에 맞게 조정됩니다.
  3. 선택적인 헤더 행이있을 수 있습니다. 이것은 파서에 영향을 미치지 않습니다.
  4. 각 행은 파일 전체에서 동일한 수의 필드를 포함해야합니다. 강제 적용되지 않는
    공백은 필드의 일부로 간주되며 무시해서는 안됩니다.-
    레코드의 마지막 필드 뒤에 쉼표가 없어야합니다.
  5. 각 필드는 큰 따옴표로 묶거나 묶지 않을 수 있습니다 ...-좋습니다
  6. 줄 바꿈 (CRLF), 큰 따옴표 및 쉼표가 포함 된 필드는 큰 따옴표로 묶어야합니다.
  7. 필드 안에 나타나는 큰 따옴표는 다른 큰 따옴표로 시작하여 이스케이프 처리해야합니다.

정규 표현식 자체는 대부분의 RFC 4180 요구 사항을 충족합니다. 나는 다른 사람들과 동의하지 않지만 파서를 조정하여 쉽게 구현할 수 있습니다.


1
이것은 질문을 제기하는 것보다 자기 홍보처럼 보입니다. 답변하는 방법
gnat

1
@ gnat, 나는 더 많은 설명을하고 RFC 4180에 대한 정규 표현식을 확인하고 자체 홍보를 줄 이도록 답변을 편집했습니다. 이 답변에는 Excel과 다른 스프레드 시트에서 사용되는 가장 일반적인 CSV 형식을 토큰 화 할 수있는 테스트 된 정규 표현식이 포함되어 있으므로 가치가 있다고 생각합니다. 이것이 문제를 해결한다고 생각합니다. 작은 CSV 파서는이 정규 표현식을 사용하여 CSV를 쉽게 구문 분석 할 수 있음을 보여줍니다.
Sam Watkins

나 자신을 과도하게 홍보하고 싶지는 않지만 작은 스프레드 시트 앱 (Google 시트가 너무 무겁습니다)의 일부로 사용하고있는 완전한 작은 csv 및 tsv 라이브러리가 있습니다. 이것은 내가 게시 한 모든 것들과 같은 오픈 소스 / 퍼블릭 도메인 / CC0 코드입니다. 나는 이것이 다른 누군가에게 유용 할 수 있기를 바랍니다. sam.aiki.info/code/js
Sam Watkins
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.