* * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * @category File * @package File_CSV_DataSource * @author Kazuyoshi Tlacaelel * @copyright 2008 Kazuyoshi Tlacaelel * @license The MIT License * @version SVN: $Id$ * @link http://code.google.com/p/php-csv-parser/ */ /** * csv data fetcher * * Sample snippets refer to this csv file for demonstration. * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * @category File * @package File_CSV_DataSource * @author Kazuyoshi Tlacaelel * @copyright 2008 Kazuyoshi Tlacaelel * @license The MIT License * @link http://code.google.com/p/php-csv-parser/ */ class File_CSV_DataSource { public /** * csv parsing default-settings * * @var array * @access public */ $settings = array( 'delimiter' => ',', 'eol' => ";", 'length' => 999999, 'escape' => '"' ); protected /** * imported data from csv * * @var array * @access protected */ $rows = array(), /** * csv file to parse * * @var string * @access protected */ $_filename = '', /** * csv headers to parse * * @var array * @access protected */ $headers = array(); /** * data load initialize * * @param mixed $filename please look at the load() method * * @access public * @see load() * @return void */ public function __construct($filename = null) { $this->load($filename); } /** * csv file loader * * indicates the object which file is to be loaded * * * * require_once 'File/CSV/DataSource.php'; * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * var_export($csv->connect()); * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * * @param string $filename the csv filename to load * * @access public * @return boolean true if file was loaded successfully * @see isSymmetric(), getAsymmetricRows(), symmetrize() */ public function load($filename) { $this->_filename = $filename; $this->flush(); return $this->parse(); } /** * settings alterator * * lets you define different settings for scanning * * Given array will override the internal settings * * * $settings = array( * 'delimiter' => ',', * 'eol' => ";", * 'length' => 999999, * 'escape' => '"' * ); * * * @param mixed $array containing settings to use * * @access public * @return boolean true if changes where applyed successfully * @see $settings */ public function settings($array) { $this->settings = array_merge($this->settings, $array); } /** * header fetcher * * gets csv headers into an array * * * * var_export($csv->getHeaders()); * * array ( * 0 => 'name', * 1 => 'age', * 2 => 'skill', * ) * * * * @access public * @return array */ public function getHeaders() { return $this->headers; } /** * header counter * * retrives the total number of loaded headers * * @access public * @return integer gets the length of headers */ public function countHeaders() { return count($this->headers); } /** * header and row relationship builder * * Attempts to create a relationship for every single cell that * was captured and its corresponding header. The sample below shows * how a connection/relationship is built. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * if (!$csv->isSymmetric()) { * die('file has headers and rows with different lengths * cannot connect'); * } * * var_export($csv->connect()); * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * * * You can pass a collection of headers in an array to build * a connection for those columns only! * * * * var_export($csv->connect(array('age'))); * * array ( * 0 => * array ( * 'age' => '13', * ), * 1 => * array ( * 'age' => '8', * ), * 2 => * array ( * 'age' => '5', * ), * ) * * * * @param array $columns the columns to connect, if nothing * is given all headers will be used to create a connection * * @access public * @return array If the data is not symmetric an empty array * will be returned instead * @see isSymmetric(), getAsymmetricRows(), symmetrize(), getHeaders() */ public function connect($columns = array()) { if (!$this->isSymmetric()) { return array(); } if (!is_array($columns)) { return array(); } if ($columns === array()) { $columns = $this->headers; } $ret_arr = array(); foreach ($this->rows as $record) { $item_array = array(); foreach ($record as $column => $value) { $header = $this->headers[$column]; if (in_array($header, $columns)) { $item_array[$header] = $value; } } // do not append empty results if ($item_array !== array()) { array_push($ret_arr, $item_array); } } return $ret_arr; } /** * data length/symmetry checker * * tells if the headers and all of the contents length match. * Note: there is a lot of methods that won't work if data is not * symmetric this method is very important! * * @access public * @return boolean * @see symmetrize(), getAsymmetricRows(), isSymmetric() */ public function isSymmetric() { $hc = count($this->headers); foreach ($this->rows as $data) { if (count($data) != $hc) { return false; } } return true; } /** * asymmetric data fetcher * * finds the rows that do not match the headers length * * lets assume that we add one more row to our csv file. * that has only two values. Something like * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * niki,6 * * * Then in our php code * * * $csv->load('my_cool.csv'); * var_export($csv->getAsymmetricRows()); * * * The result * * * * array ( * 0 => * array ( * 0 => 'niki', * 1 => '6', * ), * ) * * * * @access public * @return array filled with rows that do not match headers * @see getHeaders(), symmetrize(), isSymmetric(), * getAsymmetricRows() */ public function getAsymmetricRows() { $ret_arr = array(); $hc = count($this->headers); foreach ($this->rows as $row) { if (count($row) != $hc) { $ret_arr[] = $row; } } return $ret_arr; } /** * all rows length equalizer * * makes the length of all rows and headers the same. If no $value is given * all unexistent cells will be filled with empty spaces * * @param mixed $value the value to fill the unexistent cells * * @access public * @return array * @see isSymmetric(), getAsymmetricRows(), symmetrize() */ public function symmetrize($value = '') { $max_length = 0; foreach ($this->rows as $row) { $row_length = count($row); if ($max_length < $row_length) { $max_length = $row_length; } } foreach ($this->rows as $key => $row) { $this->rows[$key] = array_pad($row, $max_length, $value); } $this->headers = array_pad($this->headers, $max_length, $value); } /** * grid walker * * travels through the whole dataset executing a callback per each * cell * * Note: callback functions get the value of the cell as an * argument, and whatever that callback returns will be used to * replace the current value of that cell. * * @param string $callback the callback function to be called per * each cell in the dataset. * * @access public * @return void * @see walkColumn(), walkRow(), fillColumn(), fillRow(), fillCell() */ public function walkGrid($callback) { foreach (array_keys($this->getRows()) as $key) { if (!$this->walkRow($key, $callback)) { return false; } } return true; } /** * column fetcher * * gets all the data for a specific column identified by $name * * Note $name is the same as the items returned by getHeaders() * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * var_export($csv->getColumn('name')); * * * the above example outputs something like * * * * array ( * 0 => 'john', * 1 => 'tanaka', * 2 => 'jose', * ) * * * * @param string $name the name of the column to fetch * * @access public * @return array filled with values of a column * @see getHeaders(), fillColumn(), appendColumn(), getCell(), getRows(), * getRow(), hasColumn() */ public function getColumn($name) { if (!in_array($name, $this->headers)) { return array(); } $ret_arr = array(); $key = array_search($name, $this->headers, true); foreach ($this->rows as $data) { $ret_arr[] = $data[$key]; } return $ret_arr; } /** * column existance checker * * checks if a column exists, columns are identified by their * header name. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * $headers = $csv->getHeaders(); * * * now lets check if the columns exist * * * var_export($csv->hasColumn($headers[0])); // true * var_export($csv->hasColumn('age')); // true * var_export($csv->hasColumn('I dont exist')); // false * * * @param string $string an item returned by getHeaders() * * @access public * @return boolean * @see getHeaders() */ public function hasColumn($string) { return in_array($string, $this->headers); } /** * column appender * * Appends a column and each or all values in it can be * dinamically filled. Only when the $values argument is given. * * * * var_export($csv->fillColumn('age', 99)); * true * * var_export($csv->appendColumn('candy_ownership', array(99, 44, 65))); * true * * var_export($csv->appendColumn('import_id', 111111111)); * true * * var_export($csv->connect()); * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => 99, * 'skill' => 'knows magic', * 'candy_ownership' => 99, * 'import_id' => 111111111, * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => 99, * 'skill' => 'makes sushi', * 'candy_ownership' => 44, * 'import_id' => 111111111, * ), * 2 => * array ( * 'name' => 'jose', * 'age' => 99, * 'skill' => 'dances salsa', * 'candy_ownership' => 65, * 'import_id' => 111111111, * ), * ) * * * * @param string $column an item returned by getHeaders() * @param mixed $values same as fillColumn() * * @access public * @return boolean * @see getHeaders(), fillColumn(), fillCell(), createHeaders(), * setHeaders() */ public function appendColumn($column, $values = null) { if ($this->hasColumn($column)) { return false; } $this->headers[] = $column; $length = $this->countHeaders(); $rows = array(); foreach ($this->rows as $row) { $rows[] = array_pad($row, $length, ''); } $this->rows = $rows; if ($values === null) { $values = ''; } return $this->fillColumn($column, $values); } /** * collumn data injector * * fills alll the data in the given column with $values * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * // if the csv file loads * if ($csv->load('my_cool.csv')) { * * // grab all data within the age column * var_export($csv->getColumn('age')); * * // rename all values in it with the number 99 * var_export($csv->fillColumn('age', 99)); * * // grab all data within the age column * var_export($csv->getColumn('age')); * * // rename each value in a column independently * $data = array(1, 2, 3); * $csv->fillColumn('age', $data); * * var_export($csv->getColumn('age')); * } * * * standard output * * * array ( * 0 => '13', * 1 => '8', * 2 => '5', * ) * * * * true * * * * array ( * 0 => 99, * 1 => 99, * 2 => 99, * ) * * * * array ( * 0 => 1, * 1 => 2, * 2 => 3, * ) * * * @param mixed $column the column identified by a string * @param mixed $values ither one of the following * - (Number) will fill the whole column with the value of number * - (String) will fill the whole column with the value of string * - (Array) will fill the while column with the values of array * the array gets ignored if it does not match the length of rows * * @access public * @return void */ public function fillColumn($column, $values = null) { if (!$this->hasColumn($column)) { return false; } if ($values === null) { return false; } if (!$this->isSymmetric()) { return false; } $y = array_search($column, $this->headers); if (is_numeric($values) || is_string($values)) { foreach (range(0, $this->countRows() -1) as $x) { $this->fillCell($x, $y, $values); } return true; } if ($values === array()) { return false; } $length = $this->countRows(); if (is_array($values) && $length == count($values)) { for ($x = 0; $x < $length; $x++) { $this->fillCell($x, $y, $values[$x]); } return true; } return false; } /** * column remover * * Completly removes a whole column identified by $name * Note: that this function will only work if data is symmetric. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load the library and csv file * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * * lets dump currently loaded data * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * and now let's remove the second column * * * var_export($csv->removeColumn('age')); * * * output * * * true * * * those changes made let's dump the data again and see what we got * * * array ( * 0 => * array ( * 'name' => 'john', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'skill' => 'dances salsa', * ), * ) * * * @param string $name same as the ones returned by getHeaders(); * * @access public * @return boolean * @see hasColumn(), getHeaders(), createHeaders(), setHeaders(), * isSymmetric(), getAsymmetricRows() */ public function removeColumn($name) { if (!in_array($name, $this->headers)) { return false; } if (!$this->isSymmetric()) { return false; } $key = array_search($name, $this->headers); unset($this->headers[$key]); $this->resetKeys($this->headers); foreach ($this->rows as $target => $row) { unset($this->rows[$target][$key]); $this->resetKeys($this->rows[$target]); } return $this->isSymmetric(); } /** * column walker * * goes through the whole column and executes a callback for each * one of the cells in it. * * Note: callback functions get the value of the cell as an * argument, and whatever that callback returns will be used to * replace the current value of that cell. * * @param string $name the header name used to identify the column * @param string $callback the callback function to be called per * each cell value * * @access public * @return boolean * @see getHeaders(), fillColumn(), appendColumn() */ public function walkColumn($name, $callback) { if (!$this->isSymmetric()) { return false; } if (!$this->hasColumn($name)) { return false; } if (!function_exists($callback)) { return false; } $column = $this->getColumn($name); foreach ($column as $key => $cell) { $column[$key] = $callback($cell); } return $this->fillColumn($name, $column); } /** * cell fetcher * * gets the value of a specific cell by given coordinates * * Note: That indexes start with zero, and headers are not * searched! * * For example if we are trying to grab the cell that is in the * second row and the third column * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * we would do something like * * var_export($csv->getCell(1, 2)); * * * and get the following results * * 'makes sushi' * * * @param integer $x the row to fetch * @param integer $y the column to fetch * * @access public * @return mixed|false the value of the cell or false if the cell does * not exist * @see getHeaders(), hasCell(), getRow(), getRows(), getColumn() */ public function getCell($x, $y) { if ($this->hasCell($x, $y)) { $row = $this->getRow($x); return $row[$y]; } return false; } /** * cell value filler * * replaces the value of a specific cell * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * * $csv = new File_CSV_DataSource; * * // load the csv file * $csv->load('my_cool.csv'); * * // find out if the given coordinate is valid * if($csv->hasCell(1, 1)) { * * // if so grab that cell and dump it * var_export($csv->getCell(1, 1)); // '8' * * // replace the value of that cell * $csv->fillCell(1, 1, 'new value'); // true * * // output the new value of the cell * var_export($csv->getCell(1, 1)); // 'new value' * * } * * * now lets try to grab the whole row * * * // show the whole row * var_export($csv->getRow(1)); * * * standard output * * * array ( * 0 => 'tanaka', * 1 => 'new value', * 2 => 'makes sushi', * ) * * * @param integer $x the row to fetch * @param integer $y the column to fetch * @param mixed $value the value to fill the cell with * * @access public * @return boolean * @see hasCell(), getRow(), getRows(), getColumn() */ public function fillCell($x, $y, $value) { if (!$this->hasCell($x, $y)) { return false; } $row = $this->getRow($x); $row[$y] = $value; $this->rows[$x] = $row; return true; } /** * checks if a coordinate is valid * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load the csv file * * * $csv = new File_CSV_DataSource; * var_export($csv->load('my_cool.csv')); // true if file is * // loaded * * * find out if a coordinate is valid * * * var_export($csv->hasCell(99, 3)); // false * * * check again for a know valid coordinate and grab that cell * * * var_export($csv->hasCell(1, 1)); // true * var_export($csv->getCell(1, 1)); // '8' * * * @param mixed $x the row to fetch * @param mixed $y the column to fetch * * @access public * @return void */ public function hasCell($x, $y) { $has_x = array_key_exists($x, $this->rows); $has_y = array_key_exists($y, $this->headers); return ($has_x && $has_y); } /** * row fetcher * * Note: first row is zero * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load the library and csv file * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * * lets dump currently loaded data * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * Now let's fetch the second row * * * var_export($csv->getRow(1)); * * * output * * * array ( * 0 => 'tanaka', * 1 => '8', * 2 => 'makes sushi', * ) * * * @param integer $number the row number to fetch * * @access public * @return array the row identified by number, if $number does * not exist an empty array is returned instead */ public function getRow($number) { $raw = $this->rows; if (array_key_exists($number, $raw)) { return $raw[$number]; } return array(); } /** * multiple row fetcher * * Extracts a rows in the following fashion * - all rows if no $range argument is given * - a range of rows identified by their key * - if rows in range are not found nothing is retrived instead * - if no rows were found an empty array is returned * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load the library and csv file * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * * lets dump currently loaded data * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * now get the second and thirdh row * * * var_export($csv->getRows(array(1, 2))); * * * output * * * array ( * 0 => * array ( * 0 => 'tanaka', * 1 => '8', * 2 => 'makes sushi', * ), * 1 => * array ( * 0 => 'jose', * 1 => '5', * 2 => 'dances salsa', * ), * ) * * * now lets try something odd and the goodie third row * * * var_export($csv->getRows(array(9, 2))); * * * output * * * array ( * 0 => * array ( * 0 => 'jose', * 1 => '5', * 2 => 'dances salsa', * ), * ) * * * @param array $range a list of rows to retrive * * @access public * @return array */ public function getRows($range = array()) { if (is_array($range) && ($range === array())) { return $this->rows; } if (!is_array($range)) { return $this->rows; } $ret_arr = array(); foreach ($this->rows as $key => $row) { if (in_array($key, $range)) { $ret_arr[] = $row; } } return $ret_arr; } /** * row counter * * This function will exclude the headers * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * php implementation * * * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * var_export($csv->countRows()); // returns 3 * * * @access public * @return integer */ public function countRows() { return count($this->rows); } /** * row appender * * Aggregates one more row to the currently loaded dataset * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * * first let's load the file and output whatever was retrived. * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * var_export($csv->connect()); * * * output * * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * now lets do some modifications, let's try adding three rows. * * * var_export($csv->appendRow(1)); * var_export($csv->appendRow('2')); * var_export($csv->appendRow(array(3, 3, 3))); * * * output * * * true * true * true * * * and now let's try to see what has changed * * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * 3 => * array ( * 'name' => 1, * 'age' => 1, * 'skill' => 1, * ), * 4 => * array ( * 'name' => '2', * 'age' => '2', * 'skill' => '2', * ), * 5 => * array ( * 'name' => 3, * 'age' => 3, * 'skill' => 3, * ), * ) * * * @param array $values the values to be appended to the row * * @access public * @return boolean */ public function appendRow($values) { $this->rows[] = array(); $this->symmetrize(); return $this->fillRow($this->countRows() - 1, $values); } /** * fillRow * * Replaces the contents of cells in one given row with $values. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * if we load the csv file and fill the second row with new data? * * * // load the library * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * * // load csv file * $csv->load('my_cool.csv'); * * // fill exitent row * var_export($csv->fillRow(1, 'x')); * * * output * * * true * * * now let's dump whatever we have changed * * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'x', * 'age' => 'x', * 'skill' => 'x', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * now lets try to fill the row with specific data for each cell * * * var_export($csv->fillRow(1, array(1, 2, 3))); * * * output * * * true * * * and dump the results * * * var_export($csv->connect()); * * * output * * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 1, * 'age' => 2, * 'skill' => 3, * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * @param integer $row the row to fill identified by its key * @param mixed $values the value to use, if a string or number * is given the whole row will be replaced with this value. * if an array is given instead the values will be used to fill * the row. Only when the currently loaded dataset is symmetric * * @access public * @return boolean * @see isSymmetric(), getAsymmetricRows(), symmetrize(), fillColumn(), * fillCell(), appendRow() */ public function fillRow($row, $values) { if (!$this->hasRow($row)) { return false; } if (is_string($values) || is_numeric($values)) { foreach ($this->rows[$row] as $key => $cell) { $this->rows[$row][$key] = $values; } return true; } $eql_to_headers = ($this->countHeaders() == count($values)); if (is_array($values) && $this->isSymmetric() && $eql_to_headers) { $this->rows[$row] = $values; return true; } return false; } /** * row existance checker * * Scans currently loaded dataset and * checks if a given row identified by $number exists * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load library and csv file * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * * build a relationship and dump it so we can see the rows we will * be working with * * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => // THIS ROW EXISTS!!! * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * now lets check for row existance * * * var_export($csv->hasRow(1)); * var_export($csv->hasRow(-1)); * var_export($csv->hasRow(9999)); * * * output * * * true * false * false * * * @param mixed $number a numeric value that identifies the row * you are trying to fetch. * * @access public * @return boolean * @see getRow(), getRows(), appendRow(), fillRow() */ public function hasRow($number) { return (in_array($number, array_keys($this->rows))); } /** * row remover * * removes one row from the current data set. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * first let's load the file and output whatever was retrived. * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * var_export($csv->connect()); * * * output * * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * now lets remove the second row * * * var_export($csv->removeRow(1)); * * * output * * * true * * * now lets dump again the data and see what changes have been * made * * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * @param mixed $number the key that identifies that row * * @access public * @return boolean * @see hasColumn(), getHeaders(), createHeaders(), setHeaders(), * isSymmetric(), getAsymmetricRows() */ public function removeRow($number) { $cnt = $this->countRows(); $row = $this->getRow($number); if (is_array($row) && ($row != array())) { unset($this->rows[$number]); } else { return false; } $this->resetKeys($this->rows); return ($cnt == ($this->countRows() + 1)); } /** * row walker * * goes through one full row of data and executes a callback * function per each cell in that row. * * Note: callback functions get the value of the cell as an * argument, and whatever that callback returns will be used to * replace the current value of that cell. * * @param string|integer $row anything that is numeric is a valid row * identificator. As long as it is within the range of the currently * loaded dataset * * @param string $callback the callback function to be executed * per each cell in a row * * @access public * @return boolean * - false if callback does not exist * - false if row does not exits */ public function walkRow($row, $callback) { if (!function_exists($callback)) { return false; } if ($this->hasRow($row)) { foreach ($this->getRow($row) as $key => $value) { $this->rows[$row][$key] = $callback($value); } return true; } return false; } /** * raw data as array * * Gets the data that was retrived from the csv file as an array * * Note: that changes and alterations made to rows, columns and * values will also reflect on what this function retrives. * * @access public * @return array * @see connect(), getHeaders(), getRows(), isSymmetric(), getAsymmetricRows(), * symmetrize() */ public function getRawArray() { $ret_arr = array(); $ret_arr[] = $this->headers; foreach ($this->rows as $row) { $ret_arr[] = $row; } return $ret_arr; } /** * header creator * * uses prefix and creates a header for each column suffixed by a * numeric value * * by default the first row is interpreted as headers but if we * have a csv file with data only and no headers it becomes really * annoying to work with the current loaded data. * * this function will create a set dinamically generated headers * and make the current headers accessable with the row handling * functions * * Note: that the csv file contains only data but no headers * sample of a csv file "my_cool.csv" * * * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * checks if the csv file was loaded * * * $csv = new File_CSV_DataSource; * if (!$csv->load('my_cool.csv')) { * die('can not load csv file'); * } * * * dump current headers * * * var_export($csv->getHeaders()); * * * standard output * * * array ( * 0 => 'john', * 1 => '13', * 2 => 'knows magic', * ) * * * generate headers named 'column' suffixed by a number and interpret * the previous headers as rows. * * * $csv->createHeaders('column') * * * dump current headers * * * var_export($csv->getHeaders()); * * * standard output * * * array ( * 0 => 'column_1', * 1 => 'column_2', * 2 => 'column_3', * ) * * * build a relationship and dump it * * * var_export($csv->connect()); * * * output * * * * array ( * 0 => * array ( * 'column_1' => 'john', * 'column_2' => '13', * 'column_3' => 'knows magic', * ), * 1 => * array ( * 'column_1' => 'tanaka', * 'column_2' => '8', * 'column_3' => 'makes sushi', * ), * 2 => * array ( * 'column_1' => 'jose', * 'column_2' => '5', * 'column_3' => 'dances salsa', * ), * ) * * * @param string $prefix string to use as prefix for each * independent header * * @access public * @return boolean fails if data is not symmetric * @see isSymmetric(), getAsymmetricRows() */ public function createHeaders($prefix) { if (!$this->isSymmetric()) { return false; } $length = count($this->headers) + 1; $this->moveHeadersToRows(); $ret_arr = array(); for ($i = 1; $i < $length; $i ++) { $ret_arr[] = $prefix . "_$i"; } $this->headers = $ret_arr; return $this->isSymmetric(); } /** * header injector * * uses a $list of values which wil be used to replace current * headers. * * Note: that given $list must match the length of all rows. * known as symmetric. see isSymmetric() and getAsymmetricRows() methods * * Also, that current headers will be used as first row of data * and consecuently all rows order will change with this action. * * sample of a csv file "my_cool.csv" * * * name,age,skill * john,13,knows magic * tanaka,8,makes sushi * jose,5,dances salsa * * * load the library and csv file * * * require_once 'File/CSV/DataSource.php'; * $csv = new File_CSV_DataSource; * $csv->load('my_cool.csv'); * * * lets dump currently loaded data * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'name' => 'john', * 'age' => '13', * 'skill' => 'knows magic', * ), * 1 => * array ( * 'name' => 'tanaka', * 'age' => '8', * 'skill' => 'makes sushi', * ), * 2 => * array ( * 'name' => 'jose', * 'age' => '5', * 'skill' => 'dances salsa', * ), * ) * * * And now lets create a new set of headers and attempt to inject * them into the current loaded dataset * * * $new_headers = array('a', 'b', 'c'); * var_export($csv->setHeaders($new_headers)); * * * output * * * true * * * Now lets try the same with some headers that do not match the * current headers length. (this should fail) * * * $new_headers = array('a', 'b'); * var_export($csv->setHeaders($new_headers)); * * * output * * * false * * * now let's dump whatever we have changed * * * var_export($csv->connect()); * * * output * * * array ( * 0 => * array ( * 'a' => 'name', * 'b' => 'age', * 'c' => 'skill', * ), * 1 => * array ( * 'a' => 'john', * 'b' => '13', * 'c' => 'knows magic', * ), * 2 => * array ( * 'a' => 'tanaka', * 'b' => '8', * 'c' => 'makes sushi', * ), * 3 => * array ( * 'a' => 'jose', * 'b' => '5', * 'c' => 'dances salsa', * ), * ) * * * @param array $list a collection of names to use as headers, * * @access public * @return boolean fails if data is not symmetric * @see isSymmetric(), getAsymmetricRows(), getHeaders(), createHeaders() */ public function setHeaders($list) { if (!$this->isSymmetric()) { return false; } if (!is_array($list)) { return false; } if (count($list) != count($this->headers)) { return false; } $this->moveHeadersToRows(); $this->headers = $list; return true; } /** * csv parser * * reads csv data and transforms it into php-data * * @access protected * @return boolean */ protected function parse() { if (!$this->validates()) { return false; } $c = 0; $d = $this->settings['delimiter']; $e = $this->settings['escape']; $l = $this->settings['length']; // $res = fopen($this->_filename, 'r'); $res = $this->_patch(); while ($keys = fgetcsv($res, $l, $d, $e)) { if ($c == 0) { $this->headers = $keys; } else { array_push($this->rows, $keys); } $c ++; } fclose($res); $this->_unpatch(); $this->removeEmpty(); return true; } /** * empty row remover * * removes all records that have been defined but have no data. * * @access protected * @return array containing only the rows that have data */ protected function removeEmpty() { $ret_arr = array(); foreach ($this->rows as $row) { $line = trim(join('', $row)); if (!empty($line)) { $ret_arr[] = $row; } } $this->rows = $ret_arr; } /** * csv file validator * * checks wheather if the given csv file is valid or not * * @access protected * @return boolean */ protected function validates() { // file existance if (!file_exists($this->_filename)) { return false; } // file readability if (!is_readable($this->_filename)) { return false; } return true; } /** * header relocator * * @access protected * @return void */ protected function moveHeadersToRows() { $arr = array(); $arr[] = $this->headers; foreach ($this->rows as $row) { $arr[] = $row; } $this->rows = $arr; $this->headers = array(); } /** * array key reseter * * makes sure that an array's keys are setted in a correct numerical order * * Note: that this function does not return anything, all changes * are made to the original array as a reference * * @param array &$array any array, if keys are strings they will * be replaced with numeric values * * @access protected * @return void */ protected function resetKeys(&$array) { $arr = array(); foreach ($array as $item) { $arr[] = $item; } $array = $arr; } /** * object data flusher * * tells this object to forget all data loaded and start from * scratch * * @access protected * @return void */ protected function flush() { $this->rows = array(); $this->headers = array(); } /** * hash for patch * * @access private * @return string */ private function _patchHash() { return md5(md5('php-csv-parser')); } /** * fgetcsv patch * * - BUG_NUMBER_ONE, cell values starting with multibyte chars do * not parse correctly! * * - BUG_NUMBER_TWO, commas escaped withing quotes do not parse properly * if the escaped area is withing the cell rather than escaping the * whole cell ea. (field1, field2 "this fails, help!", field3) * * @access private * @return resource a file resource (fopen) */ private function _patch() { // get contents from file $data = file_get_contents($this->_filename); $hash = $this->_patchHash(); // make replacements $data = preg_replace('/^/m', $hash, $data); $data = preg_replace('/,/', "$hash,$hash", $data); $rege = '/' . preg_quote($hash) . '"/'; $data = preg_replace($rege, '"', $data); // put contents from file into temp file $t_fname = tempnam(md5(''), __CLASS__ . '_'); file_put_contents($t_fname, $data, FILE_APPEND); return fopen($t_fname, 'r'); } /** * clanses the patched data in order to be processed * * @access private * @return void */ private function _unpatch() { $hash = '/' . $this->_patchHash() . '/'; foreach ($this->rows as $key => $row) { foreach ($row as $target => $cell) { $cell = preg_replace($hash, '', $cell); $this->rows[$key][$target] = trim($cell); } } foreach ($this->headers as $key => $col) { $col = preg_replace($hash, '', $col); $this->headers[$key] = trim($col); } } } ?>