PHP5でfgetcsvが正常に動作しない

Posted on 7月 19, 2006
Filed Under PHP | 31 Comments

CSVのインポート機能を持ったシステムをPHP4環境からPHP5環境へ移行したら、
なぜかCSVデータを正しく読み込んでくれない。っていうか一文字目が文字化け。
超悩んだあげくぐーぐるさんで検索しても以下のような記事しかみつからず。

[PHP-dev 1205] PHP5のfgetcsv()関数について

人力検索はてな – PHP4からPHP5へソースの移(長いので略)

csvファイルを読み込むと1バイト目の日本語が文字化け

3つ目の掲示板のyossyはあたくし自身なんですが・・・。

setlocaleとかいろいろ試してもしても結局読み込まれるCSVの文字コードは
ほとんどSJISなせいなためかなんだかうまくいきません。

ちなみに検証環境はほぼFedoraCore4のデフォルトです。
PHPは5.0.4だったような気がします。

で、結局回避策として正規表現でCSVをぶった切ることにしました。

3つめのリンク先の掲示板に正規表現でCSV読み込む関数を書いたんですが、
改行を含んだCSVファイルには対応していなかったので
改行にも対応した関数を作りました。

Excel形式(というかRFC4180)に準拠したデータなら大丈夫・・・かな?

既存のfgetcsvとほとんど同じような感覚で使えるはず。うん。

<?php
    /**
     * ファイルポインタから行を取得し、CSVフィールドを処理する
     * @param resource handle
     * @param int length
     * @param string delimiter
     * @param string enclosure
     * @return ファイルの終端に達した場合を含み、エラー時にFALSEを返します。
     */
    function fgetcsv_reg (&$handle, $length = null, $d = ',', $e = '"') {
        $d = preg_quote($d);
        $e = preg_quote($e);
        $_line = "";
        while (($eof != true)and(!feof($handle))) {
            $_line .= (empty($length) ? fgets($handle) : fgets($handle, $length));
            $itemcnt = preg_match_all('/'.$e.'/', $_line, $dummy);
            if ($itemcnt % 2 == 0) $eof = true;
        }
        $_csv_line = preg_replace('/(?:\\r\\n|[\\r\\n])?$/', $d, trim($_line));
        $_csv_pattern = '/('.$e.'[^'.$e.']*(?:'.$e.$e.'[^'.$e.']*)*'.$e.'|[^'.$d.']*)'.$d.'/';
        preg_match_all($_csv_pattern, $_csv_line, $_csv_matches);
        $_csv_data = $_csv_matches[1];
        for($_csv_i=0;$_csv_i<count($_csv_data);$_csv_i++){
            $_csv_data[$_csv_i]=preg_replace('/^'.$e.'(.*)'.$e.'$/s','$1',$_csv_data[$_csv_i]);
            $_csv_data[$_csv_i]=str_replace($e.$e, $e, $_csv_data[$_csv_i]);
        }
        return empty($_line) ? false : $_csv_data;
    }
?>

この関数を↓こんな感じで。

<?php
    $row = 1;
    $handle = fopen("test.csv", "r");
    while (($data = fgetcsv_reg($handle)) !== false) {
        $_enc_to=mb_internal_encoding();
        $_enc_from=mb_detect_order();
        mb_convert_variables($_enc_to,$_enc_from,$data);
        $num = count($data);
        echo "<p> $num fields in line $row: </p><br />\n";
        $row++;
        for ($c=0; $c < $num; $c++) {
            echo nl2br($data[$c]) . "<br />\n";
        }
    }
    fclose($handle);
?>

