Định dạng csv có thể được xác định bởi regex không?


19

Một đồng nghiệp và tôi gần đây đã tranh luận về việc một regex thuần có khả năng đóng gói hoàn toàn định dạng csv hay không, sao cho nó có khả năng phân tích tất cả các tệp với bất kỳ char thoát nào, trích dẫn char và char phân cách.

Regex không cần phải có khả năng thay đổi các ký tự này sau khi tạo, nhưng nó không được thất bại trong bất kỳ trường hợp cạnh nào khác.

Tôi đã lập luận rằng điều này là không thể đối với chỉ một mã thông báo. Regex duy nhất có thể có thể làm điều này là một kiểu PCRE rất phức tạp, vượt ra ngoài việc token hóa.

Tôi đang tìm kiếm một cái gì đó dọc theo dòng:

... định dạng csv là một ngữ pháp miễn phí ngữ cảnh và do đó, không thể phân tích cú pháp bằng regex một mình ...

Hoặc là tôi sai? Có thể phân tích csv chỉ với regex POSIX không?

Ví dụ: nếu cả char thoát và char trích dẫn là ", thì hai dòng này là csv hợp lệ:

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

nó không phải là CSV vì không có lồng nhau ở bất cứ đâu (IIRC)
ratchet freak

1
nhưng các trường hợp cạnh là gì? có lẽ có nhiều hơn trong CSV, hơn tôi từng nghĩ?
c69

