web-dev-qa-db-ja.com

csv形式は正規表現で定義できますか?

同僚と私は最近、純粋な正規表現がcsv形式を完全にカプセル化できるかどうかについて議論しました。これにより、特定のエスケープ文字、引用文字、および区切り文字ですべてのファイルを解析できます。

正規表現は、作成後にこれらの文字を変更できる必要はありませんが、他のEdgeケースで失敗してはなりません。

これはトークナイザーだけでは不可能だと私は主張しました。これを実行できる唯一の正規表現は、トークン化だけでなく非常に複雑なPCREスタイルです。

私は次の線に沿って何かを探しています:

... csv形式は文脈自由文法であるため、正規表現だけで解析することはできません...

それとも私は間違っていますか? POSIX正規表現だけでcsvを解析することは可能ですか?

たとえば、エスケープ文字と引用文字の両方が"の場合、次の2行は有効な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..."
20
Spencer Rathbun

理論上はいい、実際にはひどい

[〜#〜] csv [〜#〜]によって、 RFCで説明されている規則を意味すると仮定します4180

基本的なCSVデータの照合は簡単ですが、

"data", "more data"

注:ところで、このような非常にシンプルで適切に構造化されたデータには、.split( '/ n')。split( '"')関数を使用する方がはるかに効率的です。正規表現は次のように機能しますエスケープ文字などのEdgeケースを追加し始めると、バックトラッキングに多くの時間を浪費するNDFSM(非決定的有限状態マシン)。

たとえば、私が見つけた最も包括的な正規表現一致文字列は次のとおりです。

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で文字列を解析する方法

一般的なEdgeケースが次のように導入されると、それは悪夢になります...

"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

Newline-as-valueエッジケースだけでも、実際に見られるRegExベースのパーサーの99.9999%を破るのに十分です。唯一の「妥当な」代替手段は、基本的な制御/非制御文字(つまり、ターミナル対非ターミナル)トークン化にRegExマッチングを使用して、より高いレベルの分析に使用される状態マシンとペアにすることです。

出典:広範囲にわたる痛みや苦しみとも呼ばれる経験。

私は jquery-CSV の作者です。これは、JavaScriptベースで完全にRFCに準拠した、世界で唯一のCSVパーサーです。私は何ヶ月もかけてこの問題に取り組み、多くのインテリジェントな人々と話をし、コアパーサーエンジンの3回の完全な書き換えを含むさまざまな実装があるかどうかを試しました。

tl; dr-ストーリーのモラル、PCREだけでは、最も単純で厳密な通常(Ie Type-III)の文法以外は何も解析しません。とはいえ、これは端末文字列と非端末文字列をトークン化するのに役立ちます。

20
Evan Plaice

正規表現は通常の言語を解析でき、再帰的な文法などの凝ったものは解析できません。しかし、CSVはかなり規則的であるように見えるため、正規表現で解析できます。

定義 から作業しましょう:シーケンス、選択肢形式の選択肢(_|_)、および繰り返し(Kleeneスター、_*_)を使用できます。

  • 引用符で囲まれていない値は通常です:_[^,]*_#カンマ以外の任意の文字
  • 引用符で囲まれた値は通常です:"([^\"]|\\\\|\\")*"#引用符以外のシーケンス_"_またはエスケープされた引用符_\"_またはエスケープされたエスケープ_\\_
    • 一部のフォームには、引用符でエスケープする引用符が含まれている場合があります。これにより、上記の式にバリアント_("")*"_が追加されます。
  • 許可される値は通常です:<unquoted-value> _|_ <quoted-value>
  • 1つのCSV行が通常です:<値> _(,_ <値> _)*_
  • _\n_で区切られた一連の行も、明らかに規則的です。

私はこれらの表現のそれぞれを綿密にテストしなかったし、キャッチグループを定義したこともない。また、_,_、_"_の代わりに使用できる文字のバリアント、または行区切り記号など、いくつかの専門知識についても説明しました。これらは規則性を損なうものではなく、いくつかのわずかに異なる言語を取得するだけです。

