CsvReader.inc 4.43 KB
<?php
/**
 * fgetscvの代わりに使うためのクラス。
 * @author mocho
 * @package jp.aimslib2.io
 */

class CsvReader {
	
	/**
	 * 読み込む対象のcsvファイル
	 * @var String $file_path
	 */
	public $file_path;
	
	/**
	 * 結果の配列
	 * @var array
	 */
	public $result;
	
	/**
	 * 囲い文字
	 */
	public $quote;
	
	/**
	 * 区切り文字
	 */
	public $delim;
	
	/**
	 * 処理用の一時データ
	 * @var String
	 */
	public $cell_data;
	
	/**
	 * コンストラクタ
	 * @param String $file_path 読み込むファイル名
	 */
	public function __construct($file_path, $delim = "\t", $quote = "\"") {
		// ファイル名必須
		$this->file_path = $file_path;
		// 初期設定
		$this->quote = $quote;
		$this->delim = $delim;
		
	}
	
	/**
	 * 読み込んで解析します。
	 * @return boolean 成功時、true
	 */
	public function parse($delim = null, $quote = null) {
		
		if (!file_exists($this->file_path)) {
			return false;
		}
		if (!is_readable($this->file_path)) {
			return false;
		}
		// 区切り文字の変更があれば変更
		if ($delim != null) {
			$this->delim = $delim;
		}
		// 囲い文字の変更があれば変更
		if ($quote != null) {
			$this->quote = $quote;
		}
		
		// 結果配列初期化
		$this->result = array();
		
		$fp = fopen($this->file_path, "r");
		
		// 前のセルのデータ
		$buffer = "";
		// 新しいセルか?
		$is_new_cell = true;
		// 行毎の一時データ
		$row = null;
		// 行毎に読み込み
		while (!feof($fp)) {
			$line = fgets($fp);
			$line = rtrim($line);
			$tmp_list = explode($this->delim, $line);
			
			// 全行からの続きであるか、新行であるかで違う
			if ($is_new_cell) {
				// CSV1行に該当
				$row = array();
				$buffer = "";
			}
			
			
			// 各要素をチェック
			for ($x = 0; $x < count($tmp_list); $x++) {
				
				if ($is_new_cell) {
					$tmp = $tmp_list[$x];
				} else {
					if ($x == 0) {
						// 1行目なら、区切りでなく改行
						$tmp = $buffer . "\n" . $tmp_list[$x];
					} else {
						$tmp = $buffer . $this->delim . $tmp_list[$x];
					}
				}
				
				// 囲い文字で始まっていなければ、そのまま生データ
				if (substr($tmp, 0, 1) == $this->quote) {
					// 囲われている
					if (CsvReader::_privateEndsWithQuote($tmp)) {
						// 最後がちゃんと囲い文字で終わっていれば
						$tmp2 = CsvReader::_privateEscapeQuote($tmp);
						array_push($row, rtrim($tmp2));
						
						if (!$is_new_cell) {
							// 前追加してなかったら、追加処理
							$is_new_cell = true;
						}
						
					} else {
						// 囲われていない
						$buffer = $tmp;
						// 継続データ
						$is_new_cell = false;
						
					}
					
				} else {
					// 生データ
					array_push($row, rtrim($tmp));
					
				}
				
			}
			if ($is_new_cell) {
				// 前追加してなかったら、追加処理
				array_push($this->result, $row);
			}
				
			
		}
		
		
		
		
		
		fclose($fp);
		return true;
	}
	
	
	/**
	 * 囲い文字で終わるか?
	 * @param String $str 評価対象文字列
	 * @return boolean 囲い文字で終わる場合、true
	 */
	public function _privateEndsWithQuote($str) {
		//ErrorLogger::doOutput("_privateEndsWithQuote:" . $str, 0);
		if (substr($str, -1, 1) != $this->quote) {
			return false;
		}
		
		// 最後の文字は囲い文字、あとは、それがエスケープされているか?
		$result = true;
		$x = -2;
		while ($x > 0 - strlen($str)) {
			if (substr($str, $x, 1) != $this->quote) {
				return $result;
			}
			if ($result) {
				$result = false;
			} else {
				$result = true;
			}
			$x--;
		}
		
		// 囲われてない、というか、内容が無い
		return false;
	}
	
	/**
	 * 余分な囲い文字を取り除きます。
	 * また、2重の""を元に戻します。
	 * @param String $str 文字列
	 * @return String 囲い文字が無い文字列
	 */
	public function _privateEscapeQuote($str) {
		$str = trim($str, $this->quote);
		$str = str_replace($this->quote . $this->quote, $this->quote, $str);
		return $str;
	}
	
	
	/**
	 * 結果を返します。
	 * 最後の1行が余計な場合、それを取り除きます。
	 */
	public function getResult() {
		if (count($this->result) > 0) {
			$last = $this->result[count($this->result) - 1];
			if (is_array($last) && count($last) == 1 && $last[0] === "") {
				// 空の最終行
				array_pop($this->result);
			}
			
		}
		return $this->result;
	}
	
}


?>