Recent changes

2013-04-14 2013-04-02 2013-02-17 2013-01-29 2013-01-20 2013-01-18 2012-12-08 2012-11-06 2012-09-04 2012-08-02

php/Tips/csv

CSVの読み込み

fgetcsvにはいろいろと問題があるのでryer氏がohzak氏のperlの正規表現を参考に書いた以下の関数を利用すると幸せになれるかもしれない。

FIle::CSVは使えないとのRandolph氏からの報告。

CSV Parser version 0.3

/** 
 * CSV行を値のリストに変換します
 * @author halt feits
 * @param string csv
 * @return array
 */
function csv2values($csv)
{
    $result = array();
    $lines = explode("

", $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 Parser version 0.2

/**
 * CSV行を値のリストに変換します
 * @author ryer
 * @param string csv
 * @return array
 */
function csv2values($csv)
{
    $values = array();
    $temp = preg_replace('/(?:x0Dx0A|[x0Dx0A])?$/', ',', $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;
}
  1. 値に含まれるダブルクォートは "" となる.

という条件は""より"のほうがPHPの世界では自然です。適用すると以下のようになります。

ほとんどかわってないですけどw とりあえずfgetcsvで思うように取れなかった値が上の関数でとれた。満足。

CSV Parser version 0.1

<code> <?php

/**
 * CSV行を値のリストに変換します
 * @author ryer
 * @param string csv
 * @return array
 */
function csv2values($csv)
{
    $values = array();
    $temp = preg_replace('/(?:x0Dx0A|[x0Dx0A])?$/', ',', $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>

このコードは

  1. 基本的にコンマで区切った部分がスペースを含めて値である.
  2. 値にコンマやダブルクォートが含まれる場合は, 値全体をダブルクウォートで囲む.
  3. 値に含まれるダブルクォートは "" となる.

という条件で抽出しています。最後の条件は""より"のほうがPHPの世界では自然です。適用するとこのようになります。

comment

[[halt>UserPage/halt]] &size(80%){2006-02-08 12:09:07}: わーわー添削ありがとうございます。助かります。