エクセルで出力された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 件のコメント:
コメントを投稿