- ベストアンサー
正規表現でCSVデータを抜き出す方法の悩み
- 正規表現でCSVデータを抜き出す方法について悩んでいます。
- エスケープ文字の問題により正規表現での解決が難しい状況です。
- 解決策についてアドバイスを頂きたいです。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
> 正規表現で (?:"((?:""|\\\"|[^"])*)"|([^,]*))(?:,|$)(?:"((?:""|\\\\"|[^"])*)"|((?:[^,]|\\\\,)*))(?:,|$) # 全て検証したわけじゃないので抜けがあるかもしれない。 ちなみに、PHP には fgetcsv あるいは str_getcsv(PHP 5.3+) が用意されている。ただし、これら関数は、解析エラーを吐き出さないため、自前で解析というのも懸命な判断かもしれない。
その他の回答 (4)
- hogehoge78
- ベストアンサー率80% (433/539)
yuu_xさんに指摘されたので、追記。 fgetcsv関数に関することはすでに、No.1と、No.2で回答がされてますので、触れませんでした。 当然、fgetcsvが使用可能であれば、それを使って処理した方がいいです。 (fgetcsvを使って文字化けがする等、環境やPHPのバージョンによって問題が現れる可能性がありますが。) で、それを使用したくない何らかの理由がある場合に、ユーザ関数等つくってやってみると思いますが、 そこに複雑な一つの正規表現で表現するのであれば、PHPスクリプトで書いたほうがやりやすいのではないか、 というのが回答の意図です。
- yuu_x
- ベストアンサー率52% (106/202)
> 3 いあ、それ使うくらいなら、既存の関数のほうが断然いい。(早い遅いの問題でなく) 既存の関数は、csv の正確さまでは検証しないといいたかっただけ。 加えて、この点は質問者も同じ事をしているが、エスケープなるものを導入したせいで扱いにくくもある(しかし、エスケープ文字が省略できないのには納得がいかないよな。phpMyAdmin も同じ事をしてくれているため結局手書きで流し込むことになる)。RFC4180 にはそんなものは存在しない。
- hogehoge78
- ベストアンサー率80% (433/539)
正規表現を使わなくても自前でパースするのはそこまで大変ではないと思いますよ。 <?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; } ?> 速度は保証しませんが。
お礼
ご回答ありがとうございます。 このmy_fgetcsv関数はhogehoge78さんがコーディングされたのでしょうか。 そうであれば感謝の限りです。ありがとうございます。 今回のCSVデータは、お客様が入力されることもあるデータでしたので、入力ミスを出来る限り検知したいという背景がありました。 自力でのパース処理も考えたのは考えたのですが、テスト工数が大きくなる、バグを内包する可能性が高くなるという判断で、出来れば既存の関数を利用しようと考えておりました。 調べたところfgetcsv関数もC言語でパース処理を行っていましたので、方法としてはアリだったのかなと今となっては思っております。 ご提示いただきましたパース処理の関数は、自分で纏めているナレッジに含めさせていただこうと思います。 本当にありがとうございました。
- shiren2
- ベストアンサー率47% (139/295)
仰っている意味がよくわからないのですが…。 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) "" }
お礼
ご回答ありがとうございます。 fgetcsvを使用しなかった理由としまして ・文字化けが発生し得る ・囲い文字が欠けている場合によるエラーが発生しない ・行末の囲い文字が存在しないと次行を含んで読み込んでしまう などがあるかと思っておりました。(実際にやってみたところいくつか発生しました) なので今回は自前でのチェックにて解析エラー出力を行うため、fgetcsv_regというネット上にあるユーザ定義関数で対応・応用しようとした背景があります。 (fgetcsv_regは正規表現にてCSVデータを取得しています) 私の説明が不足しており、申し訳ございません。お詫び申し上げます。 今のところ、正規表現での取得が厳しそうなので、str_getcsvの利用でどうにかならないか思案中です。 何か分かれば補足いたしますね。
お礼
ご回答ありがとうございます。 ご提示いただきました正規表現を試してみましたところ、一部取得できない箇所がありました。 しかし、私自身正規表現に対しての知識が乏しいためにまだ理解できていない部分もあり、今回ご提示いただきました正規表現は大変価値あるものだと思っております。 内容の理解に励み、次に活かしたいと思います。 私の意図を汲んでいただいた上でのご回答でしたので、こちらをベストアンサーとさせていただきます。 本当にありがとうございました。