1
@ c69 Làm thế nào về thoát và trích dẫn char là cả hai ". Sau đó, những điều sau đây là hợp lệ:"""this is a test.""",""
Spencer Rathbun

Bạn đã thử regrec từ đây ?
dasblinkenlight

1
Bạn cần phải coi chừng các trường hợp cạnh, nhưng một regex sẽ có thể token hóa csv như bạn đã mô tả nó. Regex không cần đếm số lượng trích dẫn tùy ý - nó chỉ cần đếm đến 3, điều mà các biểu thức thông thường có thể làm. Như những người khác đã đề cập, bạn nên cố gắng viết ra một đại diện được xác định rõ ràng về những gì bạn mong đợi mã thông báo csv sẽ ...
sắp diễn ra vào

Câu trả lời:


20

Đẹp về lý thuyết, kinh khủng trong thực tế

Theo CSV tôi sẽ giả sử bạn có nghĩa là quy ước như được mô tả trong RFC 4180 .

Mặc dù khớp dữ liệu CSV cơ bản là không đáng kể:

"data", "more data"

Lưu ý: BTW, sử dụng hàm .split ('/ n'). Split ('"') hiệu quả hơn cho dữ liệu rất đơn giản và có cấu trúc tốt như thế này. Biểu thức chính quy hoạt động như một NDFSM (Không xác định hữu hạn Máy trạng thái) gây lãng phí rất nhiều thời gian quay lại một khi bạn bắt đầu thêm các trường hợp cạnh như ký tự thoát.

Ví dụ: đây là chuỗi kết hợp biểu thức chính quy toàn diện nhất mà tôi đã tìm thấy:

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

Nó xử lý hợp lý các giá trị được trích dẫn đơn và kép, nhưng không phải là dòng mới trong các giá trị, dấu ngoặc kép đã thoát, v.v.

Nguồn: Stack Overflow - Làm cách nào tôi có thể phân tích chuỗi bằng JavaScript

Nó trở thành một cơn ác mộng một khi các trường hợp cạnh phổ biến được giới thiệu như ...

"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

Chỉ riêng trường hợp cạnh giá trị dòng mới là đủ để phá vỡ 99.9999% trình phân tích cú pháp dựa trên RegEx được tìm thấy trong tự nhiên. Cách thay thế 'hợp lý' duy nhất là sử dụng kết hợp RegEx cho mã thông báo điều khiển / không điều khiển cơ bản (nghĩa là đầu cuối so với đầu cuối không đầu cuối) được ghép nối với một máy trạng thái được sử dụng để phân tích cấp cao hơn.

Nguồn: Kinh nghiệm còn được gọi là đau khổ và đau khổ.

Tôi là tác giả của jquery-CSV , trình phân tích cú pháp CSV dựa trên javascript, hoàn toàn tuân thủ RFC trên thế giới. Tôi đã dành nhiều tháng để giải quyết vấn đề này, nói chuyện với nhiều người thông minh và thử một tấn nếu các triển khai khác nhau bao gồm 3 bản viết lại đầy đủ của công cụ phân tích cú pháp lõi.

tl; dr - Đạo đức của câu chuyện, PCRE một mình hút để phân tích bất cứ điều gì ngoại trừ các ngữ pháp thông thường (Ie Type-III) đơn giản và nghiêm ngặt nhất. Mặc dù, nó hữu ích cho việc token hóa chuỗi đầu cuối và chuỗi không đầu cuối.


1
Yup, đó cũng là kinh nghiệm của tôi. Bất kỳ nỗ lực nào để đóng gói đầy đủ hơn một mẫu CSV rất đơn giản đều gặp phải những điều này, và sau đó bạn sẽ chống lại cả các vấn đề hiệu quả và các vấn đề phức tạp của một biểu thức lớn. Bạn đã xem thư viện nút-csv chưa? Nó dường như để xác nhận lý thuyết này là tốt. Mỗi triển khai không tầm thường sử dụng một trình phân tích cú pháp bên trong.
Spencer Rathbun

@SpencerRathbun Yep. Tôi chắc chắn rằng tôi đã xem qua nguồn nút-csv trước đây. Nó xuất hiện để sử dụng một máy trạng thái mã thông báo ký tự điển hình để xử lý. Trình phân tích cú pháp jquery-csv hoạt động trên cùng một khái niệm cơ bản ngoại trừ tôi sử dụng regex cho mã thông báo đầu cuối / không đầu cuối. Thay vì đánh giá và ghép nối trên cơ sở char-by-char, regex có thể khớp nhiều ký tự không đầu cuối cùng một lúc và trả chúng thành một nhóm (tức là chuỗi). Điều này giảm thiểu sự kết hợp không cần thiết và 'nên' tăng hiệu quả.
Evan Plaice

20

Regex có thể phân tích bất kỳ ngôn ngữ thông thường nào và không thể phân tích những thứ ưa thích như ngữ pháp đệ quy. Nhưng CSV dường như khá thường xuyên, do đó có thể phân tích cú pháp bằng biểu thức chính quy.

Chúng ta hãy làm việc từ định nghĩa : được phép là chuỗi, lựa chọn thay thế ( |) và lặp lại (ngôi sao Kleene, *).

  • Một giá trị không được trích dẫn là thường xuyên: [^,]*# bất kỳ char nhưng dấu phẩy
  • Một giá trị được trích dẫn là thường xuyên: "([^\"]|\\\\|\\")*"# chuỗi của bất cứ điều gì trừ trích dẫn "hoặc thoát trích dẫn \"hoặc thoát thoát\\
    • Một số hình thức có thể bao gồm thoát dấu ngoặc kép với dấu ngoặc kép, thêm một biến thể ("")*"cho biểu thức ở trên.
  • Giá trị được phép là thường xuyên: <unquote-value> |<quote-value>
  • Một dòng CSV duy nhất là thường xuyên: <value> (,<value>)*
  • Một chuỗi các dòng cách nhau \nrõ ràng là thường xuyên.

Tôi đã không kiểm tra tỉ mỉ từng biểu thức này và không bao giờ xác định các nhóm bắt. Tôi cũng che đậy một số vấn đề chuyên môn, như các biến thể của các nhân vật có thể được sử dụng thay cho ,, "hoặc dòng tách: những không phá vỡ các quy luật, bạn chỉ nhận được một số ngôn ngữ hơi khác nhau.

Nếu bạn có thể phát hiện ra một vấn đề trong bằng chứng này, xin vui lòng bình luận! :)

Nhưng mặc dù vậy, phân tích thực tế các tệp CSV bằng các biểu thức thuần túy thông thường có thể có vấn đề. Bạn cần biết biến thể nào đang được cung cấp cho trình phân tích cú pháp và không có tiêu chuẩn nào cho nó. Bạn có thể thử một số trình phân tích cú pháp đối với từng dòng cho đến khi một dòng thành công hoặc bằng cách nào đó phân tách các nhận xét mẫu định dạng. Nhưng điều này có thể yêu cầu các phương tiện khác ngoài các biểu thức thông thường để thực hiện một cách hiệu quả, hoặc tất cả.


4
Hoàn toàn là +1 cho điểm thực tế. Có một cái gì đó mà tôi chắc chắn, ở đâu đó sâu thẳm là một ví dụ về giá trị (bị tước đoạt) sẽ phá vỡ phiên bản giá trị được trích dẫn mà tôi không biết nó là gì. 'Trò vui' với nhiều trình phân tích cú pháp sẽ là "hai công việc này, nhưng đưa ra các câu trả lời khác nhau"

1
Rõ ràng là bạn sẽ cần các biểu thức khác nhau cho dấu gạch chéo ngược-thoát-trích dẫn so với dấu ngoặc kép-thoát-trích dẫn. Một regex cho loại trường csv trước đây phải là một cái gì đó như thế [^,"]*|"(\\(\\|")|[^\\"])*", và cái sau sẽ là một cái gì đó như thế [^,"]*|"(""|[^"])*". (Hãy coi chừng, như tôi đã không kiểm tra một trong những!)
comingstorm

Săn lùng một cái gì đó có thể là một tiêu chuẩn, có một trường hợp bị bỏ lỡ - một giá trị với một dấu phân cách kỷ lục kèm theo. Điều này cũng làm cho việc phân tích cú pháp thực tế trở nên thú vị hơn khi có nhiều cách khác nhau để xử lý việc đó

Câu trả lời hay, nhưng nếu tôi chạy perl -pi -e 's/"([^\"]|\\\\|\\")*"/yay/'và dẫn vào "I have here an item,\" that is a test\""thì kết quả là 'yay đó là một bài kiểm tra \ "". Methinks regex của bạn là thiếu sót.
Spencer Rathbun

@SpencerRathbun: khi tôi có nhiều thời gian hơn, tôi sẽ thực sự kiểm tra các biểu thức chính quy và thậm chí có thể dán một số mã bằng chứng khái niệm vượt qua các bài kiểm tra. Xin lỗi, ngày làm việc đang diễn ra.
9000

5

Câu trả lời đơn giản - có lẽ là không.

Vấn đề đầu tiên là thiếu một tiêu chuẩn. Mặc dù người ta có thể mô tả csv của họ theo cách được xác định nghiêm ngặt, nhưng người ta không thể mong đợi có được các tệp csv được xác định nghiêm ngặt. "Hãy thận trọng trong những gì bạn làm, hãy tự do trong những gì bạn chấp nhận từ người khác" -Jon Postal

Giả sử rằng người ta có một tiêu chuẩn có thể chấp nhận được, có câu hỏi về các ký tự thoát và nếu những điều này cần phải được cân bằng.

Một chuỗi trong nhiều định dạng csv được định nghĩa là string value 1,string value 2. Tuy nhiên, nếu chuỗi đó chứa dấu phẩy thì bây giờ "string, value 1",string value 2. Nếu nó chứa một trích dẫn nó trở thành "string, ""value 1""",string value 2.

