2010年6月24日木曜日

PHPでTSV処理

エクセルで出力されたTSVを想定。

CSVのほうが知名度はあるが、TSVが好ましい(区切りには制御文字のほうが適している、それこそ制御文字の本分でしょう、という気がする)。

エクセルでデータを作る人、つまり非プログラマー、非SEの人は普通TSVのことを知らないが、そこは事前に説明してTSVで出力するようお願いしておく。

TSVに関する大まかな仕様

  • 改行を含む列は"で始まり"で終わる
  • タブ、二重引用符を含んでもいい
  • SJIS(PHPプログラム内ではUTF8に変換して処理する)

TSVファイルを読み込んで連想配列の配列に変換(PHP)

/*
 * TSVファイルを読み込んで連想配列の配列に変換
 * 引数:処理結果(参照渡し)、TSVファイルのパス
 */
function parseTSV(&$records, $path){
    $fp = fopen($path, 'r');
    if($fp===FALSE){
        return FALSE;
    }

    $records = array();
    $errors = array();
    //$cnt = 0; // これはデバッグ専用のリミット(後で削除)
    //    while(!feof($fp) && $cnt < 10){
    while(!feof($fp)){

        $line = fgets($fp);
        $line = mb_convert_encoding($line, 'UTF-8', 'SJIS-win');
        $line = str_replace(array("\r\n","\r"), "\n", $line);
        $len = mb_strlen($line, 'UTF-8');

        $state = 'START';
        $flg = 0;
        $buf = '';
        $fields = array();
        //        $cnt++;

        for($i=0; $i<$len; $i++){
            $char = mb_substr($line, $i, 1, 'UTF-8');
            if($state=='START'){
                if($char === '"' && $flg == 0) {
                    $flg = 1;
                    $state = 'MID';
                } elseif($char === "\n" && $flg == 0) {
                    break;
                } elseif($char === "\t") {
                    $state = 'END';
                } else {
                    $buf .= $char;
                    $state = 'MID';
                }
            } elseif($state == 'MID') {
                if($char === "\t") {
                    if($flg){
                        $buflen = mb_strlen($buf, 'UTF-8');
                        if($buflen && mb_substr($buf, $buflen - 1, 1, 'UTF-8') == '"') {
                            $state = 'END';
                        } else {
                            $char = ' ';
                        }
                    } else {
                        $state = 'END';
                    }
                } elseif($char === "\n"){
                    if($flg){
                        $buf .= $char;
                        // データが次の行に続いている場合
                        $line = fgets($fp);
                        $line = mb_convert_encoding($line, 'UTF-8', 'SJIS-win');
                        $line = str_replace(array("\r\n","\r"), "\n", $line);
                        $len = mb_strlen($line, 'UTF-8');
                        $i = -1; // ここは強引
                    } else {
                        $state = 'END';
                    }
                } else {
                    $buf .= $char;
                }
            }
            if($state == 'END') {
                if($flg){
                    $buf = mb_substr($buf, 0, mb_strlen($buf, 'UTF-8') - 1, 'UTF-8');
                }
                $buf = mb_ereg_replace('""', '"', $buf); // 連続したダブルクォートは1つに

                // 連想配列に追加して、初期化
                $fields[] = $buf;
                $state = 'START';
                $flg = 0;
                $buf = '';
            }
        }
        // 1レコード読み込み終了
        if(count($fields) > 0) {
            $records[] = $fields;
        }
    }
    fclose($fp);

    return TRUE;
}

0 件のコメント:

コメントを投稿