人生のプチ美学を教えてください!!

お疲れ様です。初投稿になります。
どうぞ宜しくお願い致します。

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

A 回答 (5件)

> 正規表現で


(?:"((?:""|\\\"|[^"])*)"|([^,]*))(?:,|$)(?:"((?:""|\\\\"|[^"])*)"|((?:[^,]|\\\\,)*))(?:,|$)
# 全て検証したわけじゃないので抜けがあるかもしれない。

ちなみに、PHP には fgetcsv あるいは str_getcsv(PHP 5.3+) が用意されている。ただし、これら関数は、解析エラーを吐き出さないため、自前で解析というのも懸命な判断かもしれない。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

ご提示いただきました正規表現を試してみましたところ、一部取得できない箇所がありました。
しかし、私自身正規表現に対しての知識が乏しいためにまだ理解できていない部分もあり、今回ご提示いただきました正規表現は大変価値あるものだと思っております。
内容の理解に励み、次に活かしたいと思います。

私の意図を汲んでいただいた上でのご回答でしたので、こちらをベストアンサーとさせていただきます。

本当にありがとうございました。

お礼日時:2010/12/13 16:09

yuu_xさんに指摘されたので、追記。



fgetcsv関数に関することはすでに、No.1と、No.2で回答がされてますので、触れませんでした。
当然、fgetcsvが使用可能であれば、それを使って処理した方がいいです。
(fgetcsvを使って文字化けがする等、環境やPHPのバージョンによって問題が現れる可能性がありますが。)


で、それを使用したくない何らかの理由がある場合に、ユーザ関数等つくってやってみると思いますが、
そこに複雑な一つの正規表現で表現するのであれば、PHPスクリプトで書いたほうがやりやすいのではないか、
というのが回答の意図です。
    • good
    • 0

> 3


いあ、それ使うくらいなら、既存の関数のほうが断然いい。(早い遅いの問題でなく)
既存の関数は、csv の正確さまでは検証しないといいたかっただけ。

加えて、この点は質問者も同じ事をしているが、エスケープなるものを導入したせいで扱いにくくもある(しかし、エスケープ文字が省略できないのには納得がいかないよな。phpMyAdmin も同じ事をしてくれているため結局手書きで流し込むことになる)。RFC4180 にはそんなものは存在しない。
    • good
    • 0

正規表現を使わなくても自前でパースするのはそこまで大変ではないと思いますよ。


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

?>
速度は保証しませんが。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

このmy_fgetcsv関数はhogehoge78さんがコーディングされたのでしょうか。
そうであれば感謝の限りです。ありがとうございます。

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

本当にありがとうございました。

お礼日時:2010/12/13 16:23

仰っている意味がよくわからないのですが…。


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) ""
}
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
fgetcsvを使用しなかった理由としまして
 ・文字化けが発生し得る
 ・囲い文字が欠けている場合によるエラーが発生しない
 ・行末の囲い文字が存在しないと次行を含んで読み込んでしまう
などがあるかと思っておりました。(実際にやってみたところいくつか発生しました)

なので今回は自前でのチェックにて解析エラー出力を行うため、fgetcsv_regというネット上にあるユーザ定義関数で対応・応用しようとした背景があります。
(fgetcsv_regは正規表現にてCSVデータを取得しています)

私の説明が不足しており、申し訳ございません。お詫び申し上げます。
今のところ、正規表現での取得が厳しそうなので、str_getcsvの利用でどうにかならないか思案中です。
何か分かれば補足いたしますね。

お礼日時:2010/12/13 14:39

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報