page_adsence

2010年12月21日火曜日

PHP5のfgetcsvに関して

PHP5でShift-JISでエンコードされているcsvファイルをfgetcsvを使うと文字化けしてしまうらしい。
文字コードがSJISのデータを処理させた時に、「"機能"」などというデータがあった場合、「能」の2バイト目が「5C(\)」であるため「"」の部分が「\"」と扱われてエスケープされてしまい、データが正しく取得できないのだ。エスケープ文字として処理された結果「5C」の部分は消えてしまい、結果、データが文字化けしてしまう。
こういった現象を回避するためにyossyさんという方が、fgetcsv_regというバグを取った版の関数を作ってくれている。
が、壊れたcsvや画像ファイルを無理やり読み込ませると無限ループに陥るという欠点がある。
それを修正した版は以下の通り。

<?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 "

$num fields in line $row:

\n"; $row++; for ($c=0; $c < $num; $c++) { echo nl2br($data[$c]) . " \n"; } } fclose($handle); ?>