PHP 想將 CSV 轉成陣列,只要使用 str_getcsv() 即可,不過 CSV 的格式通常不會讓人這麼好過,CSV 常見幾個問題先列在下面:
- Big5 編碼 (Excel 存檔成 CSV 和要打開預設都是需要 Big5)
- BOM (若有存 UTF-8 的話,大多數會有 BOM)
- 分隔符號 (常見 "\t" 和 ","),有時候還會確保整個字串,會用 """ 包起來 (三個 double quote)
- 第一行是標題,想直接拿來當 Key 操作
在此篇上述就不解決(其它文章有寫解法,在此不詳述),只解決 3 和 4 的問題。
PHP 將 CSV、TSV 使用第一欄當 Key 來轉成陣列
最近把第一欄當 Key 來操作 CSV 檔,蠻常使用的,所以想把古早前寫的 Code 貼出來即可,沒想到在 str_getcsv() 翻到其他人寫的作法,也蠻有趣的,所以也改寫幾個版本出來,有興趣可以參考看看。
資料準備 (以下都已 UTF-8 為主)
- data.csv # 一般常見 csv 多以 "," 分隔
序號,標題,內容,日期 1,title,content,2018-01-01 2,title2,content2,2018-01-02
- data.tsv # 下述 [tab] 請自行取代成 "\t" 的 tab
序號[tab]標題[tab]內容[tab]日期 1[tab]title[tab]content[tab]2018-01-01 2[tab]title2[tab]content2[tab]2018-01-02
我最早期寫的 csv_to_array(),用法就是丟入檔名 和 分隔符號,就會用檔案的第一個欄位當 Key,將陣列印出。
<?php function csv_to_array($filename, $delimiter = "\t") { if (!file_exists($filename) || !is_readable($filename)) return false; $header = ''; $data = []; if (($fh = fopen($filename, 'r')) !== false) { while (($csv = fgetcsv($fh, 1000, $delimiter)) !== false) { if (!$header) $header = $csv; else $data[] = array_combine($header, $csv); } } /* // 與上述擇一,上面的是可以處理 CSV 斷行抓取問題 foreach (file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { $csv = str_getcsv($line, $delimiter); if (!$header) $header = $csv; else $data[] = array_combine($header, $csv); } */ return $data; } ?>
使用方式
- print_r(csv_to_array('data.csv', ','));
- print_r(csv_to_array('data.tsv', "\t"));
輸出結果
Array ( [0] => Array ( [序號] => 1 [標題] => title [內容] => content [日期] => 2018-01-01 ) [1] => Array ( [序號] => 2 [標題] => title2 [內容] => content2 [日期] => 2018-01-02 ) )
再來於 PHP 官網的 str_getcsv() 看到的一些寫法,在這邊也做點整理和修改
一個非常簡短的寫法,使用 array_map + array_walk 來達成,一樣使用第一行當 Key,不過這個只能處理 "," 分隔的 CSV。
<?php function csv_to_array($filename) { $csv = array_map('str_getcsv', file($filename)); array_walk($csv, function(&$row) use ($csv) { $row = array_combine($csv[0], $row); }); array_shift($csv); // 移掉第一行的標題陣列 return $csv; } ?>
想要讓這種寫法,也可以處理各種分隔字元的話,處理方式會稍微複雜一點,主要是要如何讓 array_map 操作的 str_getcsv 吃參數的寫法,大概會如下:
- $csv = array_map('str_getcsv', file($filename), ["\t", "\t", "\t"]); // 三行需要三個 Tab
所以改寫如下:(註:if 包的 code 可以都由 else 的區塊取代即可,只是若分隔符號是 ",",就不需要多做那麼多處理)
<?php function csv_to_array($filename, $delimiter = ',') { if (!file_exists($filename) || !is_readable($filename)) return false; if ($delimiter == ',') { $csv = array_map('str_getcsv', file($filename)); } else { $lines = file($filename); $line_num = count($lines); $dm = []; // $delimiter $csv = array_map('str_getcsv', $lines, array_pad($dm, $line_num, $delimiter)); } array_walk($csv, function(&$row) use ($csv) { $row = array_combine($csv[0], $row); }); array_shift($csv); // 移掉第一行的標題陣列 return $csv; } ?>