C言語とかでは意外とCSVをパーズするライブラリがなかったりします。
PerlだとCPANのText::CSV_XSなんかがなかなかいけてたりします。
でもたしかPurePerlじゃなかった気がする。
で、PHPの場合はどうかというとビルトイン関数な感じでfgetcsv()があったりして面白い言語なわけです。
が、Webなどを見ているとバグっぽい動作をしたりといまいち評判はよくなさげ。
しかもfputcsv()はPHP5以上じゃないと使えない。
そういうわけで、(fgetcsv()を使ってみることもせずに)CSV取り扱いクラスを作って見ました。
オートマトンぽくパーズしてます。
それなりに考えたので状態遷移はあってるんじゃないかと思われますが、まあ、しらん。
ファイル丸呑みなのでメモリは使うし、1文字ずつ照合してるので速度はどうなのかという問題は多々ありますが、まあ、しらん。
オートマトンぽい部分はC言語とかにもそのまま持っていける感じじゃないでしょうか。
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
//mb_language( 'Japanese' );
/**
* $csv_in = new Csv("file_in.csv", "r");
* $csv_out = new Csv("file_out.csv", "w");
* $csv_in->open();
* $csv_out->open();
*
* while($columns = $csv_in->getline()) {
* print_r($columns);
* $csv_out->putline($columns);
* }
* $csv_in->close();
* $csv_out->close();
*/
class Csv {
var $filePath;
var $mode;
var $contents_utf8;
var $contents_size;
var $wfh;
var $iterator;
var $comma;
var $quote;
function Csv($filePath, $mode, $comma = ',', $quote = '"') {
$this->filePath = $filePath;
$this->mode = $mode;
$this->iterator = 0;
$this->comma = $comma;
$this->quote = $quote;
}
function open() {
if ("r" === $this->mode) {
$contents = file_get_contents($this->filePath);
if (!$contents) {
return false;
}
$this->contents_utf8 = mb_convert_encoding($contents, "UTF-8", "auto");
$this->contents_size = strlen($this->contents_utf8);
} else {
$this->wfh = fopen($this->filePath, $this->mode);
}
return true;
}
function close() {
if ("r" === $this->mode) {
} else {
fclose($this->wfh);
}
}
/**
* state
* -1:1行読み込み終了
* 0:セル入力終了状態
* 1:ダブルクオートセル開始状態
* 2:ダブルクオートセル内のダブルクオート出現状態
* 3:非ダブルクオートセルの入力中状態
* 4:ダブルクオートセルの入力中状態
* 5:CR出現状態
* @return array フィールド
* false EOF
*/
function getline() {
$fields = array();
$cell = "";
$successRead;
$eof = true;
$state = 0;
while( true ) {
if ($this->iterator >= $this->contents_size) {
$state = -1;
$successRead = false;
} else {
$successRead = true;
$eof = false;
$c = $this->contents_utf8[$this->iterator];
$this->iterator++;
}
if ($state == -1) { // オートマトン
$state = -1;
} elseif ($state == 0) {
if ($c === $this->comma) {
$state = 0;
} elseif ($c === "\n") {
$state = -1;
} elseif ($c === "\r") {
$state = 5;
} elseif ($c === $this->quote) {
$state = 1;
} else {
$state = 3;
}
} else if ($state == 1) {
if ($c === $this->quote) {
$state = 2;
} else {
$state = 4;
}
} elseif ($state == 2) {
if ($c === $this->quote) {
$state = 4;
} elseif ($c === $this->comma) {
$state = 0;
} elseif ($c === "\n") {
$state = -1;
} elseif ($c === "\r") {
$state = 5;
} else {
$state = 4; // エラー
}
} elseif ($state == 3) {
if ($c === $this->comma) {
$state = 0;
} elseif ($c === "\n") {
$state = -1;
} elseif ($c === "\r") {
$state = 5;
} else {
$state = 3;
}
} elseif ($state == 4) {
if ($c === $this->quote) {
$state = 2;
} else {
$state = 4;
}
} elseif ($state == 5) {
if ($c !== "\n") {
$this->iterator--; // 読み戻る
}
$state = -1;
} else {
$state = -1; // こない
} // オートマトン
if ($successRead && ($state == 0 || $state == -1)) {
// セル入力終了
$fields[] = $cell;
$cell = "";
} elseif ($state == 3 || $state == 4) {
// セル入力中
$cell .= $c;
}
if ($state == -1) {
// 1行読み込み終了
break;
}
} // while
return $eof ? false : $fields;
} // getline
function putline($fields, $forceQuote = false, $line_feed = true) {
$size = count($fields);
//mb_regex_encoding("UTF-8");
$replace_quote = $this->quote . $this->quote;
$line = "";
for ($i = 0; $i < $size; $i++) {
$outQuote = $forceQuote;
//mb_ereg_replace($this->quote, $replace_quote, $fields[$i], "m");
$fields[$i] = str_replace($this->quote, $replace_quote, $fields[$i]);
if (!$forceQuote && (mb_strpos($fields[$i], "\n", 0, "UTF-8") || mb_strpos($fields[$i], "\r", 0, "UTF-8")) ) {
$outQuote = true;
}
if ($outQuote) $line .= $this->quote;
$line .= $fields[$i];
if ($outQuote) $line .= $this->quote;
if ($i < $size - 1) $line .= $this->comma;
}
// 行末処理
if ($line_feed) {
// 改行出力
$line .= "\n";
} else {
// 改行せずにコンマを出力する場合
$line .= $this->comma;
}
fwrite($this->wfh, mb_convert_encoding($line, "sjis", "UTF-8"));
return $i;
}
}