遊星ゲームズ
FrontPage | RSS


PHP fgetcsv()は使うな
 プログラム

 いろいろあったのだけど、ようするに使えないということがわかった……。忘れないように書いとく。

 PHPにfgetcsv()という便利関数があって。ファイルをCSVとして一行とりだして、各列の値を配列に入れて返すというものなんだが。
 PHP4のはじめのころは、バグだらけなので有名だったらしいのだけど、さすがにPHP5.2なら大丈夫だろうと思ったら。しかしダメ。
 マルチバイト文字の2バイト目に'\'があると、直後のダブルクォーテーションをエスケープするという、少なくともEXCELの仕様にはない独自解釈をするらしく。つまりSJISのファイルを読ませるとうまくいかない。
(ただし、EXCELがCSVをどう解釈するかという仕様は公開されてなさげ)
 じゃあEUCに変換してから読ませれば、いちおうは大丈夫なんだけど、もちろん機種依存文字が化ける。 ←(これは指摘されたのでいちおう修正。SJIS-winからEUCJP-winとかUTF-8とかに変換すれば使える。けどめどい)
 だいだいCSVを使うときというのはEXCELからくるデータなわけで。文字コードはSJISだし、機種依存文字も使いまくりがあたりまえだ。それがいけないのではあるけど、しかし対応するしかないし。

 もしどうしても使うなら、あらかじめ機種依存文字を安全な文字列に変換して、EUCにして保存し、そのあとでファイルを開いてfgetcsv()するとか?(やる気にならないから試してない)もちろん、そんなことするなら自分で処理を書いたほうがいい。 ←(これもSJIS-winとか指定すれば特別な変換はいらない)
 というわけで。もう二度と使わないぞ。

 というわけで、できるだけExcelの動きに近づけてみた関数を作ってみたんだ。
 調べてみると、セル中でダブルクォートが出てきたときとか、あんたほんとにそれでいいのかと思う動きをするけど、あくまでそんなExcelを再現しようとしてみる。


function fgetExcelCSV(&$fp , $length = null
, $delimiter = ',' , $enclosure = '"') {
    $line = fgets($fp);
    if($line === false) {
        return false;
    }
    $bytes = preg_split('//' , trim($line));
    array_shift($bytes);array_pop($bytes);
    $cols = array();
    $col = '';
    $isInQuote = false;
    while($bytes) {
        $byte = array_shift($bytes);
        if($isInQuote) {
            if($byte == $enclosure) {
                if($bytes[0] == $enclosure) {
                    $col .= $byte;
                    array_shift($bytes);
                } else {
                    $isInQuote = false;
                }
            } else {
                $col .= $byte;
            }
        } else {
            if($byte == $delimiter) {
                $cols[] = $col;
                $col = '';
            } elseif($byte == $enclosure && $col == '') {
                $isInQuote = true;
            } else {
                $col .= $byte;
            }
        }
        while(!$bytes && $isInQuote) {
            $col .= "\n";
            $line = fgets($fp);
            if($line === false) {
                $isInQuote = false;
            } else {
                $bytes = preg_split('//' , trim($line));
                array_shift($bytes);array_pop($bytes);
            }
        }
    }
    $cols[] = $col;
    return $cols;
}

 つまりなにがいいたいかというと、

  1. セル頭のダブルクォートでダブルクォートの中に入る。
  2. でもその後どこでもいいからダブルクォートが登場すると、抜けてしまう。でもまだセルの中。
    ただしダブルクォート2個のときは例外として、ダブルクォート1個に変換。

 っていうかそんなの例外ケースなので、やる必要はまったくないけど(笑)


[2007.10.27 11:22]anonymous :
CSVファイルをSJIS-winからUTF-8に変換してから、
fgetcsv()を使用し、UTF-8からSJIS-winに戻せば
機種依存文字等も壊れないけどね。
※SJIS→EUCだと壊れる。


[2007.10.27 21:47]てらしま :
 実際仕事ではそういうことやりましたが(笑) けっきょく、それでもExcelと動作が違っていると、いつか不具合といわれても不思議はないのが現実なんですよねえ。


[2007.10.28 01:12]てらしま :
 というわけで、まあ指摘いただいたのでいちおう上の内容を修正しときます。


[2007.11.02 12:00]anonymous :
fgetExcelCSV()って、セル内に改行がある場合に対応してないのでは?
fgetcsv() はセル内に改行がある場合に対応してるけど…。


[2007.11.02 23:18]てらしま :
 対応してるはずです。がやってみてうまくいかなかったら、なおすなりなおせというなりしてください。


うえき -2009/07/13 18:16
わかりやすいソースですね。このソース(fgetExcelCSV)のライセンスを教えてください。
サイトで使わせていただきたいと思っています。


てらしま -2009/07/13 19:16
 ……考えてもいなかったです。
 なにをどうされてもかまわないです。とりあえずNYSL0.9982にしたがいます、ということでおねがいします。


うえき -2009/07/14 09:59
ありがとうございます。使わせていただきます。


Motty -2012/08/20 16:35
今だに fgetcsv に悩まされていました。
このページに救われました!
fgetExcelCSV使わせて頂きます。
有難うございます。


てらしま -2012/08/21 16:07
いやその、読んでいただいたのは嬉しいですが。
これはもう何年も前に書いたもので、しかも半分冗談の書き殴りコードです。使用はおすすめしません。
もっと新しくていいものがあるので、そちらを探してください。


PHP fgetcsv()は使うなを