正規表現でCSVデータを抜き出す方法の悩み

このQ&Aのポイント
  • 正規表現でCSVデータを抜き出す方法について悩んでいます。
  • エスケープ文字の問題により正規表現での解決が難しい状況です。
  • 解決策についてアドバイスを頂きたいです。
回答を見る
  • ベストアンサー

CSVデータを正規表現で抜き出せません・・・。

お疲れ様です。初投稿になります。 どうぞ宜しくお願い致します。 CVSデータ(囲い文字:"(ダブルコーテーション))を正規表現でマッチングさせ、抜き出そうと考えています。 エスケープ文字を以下のように設定しています。 ・""(ダブルコーテーション * 2) ⇒ "(ダブルコーテーション) ・\"(円記号 + ダブルコーテーション) ⇒ "(ダブルコーテーション) ・\\(円記号 * 2) ⇒ \(円記号) 例えば・・・ "A",""",BB,"",\",CC,\\,DD""","EEEE","", ですと、「"A"」「""",BB,"",\",CC,\\,DD"""」「"EEEE"」「""」と取れる想定になります。 特に2番目の「""",BB,"",\",CC,\\,DD"""」は、 「"""(←escape),BB,""(←escape),\"(←escape),CC,\\(←escape),DD""(←escape)"」 という想定です。 PHP関数ではエスケープ文字の問題により取得しきれず、自力での取得を行っております。 私が考えた正規表現は「"[^"]*((""|\\")[^"]*)*[^\\]",|"",」となりますが、カンマが入り乱れる上記パターンを満たすことができません。 これは、正規表現での解決は不可能でしょうか? 何日も解決できず、困っています・・・。 是非、皆様のお力添えのほど、宜しくお願い致します。 PS. このサイトで動作確認を行っておりました。 参考になれば幸いです。 http://www.rider-n.sakura.ne.jp/regexp/regexp.php

  • PHP
  • 回答数5
  • ありがとう数7

質問者が選んだベストアンサー

  • ベストアンサー
  • yuu_x
  • ベストアンサー率52% (106/202)
回答No.2

> 正規表現で (?:"((?:""|\\\"|[^"])*)"|([^,]*))(?:,|$)(?:"((?:""|\\\\"|[^"])*)"|((?:[^,]|\\\\,)*))(?:,|$) # 全て検証したわけじゃないので抜けがあるかもしれない。 ちなみに、PHP には fgetcsv あるいは str_getcsv(PHP 5.3+) が用意されている。ただし、これら関数は、解析エラーを吐き出さないため、自前で解析というのも懸命な判断かもしれない。

west1023
質問者

お礼

ご回答ありがとうございます。 ご提示いただきました正規表現を試してみましたところ、一部取得できない箇所がありました。 しかし、私自身正規表現に対しての知識が乏しいためにまだ理解できていない部分もあり、今回ご提示いただきました正規表現は大変価値あるものだと思っております。 内容の理解に励み、次に活かしたいと思います。 私の意図を汲んでいただいた上でのご回答でしたので、こちらをベストアンサーとさせていただきます。 本当にありがとうございました。

その他の回答 (4)

回答No.5

yuu_xさんに指摘されたので、追記。 fgetcsv関数に関することはすでに、No.1と、No.2で回答がされてますので、触れませんでした。 当然、fgetcsvが使用可能であれば、それを使って処理した方がいいです。 (fgetcsvを使って文字化けがする等、環境やPHPのバージョンによって問題が現れる可能性がありますが。) で、それを使用したくない何らかの理由がある場合に、ユーザ関数等つくってやってみると思いますが、 そこに複雑な一つの正規表現で表現するのであれば、PHPスクリプトで書いたほうがやりやすいのではないか、 というのが回答の意図です。

  • yuu_x
  • ベストアンサー率52% (106/202)
回答No.4

> 3 いあ、それ使うくらいなら、既存の関数のほうが断然いい。(早い遅いの問題でなく) 既存の関数は、csv の正確さまでは検証しないといいたかっただけ。 加えて、この点は質問者も同じ事をしているが、エスケープなるものを導入したせいで扱いにくくもある(しかし、エスケープ文字が省略できないのには納得がいかないよな。phpMyAdmin も同じ事をしてくれているため結局手書きで流し込むことになる)。RFC4180 にはそんなものは存在しない。

回答No.3

正規表現を使わなくても自前でパースするのはそこまで大変ではないと思いますよ。 <?php $fp = fopen('test.txt', 'r'); while($row = my_fgetcsv($fp)){ var_dump($row); } fclose($fp); function my_fgetcsv($fp, $size=4096){ $result = ""; $cnt = 0; while($row = fgets($fp, $size)){ $result .= $row; $buff = str_replace('\\"', '', $row); $buff = str_replace('""', '', $row); $cnt += substr_count($buff, '"'); if($cnt % 2 == 0){ break; } } return my_csv_explode($result); } function my_csv_explode($line){ $length = strlen($line); $in_block = false; $before_escape = 0; $data = ""; for($i=0; $i<$length; $i++){ $check = $line[$i]; if($in_block === false && $check == '"'){ $in_block = true; continue; } if($before_escape === 0){ if($check == '"'){ $before_escape = 1; continue; }elseif($check == '\\'){ $before_escape = 2; continue; } } if($before_escape === 1 || $before_escape === 2){ if($check == '"'){ $data .= '"'; }elseif($before_escape === 2){ if($check == '\\'){ $data .= $check; }else{ $data .= '\\' . $check; } }elseif($before_escape === 1 && $check === ','){ $results[] = $data; $data = ""; $in_block = false; $before_escape = 0; continue; }else{ $data .= $check; } $before_escape = 0; continue; } if($in_block === true && $check == '"'){ if($line[++$i] === ','){ $results[] = $data; $data = ""; $in_block = false; continue; }else{ trigger_error('csv syntax error. line data:'.$line, E_USER_ERROR); } } if($in_block === false && $check == ','){ $results[] = $data; $data = ""; continue; } if($in_block === false && $check == "\n"){ $results[] = $data; $data = ""; continue; } if($in_block === false && $check == "\r"){ $results[] = $data; $data = ""; if($line[++$i] != "\n"){ $i--; } continue; } $data .= $check; } if(!empty($data)){ $results[] = $data; } return $results; } ?> 速度は保証しませんが。

west1023
質問者

お礼

ご回答ありがとうございます。 このmy_fgetcsv関数はhogehoge78さんがコーディングされたのでしょうか。 そうであれば感謝の限りです。ありがとうございます。 今回のCSVデータは、お客様が入力されることもあるデータでしたので、入力ミスを出来る限り検知したいという背景がありました。 自力でのパース処理も考えたのは考えたのですが、テスト工数が大きくなる、バグを内包する可能性が高くなるという判断で、出来れば既存の関数を利用しようと考えておりました。 調べたところfgetcsv関数もC言語でパース処理を行っていましたので、方法としてはアリだったのかなと今となっては思っております。 ご提示いただきましたパース処理の関数は、自分で纏めているナレッジに含めさせていただこうと思います。 本当にありがとうございました。

  • shiren2
  • ベストアンサー率47% (139/295)
回答No.1

仰っている意味がよくわからないのですが…。 PHPの標準関数(fgetcsv)で以下のような出力が得られましたが、それとは違うのでしょうか。 <?php  $fp = fopen("log.csv", "r");  while($array = fgetcsv($fp)){   var_dump($array);  } ?> array(5) {  [0]=>  string(1) "A"  [1]=>  string(19) "",BB,",\",CC,\\,DD""  [2]=>  string(4) "EEEE"  [3]=>  string(0) ""  [4]=>  string(0) "" }

west1023
質問者

お礼

ご回答ありがとうございます。 fgetcsvを使用しなかった理由としまして  ・文字化けが発生し得る  ・囲い文字が欠けている場合によるエラーが発生しない  ・行末の囲い文字が存在しないと次行を含んで読み込んでしまう などがあるかと思っておりました。(実際にやってみたところいくつか発生しました) なので今回は自前でのチェックにて解析エラー出力を行うため、fgetcsv_regというネット上にあるユーザ定義関数で対応・応用しようとした背景があります。 (fgetcsv_regは正規表現にてCSVデータを取得しています) 私の説明が不足しており、申し訳ございません。お詫び申し上げます。 今のところ、正規表現での取得が厳しそうなので、str_getcsvの利用でどうにかならないか思案中です。 何か分かれば補足いたしますね。

関連するQ&A

  • 正規表現の置換について質問です。

    javaで入力された文字の中に半角記号が含まれていた場合、文字内に存在する記号を正規表現で文字列として扱いた際に、エスケープで置換するような処理を正規表現で作成したいのですが、どのように作成すればよいでしょうか? 例:aa_bb*cc dd! よろしくお願いします。

  • 正規表現

    正規表現を使って、数字、大文字小文字のアルファベット、「”」(ダブルコーテーション)、「、」コンマ以外の記号を、表せる正規表現方法はどうなるのでしょうか? 見当がつきません。 どなたかお願いします。

    • ベストアンサー
    • Java
  • 正規表現で囲まれた部分の文字列

    正規表現でダブルコーテーションで囲まれた部分の文字列 を検索したいのです。 \"([^\"])*\" とりあえずこんな感じで検索はできるのですが 問題はダブルコーテーションで囲まれた部分の エスケープ文字\の判定が上手くできません。 ([^\"]) この部分でダブルコーテーション以外の文字列、 ただし\"は除くって感じにしたいのですがどうすれば良いでしょうか。 宜しくお願いします。 正規表現のエンジンはBREGEXP.DLLです。 http://www.hi-ho.ne.jp/babaq/bregexp.html

  • またまた正規表現><

    いつもお世話になっておりますっ>< 正規表現の記述方法で質問があります。 (納期に追われております><) たとえば <font color=#FFFFFF size=6> みたいな文字があってこれを <font color="#FFFFFF" size="6"> に変えたいんです。つまり「=」の後ろに ダブルクォーテーションがなかったら ダブルクォーテーションを付けたいんです>< さらに <font color="#FFFFFF" size=5> とか、ちゃんとダブルクォーテーションが着いてる文字列も混在しております>< 最終的には = のあとにダブルクォーテーション で その後に続く文字を 囲いたいんです>< 正規表現については以前質問したんですが、 申し訳ありません、また教えてください、よろしくお願い致します><

  • 正規表現で少し複雑な置換がしたい

    Javaというよりも、正規表現について質問です。 (一応、言語はJavaで、String#replaceAllを使います) 以下のルールで与えられた文字列の置換を行う正規表現を考えていますが、なかなかうまくいきません。 1.正規表現で \w+:: にマッチする文字列を削除する。 2.ただし、'' (シングルクォーテーション)で囲われている文字列は   1.の対象としない 3.ただし、\' (シングルクォーテーションの前に\があった場合は)   シングルクォーテーションと認識しない。 例: 置換前  A * B \'' CCC.DD::EEE' FFF.GGG::HH ' III' 置換後  A * B \'' CCC.DD::EEE' FFF.HH 'III' 色々試してみたのですが、どうしてもうまくいきません。 正規表現が得意な方、教えていただけますでしょうか・・?

    • ベストアンサー
    • Java
  • 正規表現メタ文字「-」のエスケープ(再質問)

    いつもお世話になります。 以前に質問しているのですが、私の質問内容が悪かったため、再度質問をあげさせていただきます。 正規表現のメタ文字のエスケープ処理を行いたいのですが、「-」の記号について、「[」「]」の中に出てきたときのみ([a-z]、[0-9]など)、 一括でエスケープしてくれるような正規表現は何を指定すればよいでしょうか。 例えば、以下の文字列があった場合、 ああaa-bb[0-9]いいい-uuu[a-zA-Z] エスケープ処理された結果、以下のようになる。([]内の-のみエスケープ処理されて、「\-」となっている) ああaa-bb[0\-9]いいい-uuu[a\-zA\-Z] 上記の一括エスケープ処理を行ってくれる正規表現は、何を指定すればよいでしょうか。 宜しくお願いします。

    • ベストアンサー
    • Java
  • 正規表現メタ文字「-」のエスケープについて

    いつもお世話になります。 正規表現のメタ文字のエスケープ処理を行いたいのですが、「-」の記号については、「[」「]」の中に出てきたときのみ([a-z]、[0-9]など)、エスケープの対象になります。 この場合、どのような正規表現を指定すれば、エスケープを行うことができるでしょうか。 例)  [a-z|A-z] ⇒ [a\-z|A\-z] 宜しくお願いします。

  • 正規表現で( , -, ] を表すと。

    こんばんは。お世話になります。 正規表現を使ったプログラミングをしているのですが、一般に、) や、[ や - は正規表現の記号として使われますよね。 この文字を正規表現で検索したい時には、どうすればよいのでしょうか?. 誰か分かる方は教えてください。よろしくお願いします。

  • 正規表現

    数字と大文字、小文字のアルファベットは入力OK。 記号も入力OK。でも、記号の中でも「”」、「.」は ダメ。しかも、10桁くらい入力できるようにする。 を表す正規表現は [a-zA-Z0-9&&[\"\.]]* と思ったのですが、 正しく表す正規表現はどの様なものになるのでしょうか? お願いします。

    • ベストアンサー
    • Java
  • 正規表現ですが・・・

    正規表現なんですが(Perlです。) Aa_abcd_aa.txt Aa_abcd_bb.txt Aa_abcd_cc.txt Bb_abcd.txt 上記のabcdの部分をマッチさせるために (?<=Aa_|Bb_).*(?=_aa|_bb|_cc|\.txt) としてみましたが、これでは Aa_abcd_aa.txt Aa_abcd_bb.txt Aa_abcd_cc.txt のabcd_aaやabcd_bbやabcd_ccとマッチしてしまいます。 なにかよい表現の仕方はありますか? まとめてマッチさせる表現が知りたいです。

専門家に質問してみよう