この証明で問題を見つけることができる場合は、コメントしてください! :)

しかし、これにもかかわらず、純粋な正規表現による実用的なCSVファイルの解析には問題がある場合があります。どのバリアントがパーサーに供給されているかを知る必要があり、そのための標準はありません。 1つが成功するまで各行に対していくつかのパーサーを試すことができます。または、何らかの形でコメントをフォーマットします。しかし、これには、正規表現以外の手段が効率的に、またはまったく必要になる場合があります。

20
9000

簡単な答え-おそらく違います。

最初の問題は、標準の欠如です。厳密に定義された方法でcsvを記述することはできますが、厳密に定義されたcsvファイルを取得することは期待できません。 「自分の行動を保守的にし、他人から受け入れることを寛大に」-ジョンポスタル

許容可能な標準記号があると仮定すると、エスケープ文字と、これらをバランスさせる必要があるかどうかという問題があります。

多くのcsv形式の文字列はstring value 1,string value 2として定義されます。ただし、その文字列にカンマが含まれている場合は、"string, value 1",string value 2になります。引用符が含まれている場合は、"string, ""value 1""",string value 2になります。

現時点では不可能だと思います。問題は、読んだ引用符の数と、値の二重引用符モードの内側または外側にコンマがあるかどうかを判別する必要があることです。括弧のバランスをとることは不可能な正規表現の問題です。一部の拡張正規表現エンジン(PCRE)はこれを処理できますが、その場合は正規表現ではありません。

https://stackoverflow.com/questions/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"

ここで重要なのは、常に複数の有効な解釈を持つ文字列を持つことが可能であることです。

関連する質問(Edgeの場合)「無効な文字列が受け入れられる可能性はありますか?」

一部のアプリケーションで作成されたすべての有効なCSVに一致し、解析できないすべてのcsvを拒否できる正規表現があるかどうか、私はまだ疑っています。

5
user40980

最初にCSVの文法を定義し(フィールド区切り文字がテキストに表示される場合、何らかの方法でエスケープまたはエンコードされていますか)、次に正規表現で解析できるかどうかを判別できます。文法を最初に:パーサーを2番目に: http://www.boyet.com/articles/csvparser.html このメソッドはトークナイザーを使用することに注意してください-しかし、POSIX正規表現を構築することはできませんすべてのEdgeケースに一致します。 CSV形式の使用方法が通常ではなく、コンテキストフリーである場合、あなたの答えはあなたの質問にあります。ここで良い概要: http://nikic.github.com/2012/06/15/The-true-power-of-regular-expressions.html

2
iivel

この正規表現は、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 418 定義を参照してください。

  1. 改行(CRLF)-正規表現はより柔軟で、CRLF、LFまたはCRを許可します。
  2. ファイルの最後のレコードには、終了の改行がある場合とない場合があります-正規表現そのものには最終的な改行が必要ですが、パーサーはそれを調整します。
  3. おそらくオプションのヘッダー行があります-これはパーサーに影響を与えません。
  4. 各行には、ファイル全体で同じ数のフィールドが含まれている必要があります-強制されていません
    スペースはフィールドの一部と見なされるため、無視しないでください-大丈夫です
    レコードの最後のフィールドの後にコンマを続けることはできません-強制されません
  5. 各フィールドは二重引用符で囲まれていてもいなくてもかまいません...-わかりました
  6. 改行(CRLF)、二重引用符、およびコンマを含むフィールドは、二重引用符で囲む必要があります-わかりました
  7. フィールド内に表示される二重引用符は、その前に別の二重引用符を付けることでエスケープする必要があります-わかりました

正規表現自体は、RFC 4180の要件のほとんどを満たしています。他の人には同意しませんが、パーサーを調整して実装するのは簡単です。

2
Sam Watkins