Tại thời điểm này tôi tin rằng nó là không thể. Vấn đề là bạn cần xác định có bao nhiêu trích dẫn bạn đã đọc và nếu dấu phẩy ở bên trong hoặc bên ngoài chế độ trích dẫn kép của giá trị. Cân bằng dấu ngoặc đơn là một vấn đề regex không thể. Một số công cụ biểu thức chính quy mở rộng (PCRE) có thể xử lý nó, nhưng sau đó nó không phải là biểu thức chính quy.

Bạn có thể thấy /programming/8629763/csv-parsing-with-a-context-free-grammar hữu ích.


Sửa đổi:

Tôi đã xem xét các định dạng cho các ký tự thoát và không tìm thấy bất kỳ thứ gì cần đếm tùy ý - vì vậy đó có lẽ không phải là vấn đề.

Tuy nhiên, có vấn đề về ký tự thoát và dấu phân cách bản ghi (để bắt đầu) là gì. http://www.csvreader.com/csv_format.php là một cách đọc tốt trên các định dạng khác nhau trong tự nhiên.

  • Các quy tắc cho chuỗi trích dẫn (nếu đó là một chuỗi trích dẫn đơn hoặc một chuỗi trích dẫn kép) khác nhau.
    • 'This, is a value' đấu với "This, is a value"
  • Các quy tắc cho nhân vật thoát
    • "This ""is a value""" đấu với "This \"is a value\""
  • Việc xử lý dấu phân cách bản ghi được nhúng ({rd})
    • (nhúng nhúng) "This {rd}is a value"vs (thoát) "This \{rd}is a value"vs (dịch)"This {0x1C}is a value"

