* @license PHP License * @package WB * @subpackage base */ WBClass::load('WBFile'); /** * File operations for CSV files * * @version 1.0.0 * @package WB * @subpackage base */ class WBFile_CSV extends WBFile { /** * file handle * @var resource */ private $fh; /** * CSV format info * @var array */ private $format = array( 'del' => ',', 'enc' => '"' ); /** * column map * @var array */ private $map = array(); /** * number of columns per line * @var int */ private $cols = -1; /** * String format source to convert, if any * @var string */ private $iconvSrc = null; /** * Open CSV file * * Wrapper to fopen() * * @see fopen() * @param string $mode */ public function open($mode = 'r') { $this->fh = fopen($this->realpath(), $mode); return $this; } public function close() { fclose($this->fh); return $this; } /** * Fseek wrapper * * Jump to position in file * * @see fseek() * @param int $offset * @param int $whence */ public function seek($offset, $whence = SEEK_SET) { fseek($this->fh, $offset, $whence); } /** * Ftell wrapper * * Get current offset in file * * @see ftell() * @return int */ public function tell() { return ftell($this->fh); } /** * Feof wrapper * * @see feof() * @return bool */ public function eof() { return feof($this->fh); } /** * Read row * * Get row from CSV file using delimiter and endlosure * * @see fgetscsv() * @return array */ public function read() { $row = fgetcsv($this->fh, 0, $this->format['del'], $this->format['enc']); if (empty($row)) { return array(); } // force number of columns if (0 < $this->cols) { // read more lines to fill multi line colums while (count($row) < $this->cols) { if (feof($this->fh)) { break; } $line = fgetcsv($this->fh, 0, $this->format['del'], $this->format['enc']); $row[count($row) - 1] .= "\n" . array_shift($line); if (count($line)) { $row = array_merge($row, $line); } } } $row = array_map(array($this, 'onReadColumn'), $row); if (empty($this->map)) { return $row; } $data = array(); foreach ($this->map as $k => $i) { if (isset($row[$i])) { $data[$k] = $row[$i]; } } return $data; } /** * Map function for each column * * @param string * @return string */ private function onReadColumn($col) { if ($this->iconvSrc) { $col = iconv($this->iconvSrc, 'UTF-8', $col); } return trim($col); } /** * Write row * * Put row to CSV file using delimiter and enclosure * * @see fputcsv() * @param array $data */ public function write($data) { if (empty($this->map)) { $row = array_values($data); } else { $row = array(); foreach ($this->map as $k => $i) { $row[$i] = ''; if (isset($data[$k])) { $row[$i] = $data[$k]; } } } return fputcsv($this->fh, $row, $this->format['del'], $this->format['enc']); } /** * Set map to support associative arrays * * Optional map allow to support associative arrays * * @param array $map */ public function setMap($map = array()) { $this->map = $map; } /** * Force Number of Data Columns * * Make sure that all rows contain a defined number of columns. This is especially * usefull for multi-line content. Use force columns to fold multi-line columns into * single data rows * * @see read() * @param int $columns optional number of columns */ public function forceColumns($columns = -1) { $this->cols = $columns; } /** * Tell which format to use read/write CSV file * * Set delimiter and enclosure * * @param string $del * @param string $enc */ public function setFormat($del = ';', $enc = '"') { $this->format['del'] = $del; $this->format['enc'] = $enc; return $this; } /** * Tell to convert strings to UTF-8 * * @param string $source */ public function convert2UTF8($source = null) { $this->iconvSrc = $source; } /** * Try to detect CSV file's format * * Detect delimiter, enclosure of CSV file. * * @return bool */ public function guessFormat() { if (!$this->fh) { WBClass::load('WBException_Call'); throw new WBException_Call('Call open() first', 1, __CLASS__); } $delimiter = array(';', ',', "\t"); $enclosure = array("'", '"'); $ok = false; foreach ($delimiter as $d) { foreach ($enclosure as $e) { fseek($this->fh, 0); $row1 = fgetcsv($this->fh, 0, $d, $e); $row2 = array(); if (feof($this->fh)) { fseek($this->fh, 0); } $row2 = fgetcsv($this->fh, 0, $d, $e); // require two columns if (2 > min(count($row1), count($row2))) { continue; } $oe = '"'; if ('"' == $e) { $oe = "'"; } // does enclosure work as expected? foreach ($row1 as $c) { // onclosure does not seem to match, try next if ($c != trim($c, $oe)) { continue 2; } } $ok = true; $this->format['enc'] = $e; $this->format['del'] = $d; break 2; } } fseek($this->fh, 0); return $ok; } }