(2011/9/8追記)
コメントで無限ループのご指摘があったので
while ($eof != true) {

while (($eof != true)and(!feof($handle))) {
へ修正しました。
反応がとても遅くなりごめんなさい。

Comments

Leave a Comment

If you would like to make a comment, please fill out the form below.

Name (必須)

Email (必須)

ウェブサイト

コメント

31 Comments so far
  1. Jun Kuwamura 12月 14, 2008 22:10:01

    ありがとうございます。
    pukiwikiのトラックバックのバグ修正に使わせてもらいました。

  2. n-brid 12月 17, 2008 4:26:04

    すばらしいです。
    おかげさまでシンプルに処理できるようになりました。ありがとうございます。

  3. 池田@ママ 12月 24, 2008 20:06:59

    ありがとうーーーー
    感謝します!

  4. takus 1月 20, 2009 22:31:03

    素晴らしいです。
    一発で問題が解決しました。
    ありがとうございます。

  5. hirrroo 1月 21, 2009 14:09:58

    悩み解決しました。
    ありがとうございます!!

  6. Restromic 1月 27, 2009 3:36:57

    感謝!!!

  7. もぎゃ 4月 21, 2009 21:59:17

    この関数、間違って「”」が奇数個あるファイルを読み込むと、whileループから抜けられなくなりませんか?

    関数の5行目
    $_line .= (empty($length) ? fgets($handle) : fgets($handle, $length))
    を、こんなふうにすれば直るような気がします。

    $s = (empty($length) ? fgets($handle) : fgets($handle, $length));
    if (!$s) {break;}

  8. SE風味 6月 7, 2009 10:48:22

    fgetcsv_reg にはお世話になりました。ありがとうございます。
    で、上のコメントの無限ループについて、
    while ($eof != true) {

    while (($eof != true)and(!feof($handle))) {
    に書きかえればOKではないかと。

  9. IIS 6月 15, 2009 9:20:07

    助かりました。ありがとうございます

  10. nzk 7月 19, 2009 11:00:51

    素晴らしすぎます!ありがとうございます!

  11. Tadaske Watanabe 10月 30, 2009 16:47:14

    同じ問題で困っていたところでした。
    ありがとうございます!

    本当に助かりました。:-)

  12. hideshi 12月 26, 2009 18:00:02

    私もこれで困っていました。非常に助かりました。ありがとうございます。
    netcommonsで応用させて頂きました。

    1行用
    function fgetcsv_reg($line, $length = null, $d = ‘,’, $e = ‘”‘) {
    $d = preg_quote($d);
    $e = preg_quote($e);
    $itemcnt = preg_match_all(’/’.$e.’/', $line, $dummy);
    $_csv_line = preg_replace(’/(?:\r\n|[\r\n])?$/’, $d, trim($line));
    $_csv_pattern = ‘/(’.$e.’[^'.$e.']*(?:’.$e.$e.’[^'.$e.']*)*’.$e.’|[^'.$d.']*)’.$d.’/';
    preg_match_all($_csv_pattern, $_csv_line, $_csv_matches);
    $_csv_data = $_csv_matches[1];
    for($_csv_i=0;$_csv_i

  13. まつぼっくり 2月 9, 2010 18:16:22

    PHP 5.2.12でも同様の問題が発生し、この記事のコードで回避できました。

    大変助かりました。

  14. aidream 3月 8, 2010 16:26:03

    本当にすばらしいですね!
    3日間はまっていた問題が一気に解決しました。
    ありがとうございます。

  15. Yokokawa 3月 29, 2010 14:41:53

    ありがとうございます!
    おかげで問題が解決できました^^

  16. aidream 4月 24, 2010 22:16:35

    ブログで引用させていただきました。
    ありがとうございます。

  17. とし 5月 18, 2010 17:16:31

    Warning: mb_convert_variables() [function.mb-convert-variables]: Unable to detect encoding
    というエラーになってしまいます。
    環境はxampp1.7.3でphpの文字コードはutf-8でcsvの文字コードはshift_jisです。

    エラーになる行は下記のようなのですがおわかりになりますでしょうか?
    >mb_convert_variables($_enc_to,$_enc_from,$data);

  18. metaboy 6月 22, 2010 8:49:40

    ありがとうございます!
    とても役立ちました。
    ブログの方で、紹介もさせていただきました。
    ほんと、感謝です。

  19. よ~ぼん(twitter @aoki_1) 11月 4, 2010 16:29:12

    お蔭様で問題が解決しました。
    ありがとうございます。

    ちなみに、僕の場合の問題は
    ローカル環境(PHP Version 5.3.1)では問題ないのに、
    本サーバ(PHP Version 5.2.13)にアップすると
    例の問題が発生するというものでした。

  20. Nandinbaatar 12月 23, 2010 6:03:31

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

  21. tama 1月 24, 2011 18:42:42

    使わせて頂きました。
    感謝します!

  22.  4月 3, 2011 0:25:46

    $eofでnoticeが出ます

    $_enc_to=mb_internal_encoding();
    $_enc_from=mb_detect_order();
    の2行はwhileの外に出してもいいと思います。

  23. osaru 7月 13, 2011 18:10:52

    素敵です
    ありがとうございます
    感謝!感謝!

  24. バルバール 8月 6, 2011 17:38:27

    PHPを5.2.x から5.1.x にしても同様の現象ではまりました。

    有難うございました!

  25. vtk 12月 1, 2011 12:04:17

    fgetcsv_reg()関数を実行しているうちに、下記のメッセージが出ました。

    Notice: Undefined variable: eof in /share/…

    $eof変数が認識されていないようです。

    while (($eof != true)and(!feof($handle))) {

    }

    $eofを何かを代入するべきのようです。

  26. takesi 12月 25, 2011 13:48:49

    助かりました!ありがとうございます!

  27. gondoh 12月 28, 2011 14:34:25

    スバラデース

  28. hyo 7月 11, 2012 16:00:55

    助かりました。
    こちらの正規表現ですがフィールド内にダブルクォートが入った場合に問題ありませんか?
    “te\”st”
    のような場合です。

  29. kiyo 7月 27, 2012 11:05:29

    助かりました!ありがとうございます!

  30. nao yoko 8月 21, 2012 19:01:41

    ありがとう、助かりました!!
    感謝m(__)m

  31. konno02 2月 25, 2015 20:19:52

    ありがとうございます。利用させていただきました。

    一点、ご報告なのですが、TSV(タブ区切り)の場合、行頭、行末が空白の場合、カラム数が減ってしまうようです。

    例)
    abcde ←count($_csv_data)が5
    bcde ←count($_csv_data)が3

    どうやら、trim($_line) とあるため、最初と最後がタブだけだとと空白扱いされてしまい、データが削除されてしまうようです。

    trim($_line, “\n\r\x0B”)

    のようにすることで解決できました。

    ご参考になれば幸いです。

Recently


Categories


Archives


Wordpress Themes