Điều quan trọng ở đây là có thể có một chuỗi sẽ luôn có nhiều cách hiểu hợp lệ.

Câu hỏi liên quan (đối với các trường hợp cạnh) "có thể có một chuỗi không hợp lệ được chấp nhận không?"

Tôi vẫn nghi ngờ rằng có một biểu thức chính quy có thể khớp với mọi CSV hợp lệ được tạo bởi một số ứng dụng và từ chối mọi csv không thể phân tích cú pháp.


1
Báo giá bên trong trích dẫn không cần phải được cân bằng. Thay vào đó, phải có một số lượng trích dẫn chẵn trước khi trích dẫn được nhúng, điều này rõ ràng là thường xuyên : ("")*". Nếu các trích dẫn bên trong giá trị mất cân bằng, thì đó không phải là việc của chúng tôi.
9000

Đây là vị trí của tôi, đã gặp phải những lý do khủng khiếp cho "truyền dữ liệu" trong quá khứ. Điều duy nhất xử lý chúng đúng cách là một trình phân tích cú pháp, regex thuần túy đã phá vỡ cứ sau vài tuần.
Spencer Rathbun

2

Trước tiên, hãy xác định ngữ pháp cho CSV của bạn (các dấu phân cách trường được thoát hoặc được mã hóa bằng cách nào đó nếu chúng xuất hiện trong văn bản?) Và sau đó có thể được xác định nếu nó có thể phân tích cú pháp bằng regex. Ngữ pháp thứ nhất: trình phân tích cú pháp thứ hai: http://www.boyet.com/articles/csvparser.html Cần lưu ý rằng phương pháp này sử dụng mã thông báo - nhưng tôi không thể tạo ra một biểu thức chính thức POSIX phù hợp với tất cả các trường hợp cạnh. Nếu việc sử dụng định dạng CSV của bạn không thường xuyên và không có ngữ cảnh ... thì câu trả lời của bạn nằm trong câu hỏi của bạn. Tổng quan tốt ở đây: http://nikic.github.com/2012/06/15/The-true-power-of-THER-expressions.html


2

Regrec này có thể mã hóa CSV bình thường, như được mô tả trong RFC:

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

