fgetcsvにはいろいろと問題があるのでryer氏がohzak氏のperlの正規表現を参考に書いた以下の関数を利用すると幸せになれるかもしれない。
FIle::CSVは使えないとのRandolph氏からの報告。
/**
* CSV行を値のリストに変換します
* @author halt feits
* @param string csv
* @return array
*/
function csv2values($csv)
{
$result = array();
$lines = explode("\n", $csv);
$separator = ',';
$separator_escaped = '-_\\\\_-';
foreach ($lines as $line) {
//escape
preg_match_all('|"(.*?)"|', $line, $out);
foreach ($out[1] as $column) {
$column_after = str_replace($separator, $separator_escaped, $column);
$line = str_replace($column, $column_after, $line);
}
$params = explode($separator, $line);
//unescape
foreach ($params as $key => $param) {
$params[$key] = str_replace($separator_escaped, $separator, $param);
}
$result[] = $params;
}
if (count($result) == 1) {
$result = current($result);
}
return $result;
}
速度は一切無視してコメント欄で報告してもらった状況でもパースできるように再実装した。 テストは以下のような感じで。
require_once 'lime/lib/lime.php';
$t = new lime_test();
$test_data = '1,2,3,4,5';
$t->is(count(csv2values($test_data)), 5);
$test_data = '"1",2,3,4,5';
$t->is(count(csv2values($test_data)), 5);
$test_data = '"1,1-1",2,3,4,5';
$t->is(count(csv2values($test_data)), 5);
$test_data = '1,2,"3","4",5';
$t->is(count(csv2values($test_data)), 5);
$test_data = <<<EOD
1,2,"3","4",5
a,b,c,d,e'
EOD;
$result = csv2values($test_data);
$t->is(count($result), 2);
$t->is(count($result[0]), 5);
?>
/**
* CSV行を値のリストに変換します
* @author ryer
* @param string csv
* @return array
*/
function csv2values($csv)
{
$values = array();
$temp = preg_replace('/(?:\x0D\x0A|[\x0D\x0A])?$/', ',', $csv, 1);
preg_match_all('/("[^"]*(?:\\"[^"]*)*"|[^,]*),/', $temp, $matches);
for ($i=0; $i<count($matches[1]); $i++) {
if (preg_match('/^"(.*)"$/', $matches[1][$i], $m)) {
$matches[1][$i] = $m[1];
}
$values[] = $matches[1][$i];
}
return $values;
}
という条件は""より\"のほうがPHPの世界では自然です。適用すると以下のようになります。
ほとんどかわってないですけどw とりあえずfgetcsvで思うように取れなかった値が上の関数でとれた。満足。
<code> <?php
/**
* CSV行を値のリストに変換します
* @author ryer
* @param string csv
* @return array
*/
function csv2values($csv)
{
$values = array();
$temp = preg_replace('/(?:\x0D\x0A|[\x0D\x0A])?$/', ',', $csv, 1);
preg_match_all('/("[^"]*(?:""[^"]*)*"|[^,]*),/', $temp, $matches);
for ($i=0; $i<count($matches[1]); $i++) {
if (preg_match('/^"(.*)"$/', $matches[1][$i], $m)) {
$matches[1][$i] = $m[1];
$matches[1][$i] = preg_replace('/""/', '"', $matches[1][$i]);
}
$values[] = $matches[1][$i];
}
return $values;
}
?> </code>
このコードは
という条件で抽出しています。最後の条件は""より\"のほうがPHPの世界では自然です。適用するとこのようになります。