Giải trình:

  • ("(?:[^"]|"")*"|[^,"\n\r]*) - trường CSV, được trích dẫn hoặc không
    • "(?:[^"]|"")*" - một trường trích dẫn;
      • [^"]|""- mỗi nhân vật không "hoặc "thoát ra như""
    • [^,"\n\r]* - một trường không trích dẫn, có thể không chứa , " \n \r
  • (,|\r?\n|\r)- dấu phân cách sau, ,hoặc một dòng mới
    • \r?\n|\r - một dòng mới, một trong những \r\n \n \r

Toàn bộ tệp CSV có thể được khớp và xác thực bằng cách sử dụng biểu thức chính quy này nhiều lần. Sau đó, cần phải sửa các trường được trích dẫn và chia nó thành các hàng dựa trên các dấu phân cách.

Đây là mã cho trình phân tích cú pháp CSV trong Javascript, dựa trên biểu thức chính quy:

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

Liệu câu trả lời này có giúp giải quyết tranh luận của bạn hay không là để bạn quyết định; Tôi rất vui khi có một trình phân tích cú pháp CSV nhỏ, đơn giản và chính xác.

Theo tôi, một lexchương trình ít nhiều là một biểu thức chính quy lớn và chúng có thể token hóa các định dạng phức tạp hơn nhiều, chẳng hạn như ngôn ngữ lập trình C.

Với tham chiếu đến các định nghĩa RFC 4180 :

  1. ngắt dòng (CRLF) - regrec linh hoạt hơn, cho phép CRLF, LF hoặc CR.
  2. Bản ghi cuối cùng trong tệp có thể có hoặc không có ngắt dòng kết thúc - regrec vì nó yêu cầu ngắt dòng cuối cùng, nhưng trình phân tích cú pháp điều chỉnh cho điều đó.
  3. Có thể có một dòng tiêu đề tùy chọn - Điều này không ảnh hưởng đến trình phân tích cú pháp.
  4. Mỗi dòng phải chứa cùng một số trường trong toàn bộ tệp - Không được thực thi
    Spaces được coi là một phần của trường và không được bỏ qua - okay
    Trường cuối cùng trong bản ghi không được theo dấu phẩy - không được thi hành
  5. Mỗi lĩnh vực có thể hoặc không được đặt trong dấu ngoặc kép ... - được
  6. Các trường có chứa dấu ngắt dòng (CRLF), dấu ngoặc kép và dấu phẩy nên được đặt trong dấu ngoặc kép - được
  7. một trích dẫn kép xuất hiện bên trong một trường phải được thoát bằng cách đặt trước nó bằng một trích dẫn kép khác - được

Bản thân regrec đáp ứng hầu hết các yêu cầu của RFC 4180. Tôi không đồng ý với những người khác, nhưng thật dễ dàng để điều chỉnh trình phân tích cú pháp để thực hiện chúng.


1
điều này trông giống như tự quảng cáo hơn là giải quyết câu hỏi đã hỏi, xem Cách trả lời
gnat

1
@gnat, tôi đã chỉnh sửa câu trả lời của mình để đưa ra lời giải thích thêm, kiểm tra biểu thức chính quy với RFC 4180 và để làm cho nó ít tự quảng cáo hơn. Tôi tin rằng câu trả lời này có giá trị, vì nó chứa một biểu thức chính quy được thử nghiệm có thể mã hóa hình thức CSV phổ biến nhất được sử dụng bởi Excel và các bảng tính khác. Tôi nghĩ rằng điều này giải quyết câu hỏi. Trình phân tích cú pháp CSV nhỏ chứng tỏ rằng thật dễ dàng phân tích CSV bằng cách sử dụng biểu thức chính quy này.
Sam Watkins

Không muốn quảng bá bản thân quá mức, đây là các thư viện csv và tsv hoàn chỉnh mà tôi đang sử dụng như một phần của ứng dụng bảng tính nhỏ (các trang tính của Google cảm thấy quá nặng đối với tôi). Đây là mã nguồn mở / miền công cộng / mã Muff giống như tất cả những thứ tôi xuất bản. Tôi hy vọng điều này có thể hữu ích cho người khác. sam.aiki.info/code/js
Sam Watkins
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.